feat(goal-run): add multi-checkpoint goal orchestration with H5 and admin surfaces.

Persist goal runs in MySQL, bind agent runs to checkpoints, expose awaiting-approval
UX in chat, and add admin inspection routes with local verify scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-01 17:03:16 +08:00
parent 43bc8bbc2b
commit 666db0b939
47 changed files with 4417 additions and 4 deletions
+1
View File
@@ -1508,6 +1508,7 @@ export async function createAgentRun(
...(options.selectedAssetIds?.length
? { selected_asset_ids: options.selectedAssetIds }
: {}),
...(options.goalRunId ? { goal_run_id: options.goalRunId } : {}),
}),
},
{ timeoutMs: AGENT_CONNECT_TIMEOUT_MS },
+77
View File
@@ -0,0 +1,77 @@
import type { GoalRun } from '../types';
import type { AgentRun } from './client';
import { apiFetch, ApiError } from './core';
export type GoalRunAwaitingItem = {
goal: GoalRun;
checkpoint: GoalRun['checkpoints'][number];
};
export function findAwaitingApprovalCheckpoint(goal: GoalRun) {
return goal.checkpoints.find((item) => item.status === 'awaiting_approval') ?? null;
}
export function listAwaitingGoalRuns(goals: GoalRun[]): GoalRunAwaitingItem[] {
return goals
.map((goal) => {
const checkpoint = findAwaitingApprovalCheckpoint(goal);
return checkpoint ? { goal, checkpoint } : null;
})
.filter((item): item is GoalRunAwaitingItem => item != null);
}
export async function listGoalRuns(options?: {
statuses?: string[];
limit?: number;
}): Promise<GoalRun[]> {
const params = new URLSearchParams();
if (options?.statuses?.length) {
params.set('status', options.statuses.join(','));
}
if (options?.limit != null) {
params.set('limit', String(options.limit));
}
const qs = params.size ? `?${params.toString()}` : '';
const result = await apiFetch<{ goals: GoalRun[] }>(`/goals${qs}`);
return result.goals ?? [];
}
export async function getGoalRun(goalRunId: string): Promise<GoalRun | null> {
try {
const result = await apiFetch<{ goal: GoalRun }>(`/goals/${encodeURIComponent(goalRunId)}`);
return result.goal ?? null;
} catch (err) {
if (err instanceof ApiError && err.status === 404) return null;
throw err;
}
}
export async function approveGoalCheckpoint(
goalRunId: string,
checkpointId: string,
feedback?: string | null,
sessionId?: string | null,
): Promise<{ goal: GoalRun; run?: AgentRun | null }> {
const result = await apiFetch<{ goal: GoalRun; run?: AgentRun | null }>(
`/goals/${encodeURIComponent(goalRunId)}/checkpoints/${encodeURIComponent(checkpointId)}/approve`,
{
method: 'POST',
body: JSON.stringify({
feedback: feedback ?? null,
...(sessionId ? { session_id: sessionId } : {}),
}),
},
);
return { goal: result.goal, run: result.run ?? null };
}
export async function cancelGoalRun(goalRunId: string): Promise<GoalRun> {
const result = await apiFetch<{ goal: GoalRun }>(`/goals/${encodeURIComponent(goalRunId)}`, {
method: 'DELETE',
});
return result.goal;
}
export function isGoalRunApiAvailableError(err: unknown): boolean {
return err instanceof ApiError && (err.status === 403 || err.status === 503);
}