feat(runtime): add checkpoint summaries to shared and cowork surfaces
This commit is contained in:
parent
3330ca55cd
commit
9aeb9bbd59
@ -1,6 +1,7 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
AgentActionLogSchema,
|
AgentActionLogSchema,
|
||||||
|
AgentCheckpointSchema,
|
||||||
AgentApprovalCheckpointSchema,
|
AgentApprovalCheckpointSchema,
|
||||||
AgentDispatchRequestSchema,
|
AgentDispatchRequestSchema,
|
||||||
AgentRunSchema,
|
AgentRunSchema,
|
||||||
@ -132,4 +133,36 @@ describe('agent runtime contract baseline', () => {
|
|||||||
|
|
||||||
expect(run.status).toBe('queued');
|
expect(run.status).toBe('queued');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('validates checkpoint summaries used for resume review', () => {
|
||||||
|
const checkpoint = AgentCheckpointSchema.parse({
|
||||||
|
checkpointId: 'ckpt_cowork_1',
|
||||||
|
sessionId: 'sess_cowork_1',
|
||||||
|
runId: 'run_cowork_1',
|
||||||
|
productId: 'clawcowork',
|
||||||
|
userId: 'saravana',
|
||||||
|
createdAt: '2026-04-04T12:00:00.000Z',
|
||||||
|
statusAtCapture: 'waiting-approval',
|
||||||
|
currentTaskId: 'task_cowork_1',
|
||||||
|
todoIds: ['todo_cowork_1'],
|
||||||
|
artifactRefs: [],
|
||||||
|
memoryRefs: [],
|
||||||
|
approvalRefs: ['approval_1'],
|
||||||
|
dispatchContext: {
|
||||||
|
originSurface: 'desktop',
|
||||||
|
originProductId: 'clawcowork',
|
||||||
|
dispatchMode: 'interactive',
|
||||||
|
initiatedAt: '2026-04-04T11:58:00.000Z',
|
||||||
|
},
|
||||||
|
resumeToken: 'task_cowork_1',
|
||||||
|
stateSummary: {
|
||||||
|
title: 'Investigate imported roadmap risks',
|
||||||
|
summary: 'Paused for approval after scanning the repository and proposing changes.',
|
||||||
|
lastActionAt: '2026-04-04T12:00:00.000Z',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(checkpoint.statusAtCapture).toBe('waiting-approval');
|
||||||
|
expect(checkpoint.resumeToken).toBe('task_cowork_1');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -62,6 +62,38 @@ export const AgentTodoSchema = z.object({
|
|||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.string().datetime(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const AgentCheckpointStatusSchema = z.enum([
|
||||||
|
'queued',
|
||||||
|
'running',
|
||||||
|
'paused',
|
||||||
|
'waiting-approval',
|
||||||
|
'completed',
|
||||||
|
'failed',
|
||||||
|
'cancelled',
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const AgentCheckpointSchema = z.object({
|
||||||
|
checkpointId: z.string().min(1),
|
||||||
|
sessionId: z.string().min(1),
|
||||||
|
runId: z.string().min(1).nullable().optional(),
|
||||||
|
productId: z.string().min(1),
|
||||||
|
userId: z.string().min(1),
|
||||||
|
createdAt: z.string().datetime(),
|
||||||
|
statusAtCapture: AgentCheckpointStatusSchema,
|
||||||
|
currentTaskId: z.string().min(1).nullable().optional(),
|
||||||
|
todoIds: z.array(z.string().min(1)),
|
||||||
|
artifactRefs: z.array(z.string().min(1)),
|
||||||
|
memoryRefs: z.array(z.string().min(1)),
|
||||||
|
approvalRefs: z.array(z.string().min(1)),
|
||||||
|
dispatchContext: AgentDispatchContextSchema.nullable().optional(),
|
||||||
|
resumeToken: z.string().min(1).nullable().optional(),
|
||||||
|
stateSummary: z.object({
|
||||||
|
title: z.string().min(1),
|
||||||
|
summary: z.string().min(1),
|
||||||
|
lastActionAt: z.string().datetime().nullable().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const AgentRunStatusSchema = z.enum([
|
export const AgentRunStatusSchema = z.enum([
|
||||||
'queued',
|
'queued',
|
||||||
'running',
|
'running',
|
||||||
@ -122,6 +154,7 @@ export type AgentDispatchContext = z.infer<typeof AgentDispatchContextSchema>;
|
|||||||
export type AgentSession = z.infer<typeof AgentSessionSchema>;
|
export type AgentSession = z.infer<typeof AgentSessionSchema>;
|
||||||
export type AgentTask = z.infer<typeof AgentTaskSchema>;
|
export type AgentTask = z.infer<typeof AgentTaskSchema>;
|
||||||
export type AgentTodo = z.infer<typeof AgentTodoSchema>;
|
export type AgentTodo = z.infer<typeof AgentTodoSchema>;
|
||||||
|
export type AgentCheckpoint = z.infer<typeof AgentCheckpointSchema>;
|
||||||
export type AgentRun = z.infer<typeof AgentRunSchema>;
|
export type AgentRun = z.infer<typeof AgentRunSchema>;
|
||||||
export type AgentApprovalCheckpoint = z.infer<typeof AgentApprovalCheckpointSchema>;
|
export type AgentApprovalCheckpoint = z.infer<typeof AgentApprovalCheckpointSchema>;
|
||||||
export type AgentDispatchRequest = z.infer<typeof AgentDispatchRequestSchema>;
|
export type AgentDispatchRequest = z.infer<typeof AgentDispatchRequestSchema>;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export type { DurableEventBusOptions } from './durable.js';
|
|||||||
export { PlatformEventSchemas } from './types.js';
|
export { PlatformEventSchemas } from './types.js';
|
||||||
export {
|
export {
|
||||||
AgentActionLogSchema,
|
AgentActionLogSchema,
|
||||||
|
AgentCheckpointSchema,
|
||||||
AgentApprovalCheckpointSchema,
|
AgentApprovalCheckpointSchema,
|
||||||
AgentDispatchContextSchema,
|
AgentDispatchContextSchema,
|
||||||
AgentDispatchRequestSchema,
|
AgentDispatchRequestSchema,
|
||||||
@ -52,6 +53,7 @@ export {
|
|||||||
} from './timeline.js';
|
} from './timeline.js';
|
||||||
export type {
|
export type {
|
||||||
AgentActionLog,
|
AgentActionLog,
|
||||||
|
AgentCheckpoint,
|
||||||
AgentApprovalCheckpoint,
|
AgentApprovalCheckpoint,
|
||||||
AgentDispatchContext,
|
AgentDispatchContext,
|
||||||
AgentDispatchRequest,
|
AgentDispatchRequest,
|
||||||
|
|||||||
@ -154,20 +154,38 @@ describe('agent runtime routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('projects IPC tasks into shared AgentTodo objects', async () => {
|
it('projects IPC tasks into shared AgentTodo objects', async () => {
|
||||||
call.mockResolvedValue({
|
call
|
||||||
result: {
|
.mockResolvedValueOnce({
|
||||||
tasks: [
|
result: {
|
||||||
{
|
tasks: [
|
||||||
id: 'task-1',
|
{
|
||||||
goal: 'Review the repository and summarize changes',
|
id: 'task-1',
|
||||||
status: 'running',
|
goal: 'Review the repository and summarize changes',
|
||||||
createdAt: '2026-04-04T08:00:00.000Z',
|
status: 'running',
|
||||||
updatedAt: '2026-04-04T08:10:00.000Z',
|
createdAt: '2026-04-04T08:00:00.000Z',
|
||||||
sessionId: 'sess-1',
|
updatedAt: '2026-04-04T08:10:00.000Z',
|
||||||
},
|
sessionId: 'sess-1',
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
});
|
},
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
result: {
|
||||||
|
checkpoints: [
|
||||||
|
{
|
||||||
|
task_id: 'task-1',
|
||||||
|
user_id: 'demo-user',
|
||||||
|
goal: 'Review the repository and summarize changes',
|
||||||
|
created_at: '2026-04-04T08:00:00.000Z',
|
||||||
|
last_updated: '2026-04-04T08:10:00.000Z',
|
||||||
|
completed: false,
|
||||||
|
cancelled: false,
|
||||||
|
completed_tool_calls: 3,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/todos' });
|
const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/todos' });
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
@ -180,6 +198,53 @@ describe('agent runtime routes', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('projects persisted checkpoints into shared AgentCheckpoint objects', async () => {
|
||||||
|
call
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
result: {
|
||||||
|
tasks: [
|
||||||
|
{
|
||||||
|
id: 'task-1',
|
||||||
|
goal: 'Review the repository and summarize changes',
|
||||||
|
status: 'pending',
|
||||||
|
createdAt: '2026-04-04T08:00:00.000Z',
|
||||||
|
updatedAt: '2026-04-04T08:10:00.000Z',
|
||||||
|
sessionId: 'sess-1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
result: {
|
||||||
|
checkpoints: [
|
||||||
|
{
|
||||||
|
task_id: 'task-1',
|
||||||
|
user_id: 'demo-user',
|
||||||
|
goal: 'Review the repository and summarize changes',
|
||||||
|
created_at: '2026-04-04T08:00:00.000Z',
|
||||||
|
last_updated: '2026-04-04T08:10:00.000Z',
|
||||||
|
completed: false,
|
||||||
|
cancelled: false,
|
||||||
|
completed_tool_calls: 2,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/checkpoints' });
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const body = JSON.parse(res.payload);
|
||||||
|
expect(body.checkpoints[0]).toMatchObject({
|
||||||
|
checkpointId: 'ckpt_task-1',
|
||||||
|
sessionId: 'sess-1',
|
||||||
|
runId: 'task-1',
|
||||||
|
userId: 'demo-user',
|
||||||
|
statusAtCapture: 'queued',
|
||||||
|
resumeToken: 'task-1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('projects approval audit records into AgentApprovalCheckpoint objects', async () => {
|
it('projects approval audit records into AgentApprovalCheckpoint objects', async () => {
|
||||||
mockFetch
|
mockFetch
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce({
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
||||||
import {
|
import {
|
||||||
AgentActionLogSchema,
|
AgentActionLogSchema,
|
||||||
|
AgentCheckpointSchema,
|
||||||
AgentApprovalCheckpointSchema,
|
AgentApprovalCheckpointSchema,
|
||||||
AgentDispatchRequestSchema,
|
AgentDispatchRequestSchema,
|
||||||
AgentRunSchema,
|
AgentRunSchema,
|
||||||
@ -8,6 +9,7 @@ import {
|
|||||||
AgentTaskSchema,
|
AgentTaskSchema,
|
||||||
AgentTodoSchema,
|
AgentTodoSchema,
|
||||||
type AgentActionLog,
|
type AgentActionLog,
|
||||||
|
type AgentCheckpoint,
|
||||||
type AgentApprovalCheckpoint,
|
type AgentApprovalCheckpoint,
|
||||||
type AgentRun,
|
type AgentRun,
|
||||||
type AgentSession,
|
type AgentSession,
|
||||||
@ -27,7 +29,13 @@ function buildAuth(req: FastifyRequest): Record<string, unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function asIsoString(value: unknown, fallback: string): string {
|
function asIsoString(value: unknown, fallback: string): string {
|
||||||
return typeof value === 'string' && !Number.isNaN(Date.parse(value)) ? value : fallback;
|
if (typeof value === 'string' && !Number.isNaN(Date.parse(value))) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||||
|
return new Date(value * 1000).toISOString();
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapTaskStatus(status: unknown): AgentRun['status'] {
|
function mapTaskStatus(status: unknown): AgentRun['status'] {
|
||||||
@ -164,25 +172,137 @@ function mapTodoStatus(status: unknown): AgentTodo['status'] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toAgentTodo(task: Record<string, unknown>, fallbackNow: string): AgentTodo {
|
function toAgentTodo(
|
||||||
const todoId =
|
task: Record<string, unknown> | null,
|
||||||
typeof task.id === 'string' && task.id.length > 0 ? `todo_${task.id}` : `todo_${fallbackNow}`;
|
checkpoint: Record<string, unknown>,
|
||||||
const createdAt = asIsoString(task.createdAt ?? task.startedAt, fallbackNow);
|
fallbackNow: string
|
||||||
const updatedAt = asIsoString(task.updatedAt ?? task.completedAt ?? createdAt, createdAt);
|
): AgentTodo {
|
||||||
|
const taskId =
|
||||||
|
typeof checkpoint.task_id === 'string' && checkpoint.task_id.length > 0
|
||||||
|
? checkpoint.task_id
|
||||||
|
: typeof task?.id === 'string' && task.id.length > 0
|
||||||
|
? task.id
|
||||||
|
: fallbackNow;
|
||||||
|
const todoId = `todo_${taskId}`;
|
||||||
|
const createdAt = asIsoString(
|
||||||
|
task?.createdAt ?? checkpoint.created_at ?? checkpoint.last_updated,
|
||||||
|
fallbackNow
|
||||||
|
);
|
||||||
|
const updatedAt = asIsoString(task?.updatedAt ?? checkpoint.last_updated ?? createdAt, createdAt);
|
||||||
const text =
|
const text =
|
||||||
typeof task.goal === 'string' && task.goal.trim().length > 0 ? task.goal.trim() : 'Cowork task';
|
typeof checkpoint.goal === 'string' && checkpoint.goal.trim().length > 0
|
||||||
|
? checkpoint.goal.trim()
|
||||||
|
: typeof task?.goal === 'string' && task.goal.trim().length > 0
|
||||||
|
? task.goal.trim()
|
||||||
|
: 'Cowork task';
|
||||||
|
const status =
|
||||||
|
checkpoint.completed === true
|
||||||
|
? 'done'
|
||||||
|
: checkpoint.cancelled === true
|
||||||
|
? 'dropped'
|
||||||
|
: typeof checkpoint.error === 'string' && checkpoint.error.length > 0
|
||||||
|
? 'dropped'
|
||||||
|
: mapTodoStatus(task?.status);
|
||||||
|
|
||||||
return AgentTodoSchema.parse({
|
return AgentTodoSchema.parse({
|
||||||
todoId,
|
todoId,
|
||||||
sessionId:
|
sessionId:
|
||||||
typeof task.sessionId === 'string' && task.sessionId.length > 0 ? task.sessionId : todoId,
|
typeof task?.sessionId === 'string' && task.sessionId.length > 0 ? task.sessionId : taskId,
|
||||||
text,
|
text,
|
||||||
status: mapTodoStatus(task.status),
|
status,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapCheckpointStatus(
|
||||||
|
task: Record<string, unknown> | null,
|
||||||
|
checkpoint: Record<string, unknown>
|
||||||
|
): AgentCheckpoint['statusAtCapture'] {
|
||||||
|
if (checkpoint.completed === true) return 'completed';
|
||||||
|
if (checkpoint.cancelled === true) return 'cancelled';
|
||||||
|
if (typeof checkpoint.error === 'string' && checkpoint.error.length > 0) return 'failed';
|
||||||
|
|
||||||
|
switch (task?.status) {
|
||||||
|
case 'running':
|
||||||
|
return 'running';
|
||||||
|
case 'completed':
|
||||||
|
return 'completed';
|
||||||
|
case 'failed':
|
||||||
|
return 'failed';
|
||||||
|
case 'cancelled':
|
||||||
|
return 'cancelled';
|
||||||
|
case 'pending':
|
||||||
|
return 'queued';
|
||||||
|
default:
|
||||||
|
return 'paused';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toAgentCheckpoint(
|
||||||
|
task: Record<string, unknown> | null,
|
||||||
|
checkpoint: Record<string, unknown>,
|
||||||
|
fallbackNow: string
|
||||||
|
): AgentCheckpoint {
|
||||||
|
const taskId =
|
||||||
|
typeof checkpoint.task_id === 'string' && checkpoint.task_id.length > 0
|
||||||
|
? checkpoint.task_id
|
||||||
|
: typeof task?.id === 'string' && task.id.length > 0
|
||||||
|
? task.id
|
||||||
|
: `checkpoint_${fallbackNow}`;
|
||||||
|
const createdAt = asIsoString(checkpoint.created_at ?? checkpoint.last_updated, fallbackNow);
|
||||||
|
const lastActionAt = asIsoString(checkpoint.last_updated, createdAt);
|
||||||
|
const goal =
|
||||||
|
typeof checkpoint.goal === 'string' && checkpoint.goal.trim().length > 0
|
||||||
|
? checkpoint.goal.trim()
|
||||||
|
: typeof task?.goal === 'string' && task.goal.trim().length > 0
|
||||||
|
? task.goal.trim()
|
||||||
|
: 'Cowork task';
|
||||||
|
const completedToolCalls =
|
||||||
|
typeof checkpoint.completed_tool_calls === 'number' ? checkpoint.completed_tool_calls : 0;
|
||||||
|
const summary = [
|
||||||
|
`${completedToolCalls} tool calls recorded`,
|
||||||
|
checkpoint.completed === true
|
||||||
|
? 'task completed'
|
||||||
|
: checkpoint.cancelled === true
|
||||||
|
? 'task cancelled'
|
||||||
|
: typeof checkpoint.error === 'string' && checkpoint.error.length > 0
|
||||||
|
? `task failed: ${checkpoint.error}`
|
||||||
|
: 'checkpoint available for resume',
|
||||||
|
].join('; ');
|
||||||
|
|
||||||
|
return AgentCheckpointSchema.parse({
|
||||||
|
checkpointId: `ckpt_${taskId}`,
|
||||||
|
sessionId:
|
||||||
|
typeof task?.sessionId === 'string' && task.sessionId.length > 0 ? task.sessionId : taskId,
|
||||||
|
runId: typeof task?.id === 'string' && task.id.length > 0 ? task.id : taskId,
|
||||||
|
productId: PRODUCT_ID,
|
||||||
|
userId:
|
||||||
|
typeof checkpoint.user_id === 'string' && checkpoint.user_id.length > 0
|
||||||
|
? checkpoint.user_id
|
||||||
|
: 'unknown-user',
|
||||||
|
createdAt,
|
||||||
|
statusAtCapture: mapCheckpointStatus(task, checkpoint),
|
||||||
|
currentTaskId: taskId,
|
||||||
|
todoIds: [`todo_${taskId}`],
|
||||||
|
artifactRefs: [],
|
||||||
|
memoryRefs: [],
|
||||||
|
approvalRefs: [],
|
||||||
|
dispatchContext: {
|
||||||
|
originSurface: 'desktop',
|
||||||
|
originProductId: PRODUCT_ID,
|
||||||
|
dispatchMode: 'interactive',
|
||||||
|
initiatedAt: createdAt,
|
||||||
|
},
|
||||||
|
resumeToken: taskId,
|
||||||
|
stateSummary: {
|
||||||
|
title: goal.length > 120 ? `${goal.slice(0, 117)}...` : goal,
|
||||||
|
summary,
|
||||||
|
lastActionAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function mapApprovalStatus(action: unknown): AgentApprovalCheckpoint['status'] {
|
function mapApprovalStatus(action: unknown): AgentApprovalCheckpoint['status'] {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'approval_granted':
|
case 'approval_granted':
|
||||||
@ -339,6 +459,30 @@ async function fetchApprovalRecords(req: FastifyRequest): Promise<Record<string,
|
|||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchTasks(req: FastifyRequest): Promise<Record<string, unknown>[]> {
|
||||||
|
const bridge = getIpcBridge();
|
||||||
|
if (!bridge?.isRunning) return [];
|
||||||
|
|
||||||
|
const response = await bridge.call('list_tasks', { auth: buildAuth(req) });
|
||||||
|
const result =
|
||||||
|
response.result && typeof response.result === 'object'
|
||||||
|
? (response.result as Record<string, unknown>)
|
||||||
|
: {};
|
||||||
|
return Array.isArray(result.tasks) ? (result.tasks as Record<string, unknown>[]) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCheckpoints(req: FastifyRequest): Promise<Record<string, unknown>[]> {
|
||||||
|
const bridge = getIpcBridge();
|
||||||
|
if (!bridge?.isRunning) return [];
|
||||||
|
|
||||||
|
const response = await bridge.call('list_checkpoints', { auth: buildAuth(req) });
|
||||||
|
const result =
|
||||||
|
response.result && typeof response.result === 'object'
|
||||||
|
? (response.result as Record<string, unknown>)
|
||||||
|
: {};
|
||||||
|
return Array.isArray(result.checkpoints) ? (result.checkpoints as Record<string, unknown>[]) : [];
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchAuditRecords(req: FastifyRequest): Promise<Record<string, unknown>[]> {
|
async function fetchAuditRecords(req: FastifyRequest): Promise<Record<string, unknown>[]> {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
days: '30',
|
days: '30',
|
||||||
@ -423,12 +567,25 @@ export async function agentRuntimeRoutes(app: FastifyInstance) {
|
|||||||
return { todos: [], count: 0 };
|
return { todos: [], count: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await bridge.call('list_tasks', { auth: buildAuth(req) });
|
const [rawTasks, checkpoints] = await Promise.all([fetchTasks(req), fetchCheckpoints(req)]);
|
||||||
if (resp.error) throw new BadRequestError(resp.error.message);
|
const taskMap = new Map(
|
||||||
|
rawTasks
|
||||||
const raw = (resp.result as { tasks?: Record<string, unknown>[] } | undefined)?.tasks ?? [];
|
.filter(task => typeof task.id === 'string' && task.id.length > 0)
|
||||||
|
.map(task => [task.id as string, task])
|
||||||
|
);
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const todos = raw.map(task => toAgentTodo(task, now));
|
const todos =
|
||||||
|
checkpoints.length > 0
|
||||||
|
? checkpoints.map(checkpoint =>
|
||||||
|
toAgentTodo(
|
||||||
|
typeof checkpoint.task_id === 'string'
|
||||||
|
? (taskMap.get(checkpoint.task_id) ?? null)
|
||||||
|
: null,
|
||||||
|
checkpoint,
|
||||||
|
now
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: rawTasks.map(task => toAgentTodo(task, { task_id: task.id, goal: task.goal }, now));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
todos,
|
todos,
|
||||||
@ -436,6 +593,32 @@ export async function agentRuntimeRoutes(app: FastifyInstance) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/api/agent-runtime/checkpoints', async req => {
|
||||||
|
if (!bridge.isRunning) {
|
||||||
|
return { checkpoints: [], count: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rawTasks, checkpoints] = await Promise.all([fetchTasks(req), fetchCheckpoints(req)]);
|
||||||
|
const taskMap = new Map(
|
||||||
|
rawTasks
|
||||||
|
.filter(task => typeof task.id === 'string' && task.id.length > 0)
|
||||||
|
.map(task => [task.id as string, task])
|
||||||
|
);
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const projected = checkpoints.map(checkpoint =>
|
||||||
|
toAgentCheckpoint(
|
||||||
|
typeof checkpoint.task_id === 'string' ? (taskMap.get(checkpoint.task_id) ?? null) : null,
|
||||||
|
checkpoint,
|
||||||
|
now
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
checkpoints: projected,
|
||||||
|
count: projected.length,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/agent-runtime/approvals', async req => {
|
app.get('/api/agent-runtime/approvals', async req => {
|
||||||
const records = await fetchApprovalRecords(req);
|
const records = await fetchApprovalRecords(req);
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user