feat(cowork-service): add runtime task projection
This commit is contained in:
parent
600e26cc41
commit
01201f8f56
@ -242,11 +242,12 @@ These should be resolved before claiming the ecosystem docs are fully implementa
|
|||||||
Status note:
|
Status note:
|
||||||
- Cowork product-native runtime projections are now implemented in cowork-service
|
- 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`
|
- `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
|
- 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
|
### 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.
|
- 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.
|
- 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.
|
- Platform-service: refine the `queued -> paused` projection fallback once run-vs-session semantics are finalized.
|
||||||
|
|||||||
@ -68,10 +68,11 @@ Observed baseline:
|
|||||||
- the hosted UI supports projected session review, projected run review, and dispatch payload validation
|
- the hosted UI supports projected session review, projected run review, and dispatch payload validation
|
||||||
- cowork-service now exposes direct product-native runtime projections at:
|
- cowork-service now exposes direct product-native runtime projections at:
|
||||||
- `GET /api/agent-runtime/sessions`
|
- `GET /api/agent-runtime/sessions`
|
||||||
|
- `GET /api/agent-runtime/tasks`
|
||||||
- `GET /api/agent-runtime/runs`
|
- `GET /api/agent-runtime/runs`
|
||||||
- `GET /api/agent-runtime/approvals`
|
- `GET /api/agent-runtime/approvals`
|
||||||
- `POST /api/agent-runtime/dispatch/validate`
|
- `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
|
## 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.
|
- 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.
|
- run-vs-session semantics for queued work still need a stricter mapping than the current projection fallback.
|
||||||
|
|||||||
@ -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 () => {
|
it('projects approval audit records into AgentApprovalCheckpoint objects', async () => {
|
||||||
mockFetch
|
mockFetch
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce({
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import {
|
|||||||
AgentDispatchRequestSchema,
|
AgentDispatchRequestSchema,
|
||||||
AgentRunSchema,
|
AgentRunSchema,
|
||||||
AgentSessionSchema,
|
AgentSessionSchema,
|
||||||
|
AgentTaskSchema,
|
||||||
type AgentApprovalCheckpoint,
|
type AgentApprovalCheckpoint,
|
||||||
type AgentRun,
|
type AgentRun,
|
||||||
type AgentSession,
|
type AgentSession,
|
||||||
|
type AgentTask,
|
||||||
} from '@bytelyst/events';
|
} from '@bytelyst/events';
|
||||||
import { BadRequestError } from '@bytelyst/errors';
|
import { BadRequestError } from '@bytelyst/errors';
|
||||||
import { config } from '../../lib/config.js';
|
import { config } from '../../lib/config.js';
|
||||||
@ -104,6 +106,43 @@ function toAgentRun(task: Record<string, unknown>, 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<string, unknown>, 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'] {
|
function mapApprovalStatus(action: unknown): AgentApprovalCheckpoint['status'] {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'approval_granted':
|
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<string, unknown>[] } | 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 => {
|
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