EXECUTE: run the DAG to completion.
(graph: TaskGraph, signal?: AbortSignal)
| 144 | |
| 145 | /** EXECUTE: run the DAG to completion. */ |
| 146 | async execute(graph: TaskGraph, signal?: AbortSignal): Promise<ExecutionReport> { |
| 147 | const started = Date.now(); |
| 148 | const staging = new StagingArea(this.opts.cwd); |
| 149 | const qa = new QaNode(this.opts.qaHooks); |
| 150 | const projGraph = await this.projectGraph(); |
| 151 | |
| 152 | const slicerDeps: SlicerDeps = { |
| 153 | cwd: this.opts.cwd, |
| 154 | graph: projGraph, |
| 155 | designTokens: this.opts.designTokens, |
| 156 | // Staging-aware read: workers see committed-but-not-yet-on-disk content too. |
| 157 | read: async (abs) => { |
| 158 | try { return await fs.readFile(abs, 'utf-8'); } catch { return null; } |
| 159 | }, |
| 160 | }; |
| 161 | |
| 162 | let tokensSaved = 0; |
| 163 | let totalToolCalls = 0; |
| 164 | const conflicts: ConflictRecord[] = []; |
| 165 | |
| 166 | const hooks: SchedulerHooks = { |
| 167 | runNode: async (node, sig) => { |
| 168 | // 1. Slice the minimal context for this node. |
| 169 | const ctx = await buildTaskContext(node, slicerDeps); |
| 170 | const naive = await estimateNaiveTokens(node, slicerDeps); |
| 171 | tokensSaved += Math.max(0, naive - ctx.estimatedTokens); |
| 172 | |
| 173 | // 2. Dispatch the worker (existing SubAgentRunner) with the role. |
| 174 | const runner = getSubAgentRunner(); |
| 175 | if (!runner) return { taskId: node.id, ok: false, fileEdits: [], summary: '', toolCallsRun: 0, error: 'no runner' }; |
| 176 | |
| 177 | const prompt = renderContextPrompt(ctx); |
| 178 | const res = await runner(prompt, { |
| 179 | maxIterations: 12, |
| 180 | signal: sig, |
| 181 | sessionId: `worker-${node.id}-${Date.now()}`, |
| 182 | role: node.role, |
| 183 | }); |
| 184 | totalToolCalls += res.toolCallsRun; |
| 185 | |
| 186 | // 3. Parse worker output into staged file edits. |
| 187 | const fileEdits = await extractFileEdits(res.finalText, node, this.opts.cwd); |
| 188 | return { |
| 189 | taskId: node.id, |
| 190 | ok: res.ok && fileEdits.length > 0, |
| 191 | fileEdits, |
| 192 | summary: res.finalText.slice(0, 500), |
| 193 | modelUsed: res.modelUsed, |
| 194 | toolCallsRun: res.toolCallsRun, |
| 195 | error: res.ok ? (fileEdits.length === 0 ? 'worker produced no file edits' : undefined) : res.error, |
| 196 | }; |
| 197 | }, |
| 198 | |
| 199 | review: (node, result, sig) => qa.review(node, result, sig), |
| 200 | |
| 201 | dryRunMerge: async (node, result) => { |
| 202 | const c = await staging.dryRunMerge(node, result); |
| 203 | conflicts.push(...c); |
no test coverage detected