(args: z.infer<typeof Args>, ctx: ToolContext)
| 38 | argsSchema = Args; |
| 39 | |
| 40 | async execute(args: z.infer<typeof Args>, ctx: ToolContext): Promise<ToolResult> { |
| 41 | if (!getSubAgentRunner()) { |
| 42 | return { |
| 43 | content: 'Orchestration needs sub-agents enabled. Turn them on with `/subagents parallel` (or `sequential`), then retry.', |
| 44 | isError: true, |
| 45 | }; |
| 46 | } |
| 47 | |
| 48 | const { Orchestrator } = await import('../../orchestration/engine.js'); |
| 49 | const designTokens = await extractDesignTokens(ctx.cwd); |
| 50 | |
| 51 | const events: string[] = []; |
| 52 | const engine = new Orchestrator({ |
| 53 | cwd: ctx.cwd, |
| 54 | maxConcurrency: args.max_concurrency ?? 3, |
| 55 | maxAttempts: 2, |
| 56 | designTokens, |
| 57 | qaHooks: buildQaHooks(ctx.cwd), |
| 58 | onEvent: (ev) => { |
| 59 | const line = formatEvent(ev); |
| 60 | if (line) { events.push(line); ctx.emit({ type: 'progress', message: line }); } |
| 61 | }, |
| 62 | }); |
| 63 | |
| 64 | // 1. Decompose. |
| 65 | ctx.emit({ type: 'progress', message: `Orchestrator: decomposing goal…` }); |
| 66 | let graph; |
| 67 | try { |
| 68 | graph = await engine.decompose(args.goal, ctx.signal); |
| 69 | } catch (e: any) { |
| 70 | return { content: `Decomposition failed: ${e.message}`, isError: true }; |
| 71 | } |
| 72 | |
| 73 | const planLines = [`# Task DAG (${graph.nodes.size} nodes)`]; |
| 74 | for (const n of graph.nodes.values()) { |
| 75 | const deps = n.dependsOn.length ? ` ← [${n.dependsOn.join(', ')}]` : ''; |
| 76 | planLines.push(` ${n.id} (${n.kind})${deps}: ${n.title} → ${n.targetFiles.join(', ')}`); |
| 77 | } |
| 78 | |
| 79 | if (args.dry_run) { |
| 80 | return { content: planLines.join('\n') + '\n\n(dry run — not executed)' }; |
| 81 | } |
| 82 | |
| 83 | // 2. Execute. |
| 84 | const report = await engine.execute(graph, ctx.signal); |
| 85 | |
| 86 | // 3. Report. |
| 87 | const out = [...planLines, '']; |
| 88 | out.push(`## Execution report`); |
| 89 | out.push(` ✓ committed: ${report.committed.length} [${report.committed.join(', ')}]`); |
| 90 | if (report.failed.length) out.push(` ✗ failed: ${report.failed.length} [${report.failed.join(', ')}]`); |
| 91 | if (report.blocked.length) out.push(` ⊘ blocked: ${report.blocked.length} [${report.blocked.join(', ')}]`); |
| 92 | out.push(` ⧉ tool calls: ${report.totalToolCalls}`); |
| 93 | out.push(` ⤓ tokens saved vs naive context: ~${report.tokensSavedEstimate.toLocaleString()}`); |
| 94 | out.push(` ⏱ duration: ${(report.durationMs / 1000).toFixed(1)}s`); |
| 95 | if (report.conflicts.length) { |
| 96 | out.push(` ⚠ conflicts:`); |
| 97 | for (const c of report.conflicts) out.push(` ${c.kind} on ${c.file} (${c.taskA} vs ${c.taskB}) → ${c.resolution}`); |
nothing calls this directly
no test coverage detected