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:
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user