Phase 7 of v2.0. BooCoder gains a terminal-driven UX and subagent isolation primitive. CLI (src/cli.ts): standalone entry point for terminal use. - boocode run "task" [--agent x] [--model y] — create + stream output - boocode ls [--state x] — formatted task table - boocode attach <id> — WS stream of running task - boocode send <id> "msg" — follow-up message to task session Connects to BOOCODER_URL (default http://100.114.205.53:9502). Human inbox (routes/inbox.ts): GET /api/inbox (failed/blocked tasks), POST /api/inbox/:id/retry (reset to pending for re-dispatch). Cost tracking: dispatcher aggregates tokens_used from all messages in the task's session after completion, stores in tasks.cost_tokens. GET /api/stats/costs?group_by=project|agent|day for aggregation. Boomerang subagent isolation (3 new tools): - new_task: creates child task with parent_task_id linkage, runs in fresh isolated session. Orchestrator sees only output_summary. - list_tasks: query child tasks of current parent - check_task_status: read task state + output_summary The orchestrator pattern: an agent with tools: [new_task, list_tasks, check_task_status] can ONLY dispatch — can't read files or MCP. This is the Roo Code Boomerang Tasks capability-restriction principle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 lines
1.1 KiB
TypeScript
34 lines
1.1 KiB
TypeScript
import type { FastifyInstance } from 'fastify';
|
|
import type { Sql } from '../db.js';
|
|
|
|
export function registerInboxRoutes(app: FastifyInstance, sql: Sql): void {
|
|
// GET /api/inbox — tasks needing human attention (blocked or failed)
|
|
app.get('/api/inbox', async () => {
|
|
return sql`
|
|
SELECT id, project_id, parent_task_id, state, input, output_summary, agent, model, session_id, started_at, ended_at, created_at
|
|
FROM human_inbox
|
|
ORDER BY created_at DESC
|
|
LIMIT 100
|
|
`;
|
|
});
|
|
|
|
// POST /api/inbox/:id/retry — reset a blocked/failed task to pending for re-dispatch
|
|
app.post<{ Params: { id: string } }>('/api/inbox/:id/retry', async (req, reply) => {
|
|
const taskId = req.params.id;
|
|
|
|
const result = await sql`
|
|
UPDATE tasks
|
|
SET state = 'pending', started_at = NULL, ended_at = NULL, output_summary = NULL
|
|
WHERE id = ${taskId} AND state IN ('blocked', 'failed')
|
|
RETURNING id, state
|
|
`;
|
|
|
|
if (result.length === 0) {
|
|
reply.code(404);
|
|
return { error: 'task not found or not in retryable state' };
|
|
}
|
|
|
|
return { id: result[0]!.id, state: result[0]!.state };
|
|
});
|
|
}
|