diff --git a/docs/ecosystem/ECOSYSTEM_IMPLEMENTATION_TRACKER.md b/docs/ecosystem/ECOSYSTEM_IMPLEMENTATION_TRACKER.md index 9e85fb85..c7caf2eb 100644 --- a/docs/ecosystem/ECOSYSTEM_IMPLEMENTATION_TRACKER.md +++ b/docs/ecosystem/ECOSYSTEM_IMPLEMENTATION_TRACKER.md @@ -242,11 +242,12 @@ These should be resolved before claiming the ecosystem docs are fully implementa Status note: - Cowork product-native runtime projections are now implemented in cowork-service - `023826e` adds `GET /api/agent-runtime/sessions`, `GET /api/agent-runtime/runs`, `GET /api/agent-runtime/approvals`, and `POST /api/agent-runtime/dispatch/validate` + - `COMMIT_PENDING` adds `GET /api/agent-runtime/tasks` with canonical `AgentTask` projection - FlowMonk runtime-emitter implementation work was started, but this clone cannot currently verify it because the repo depends on a local npm registry at `http://localhost:3300` and backend dependencies are not installed ### 6.1 Remaining Direct Runtime TODOs -- Cowork: add `AgentTask`/`AgentTodo` direct projections once the product exposes first-class task/todo entities beyond current task-run/session surfaces. +- Cowork: add `AgentTodo` direct projection once the product exposes first-class todo entities. - Cowork: attach canonical event IDs to approval and audit trails so ActionTrail lineage can stop using fallback/null semantics. - FlowMonk: finish direct runtime-emitter integration once the local npm registry and backend dependencies are available again. - Platform-service: refine the `queued -> paused` projection fallback once run-vs-session semantics are finalized. diff --git a/docs/ecosystem/PHASE5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md b/docs/ecosystem/PHASE5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md index c37a1026..f8f3a737 100644 --- a/docs/ecosystem/PHASE5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md +++ b/docs/ecosystem/PHASE5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md @@ -68,10 +68,11 @@ Observed baseline: - the hosted UI supports projected session review, projected run review, and dispatch payload validation - cowork-service now exposes direct product-native runtime projections at: - `GET /api/agent-runtime/sessions` + - `GET /api/agent-runtime/tasks` - `GET /api/agent-runtime/runs` - `GET /api/agent-runtime/approvals` - `POST /api/agent-runtime/dispatch/validate` -- Cowork projections now emit canonical `AgentSession`, `AgentRun`, and `AgentApprovalCheckpoint` objects from the product backend instead of only relying on platform projections +- Cowork projections now emit canonical `AgentSession`, `AgentTask`, `AgentRun`, and `AgentApprovalCheckpoint` objects from the product backend instead of only relying on platform projections --- @@ -96,6 +97,6 @@ Observed baseline: ## 7. Remaining Gaps -- Cowork now emits shared runtime projections from cowork-service, but Rust-side canonical event IDs are still missing on approval/audit records. +- Cowork now emits shared runtime projections from cowork-service, but Rust-side canonical event IDs are still missing on approval/audit records and `AgentTodo` still has no first-class product source. - FlowMonk runtime-emitter code was started locally, but verification in this clone is blocked because the repo depends on a local npm registry at `http://localhost:3300` and its backend `node_modules` are missing. - run-vs-session semantics for queued work still need a stricter mapping than the current projection fallback. diff --git a/services/cowork-service/src/modules/agent-runtime/routes.test.ts b/services/cowork-service/src/modules/agent-runtime/routes.test.ts index fc90e78c..767e37ee 100644 --- a/services/cowork-service/src/modules/agent-runtime/routes.test.ts +++ b/services/cowork-service/src/modules/agent-runtime/routes.test.ts @@ -100,6 +100,34 @@ describe('agent runtime routes', () => { }); }); + it('projects IPC tasks into shared AgentTask objects', async () => { + call.mockResolvedValue({ + 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', + }, + ], + }, + }); + + const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/tasks' }); + expect(res.statusCode).toBe(200); + const body = JSON.parse(res.payload); + expect(body.tasks[0]).toMatchObject({ + taskId: 'task-1', + sessionId: 'sess-1', + status: 'queued', + title: 'Review the repository and summarize changes', + intent: 'Review the repository and summarize changes', + }); + }); + it('projects approval audit records into AgentApprovalCheckpoint objects', async () => { mockFetch .mockResolvedValueOnce({ diff --git a/services/cowork-service/src/modules/agent-runtime/routes.ts b/services/cowork-service/src/modules/agent-runtime/routes.ts index 71839893..8749474c 100644 --- a/services/cowork-service/src/modules/agent-runtime/routes.ts +++ b/services/cowork-service/src/modules/agent-runtime/routes.ts @@ -4,9 +4,11 @@ import { AgentDispatchRequestSchema, AgentRunSchema, AgentSessionSchema, + AgentTaskSchema, type AgentApprovalCheckpoint, type AgentRun, type AgentSession, + type AgentTask, } from '@bytelyst/events'; import { BadRequestError } from '@bytelyst/errors'; import { config } from '../../lib/config.js'; @@ -104,6 +106,43 @@ function toAgentRun(task: Record, fallbackNow: string): AgentRu }); } +function mapTaskProjectionStatus(status: unknown): AgentTask['status'] { + switch (status) { + case 'running': + return 'running'; + case 'completed': + return 'completed'; + case 'failed': + return 'failed'; + case 'cancelled': + return 'cancelled'; + case 'pending': + default: + return 'queued'; + } +} + +function toAgentTask(task: Record, fallbackNow: string): AgentTask { + const taskId = + typeof task.id === 'string' && task.id.length > 0 ? task.id : `task_${fallbackNow}`; + const createdAt = asIsoString(task.createdAt ?? task.startedAt, fallbackNow); + const updatedAt = asIsoString(task.updatedAt ?? task.completedAt ?? createdAt, createdAt); + const goal = + typeof task.goal === 'string' && task.goal.trim().length > 0 ? task.goal.trim() : 'Cowork task'; + + return AgentTaskSchema.parse({ + taskId, + sessionId: + typeof task.sessionId === 'string' && task.sessionId.length > 0 ? task.sessionId : taskId, + title: goal.length > 120 ? `${goal.slice(0, 117)}...` : goal, + intent: goal, + status: mapTaskProjectionStatus(task.status), + priority: typeof task.priority === 'string' ? task.priority : null, + createdAt, + updatedAt, + }); +} + function mapApprovalStatus(action: unknown): AgentApprovalCheckpoint['status'] { switch (action) { case 'approval_granted': @@ -235,6 +274,24 @@ export async function agentRuntimeRoutes(app: FastifyInstance) { }; }); + app.get('/api/agent-runtime/tasks', async req => { + if (!bridge.isRunning) { + return { tasks: [], count: 0 }; + } + + const resp = await bridge.call('list_tasks', { auth: buildAuth(req) }); + if (resp.error) throw new BadRequestError(resp.error.message); + + const raw = (resp.result as { tasks?: Record[] } | undefined)?.tasks ?? []; + const now = new Date().toISOString(); + const tasks = raw.map(task => toAgentTask(task, now)); + + return { + tasks, + count: tasks.length, + }; + }); + app.get('/api/agent-runtime/approvals', async req => { const records = await fetchApprovalRecords(req); const now = new Date().toISOString();