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>
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
import { z } from 'zod';
|
|
import type { ToolDef, ToolContext } from './types.js';
|
|
import { getInferenceContext } from './inference_context.js';
|
|
|
|
const ListTasksInput = z.object({
|
|
parent_task_id: z.string().uuid().optional().describe('Filter by parent task ID. Omit to list children of current task.'),
|
|
});
|
|
|
|
type ListTasksInputT = z.infer<typeof ListTasksInput>;
|
|
|
|
export const listTasksTool: ToolDef<ListTasksInputT> = {
|
|
name: 'list_tasks',
|
|
description: 'List child tasks of the current task (or a specified parent). Returns id, state, input preview, and output_summary.',
|
|
inputSchema: ListTasksInput,
|
|
jsonSchema: {
|
|
type: 'function',
|
|
function: {
|
|
name: 'list_tasks',
|
|
description: 'List child tasks of the current task (or a specified parent).',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
parent_task_id: { type: 'string', description: 'Filter by parent task ID. Omit to list children of current task.' },
|
|
},
|
|
required: [],
|
|
},
|
|
},
|
|
},
|
|
|
|
async execute(input: ListTasksInputT, _projectRoot: string, context: ToolContext): Promise<unknown> {
|
|
const { sql } = context;
|
|
const ctx = getInferenceContext();
|
|
const parentId = input.parent_task_id ?? ctx.taskId;
|
|
|
|
if (!parentId) {
|
|
return { tasks: [], note: 'No parent task context — not running inside a task.' };
|
|
}
|
|
|
|
const rows = await sql<{ id: string; state: string; input: string; output_summary: string | null }[]>`
|
|
SELECT id, state, input, output_summary
|
|
FROM tasks
|
|
WHERE parent_task_id = ${parentId}
|
|
ORDER BY created_at DESC
|
|
LIMIT 50
|
|
`;
|
|
|
|
return {
|
|
tasks: rows.map((r) => ({
|
|
id: r.id,
|
|
state: r.state,
|
|
input_preview: r.input.slice(0, 100),
|
|
output_summary: r.output_summary,
|
|
})),
|
|
};
|
|
},
|
|
};
|