v2.0.3: CLI client + human inbox + cost tracking + Boomerang new_task
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>
This commit is contained in:
48
apps/coder/src/routes/stats.ts
Normal file
48
apps/coder/src/routes/stats.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import type { Sql } from '../db.js';
|
||||
|
||||
const CostQuery = z.object({
|
||||
group_by: z.enum(['project', 'agent', 'day']).default('project'),
|
||||
});
|
||||
|
||||
export function registerStatsRoutes(app: FastifyInstance, sql: Sql): void {
|
||||
// GET /api/stats/costs — aggregate cost_tokens by project, agent, or day
|
||||
app.get('/api/stats/costs', async (req, reply) => {
|
||||
const parsed = CostQuery.safeParse(req.query);
|
||||
if (!parsed.success) {
|
||||
reply.code(400);
|
||||
return { error: 'invalid query', details: parsed.error.flatten() };
|
||||
}
|
||||
|
||||
const { group_by } = parsed.data;
|
||||
|
||||
switch (group_by) {
|
||||
case 'project':
|
||||
return sql`
|
||||
SELECT project_id, COUNT(*)::int AS task_count, COALESCE(SUM(cost_tokens), 0)::int AS total_tokens
|
||||
FROM tasks
|
||||
WHERE cost_tokens IS NOT NULL
|
||||
GROUP BY project_id
|
||||
ORDER BY total_tokens DESC
|
||||
`;
|
||||
case 'agent':
|
||||
return sql`
|
||||
SELECT COALESCE(agent, 'native') AS agent, COUNT(*)::int AS task_count, COALESCE(SUM(cost_tokens), 0)::int AS total_tokens
|
||||
FROM tasks
|
||||
WHERE cost_tokens IS NOT NULL
|
||||
GROUP BY agent
|
||||
ORDER BY total_tokens DESC
|
||||
`;
|
||||
case 'day':
|
||||
return sql`
|
||||
SELECT DATE(created_at) AS day, COUNT(*)::int AS task_count, COALESCE(SUM(cost_tokens), 0)::int AS total_tokens
|
||||
FROM tasks
|
||||
WHERE cost_tokens IS NOT NULL
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY day DESC
|
||||
LIMIT 90
|
||||
`;
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user