Files
Agent-System/web/src/engine/planCard.ts
T
weichenandCursor 29bb63f766 feat(web): render PLAN_CONFIRM from PlanView without local AST writes
Plan actions post to the decision API and reconcile on 409 instead of mutating documents.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 01:09:05 +00:00

105 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { CardActionStatus, InteractiveCardData } from '../types/workbench';
import type { IntentPlanView } from '../api/intentPlans';
export function isPlanConfirmCard(
card?: Pick<InteractiveCardData, 'cardType' | 'planId'> | null,
payload?: Record<string, string>,
): boolean {
if (card?.cardType === 'PLAN_CONFIRM' || card?.planId) return true;
return Boolean(payload?.plan_id);
}
export function planCardTouchesLocalAST(_actionId?: string): boolean {
return false;
}
export function mapPlanStatusToCard(status: IntentPlanView['status']): CardActionStatus {
switch (status) {
case 'executing':
return 'executing';
case 'completed':
case 'partially_completed':
return 'resolved';
case 'cancelled':
case 'superseded':
return 'cancelled';
case 'expired':
return 'expired';
case 'failed':
return 'outdated';
default:
return 'active';
}
}
export function cardFromPlanView(plan: IntentPlanView): InteractiveCardData {
const steps = plan.steps ?? [];
const writeish = steps.filter((s) => s.action_key.startsWith('task.') || s.action_key.includes('run'));
const summary =
plan.safe_summary ||
steps.map((s) => s.safe_summary || s.action_key).filter(Boolean).join(';');
return {
cardId: `plan-${plan.plan_id}-r${plan.revision}`,
cardType: 'PLAN_CONFIRM',
title: `计划确认(${steps.length} 步)`,
proposedContent: summary,
impactRisk:
writeish.length > 0
? '含生成/写入:逐步提交,后续失败不回滚'
: '只读查询:确认后按步执行',
status: mapPlanStatusToCard(plan.status),
ttlTurns: 3,
planId: plan.plan_id,
planRevision: plan.revision,
stepCount: steps.length,
validatedOnly: plan.execution_mode === 'validated_only',
actions: [
{
actionId: 'plan_confirm',
label: '确认执行',
style: 'primary',
isAffirmativeDefault: true,
payload: {
plan_id: plan.plan_id,
expected_revision: String(plan.revision),
kind: 'confirm',
},
},
{
actionId: 'plan_cancel',
label: '取消计划',
style: 'subtle',
payload: {
plan_id: plan.plan_id,
expected_revision: String(plan.revision),
kind: 'cancel',
},
},
],
};
}
export function applyPlanViewToCard(card: InteractiveCardData, plan: IntentPlanView): InteractiveCardData {
const next = cardFromPlanView(plan);
return {
...card,
...next,
cardId: card.cardId,
};
}
export function markPlanCard(card: InteractiveCardData, status: CardActionStatus, summary?: string): InteractiveCardData {
return {
...card,
status,
resolutionSummary: summary ?? card.resolutionSummary,
};
}
export function reconcileConflictCard(card: InteractiveCardData, latest?: IntentPlanView | null): InteractiveCardData {
if (latest) {
return { ...applyPlanViewToCard(card, latest), status: 'outdated' };
}
return markPlanCard(card, 'outdated', '计划已更新,请查看最新版本');
}