Files
Agent-System/web/src/api/intentPlans.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

112 lines
3.1 KiB
TypeScript

import { getBearerAuthHeaders } from './authHeaders';
export type IntentPlanStatus =
| 'proposed'
| 'awaiting_clarification'
| 'awaiting_confirmation'
| 'executing'
| 'partially_completed'
| 'completed'
| 'failed'
| 'cancelled'
| 'expired'
| 'superseded';
export type IntentPlanView = {
plan_id: string;
revision: number;
status: IntentPlanStatus;
execution_mode?: string;
expires_at?: string;
safe_summary?: string;
steps?: Array<{
step_id: string;
action_key: string;
status: string;
execution_mode?: string;
depends_on?: string[];
safe_summary?: string;
}>;
};
export type IntentPlanEnvelope = {
plan: IntentPlanView | null;
error_code?: string;
message?: string;
};
function scopeQuery(projectId: number, sessionId: string): string {
return `project_id=${encodeURIComponent(String(projectId))}&session_id=${encodeURIComponent(sessionId)}`;
}
export async function fetchActiveIntentPlan(
projectId: number,
sessionId: string,
signal?: AbortSignal,
): Promise<IntentPlanView | null> {
const res = await fetch(`/api/v1/intent-plans/active?${scopeQuery(projectId, sessionId)}`, {
signal,
headers: getBearerAuthHeaders(),
});
if (res.status === 404) return null;
if (!res.ok) {
throw new Error(`fetchActiveIntentPlan failed: HTTP ${res.status}`);
}
const raw = (await res.json()) as IntentPlanEnvelope;
return raw.plan ?? null;
}
export async function fetchIntentPlan(
planId: string,
projectId: number,
sessionId: string,
signal?: AbortSignal,
): Promise<IntentPlanView | null> {
const res = await fetch(
`/api/v1/intent-plans/${encodeURIComponent(planId)}?${scopeQuery(projectId, sessionId)}`,
{ signal, headers: getBearerAuthHeaders() },
);
if (res.status === 404) return null;
if (!res.ok) {
throw new Error(`fetchIntentPlan failed: HTTP ${res.status}`);
}
const raw = (await res.json()) as IntentPlanEnvelope;
return raw.plan ?? null;
}
export type IntentPlanDecisionInput = {
planId: string;
projectId: number;
sessionId: string;
expectedRevision: number;
decisionNonce: string;
decisionDigest: string;
kind: 'confirm' | 'cancel' | 'exclude' | 'edit' | 'confirm_retry' | 'continue';
stepIds?: string[];
excludeUnselected?: boolean;
};
export async function submitIntentPlanDecision(
input: IntentPlanDecisionInput,
signal?: AbortSignal,
): Promise<IntentPlanEnvelope & { httpStatus: number }> {
const res = await fetch(
`/api/v1/intent-plans/${encodeURIComponent(input.planId)}/decisions?${scopeQuery(input.projectId, input.sessionId)}`,
{
method: 'POST',
signal,
headers: getBearerAuthHeaders('application/json'),
body: JSON.stringify({
expected_revision: input.expectedRevision,
decision_nonce: input.decisionNonce,
decision_digest: input.decisionDigest,
kind: input.kind,
step_ids: input.stepIds,
exclude_unselected: input.excludeUnselected,
}),
},
);
const raw = (await res.json().catch(() => ({}))) as IntentPlanEnvelope;
return { ...raw, httpStatus: res.status };
}