@zhixuan92/multi-model-agent-core 3.1.7 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/dist/auto-commit.d.ts +8 -1
  2. package/dist/auto-commit.d.ts.map +1 -1
  3. package/dist/auto-commit.js +6 -3
  4. package/dist/auto-commit.js.map +1 -1
  5. package/dist/batch-cache.d.ts +1 -1
  6. package/dist/batch-cache.d.ts.map +1 -1
  7. package/dist/batch-cache.js +3 -5
  8. package/dist/batch-cache.js.map +1 -1
  9. package/dist/config/schema.d.ts +13 -13
  10. package/dist/delegate-with-escalation.d.ts +2 -1
  11. package/dist/delegate-with-escalation.d.ts.map +1 -1
  12. package/dist/delegate-with-escalation.js.map +1 -1
  13. package/dist/diagnostics/request-spill.d.ts +16 -0
  14. package/dist/diagnostics/request-spill.d.ts.map +1 -0
  15. package/dist/diagnostics/request-spill.js +23 -0
  16. package/dist/diagnostics/request-spill.js.map +1 -0
  17. package/dist/diagnostics/verbose-line.d.ts +12 -0
  18. package/dist/diagnostics/verbose-line.d.ts.map +1 -0
  19. package/dist/diagnostics/verbose-line.js +80 -0
  20. package/dist/diagnostics/verbose-line.js.map +1 -0
  21. package/dist/executors/audit.js +1 -1
  22. package/dist/executors/audit.js.map +1 -1
  23. package/dist/executors/debug.js +2 -2
  24. package/dist/executors/debug.js.map +1 -1
  25. package/dist/executors/delegate.d.ts +2 -2
  26. package/dist/executors/delegate.d.ts.map +1 -1
  27. package/dist/executors/delegate.js +7 -3
  28. package/dist/executors/delegate.js.map +1 -1
  29. package/dist/executors/execute-plan.d.ts.map +1 -1
  30. package/dist/executors/execute-plan.js +10 -3
  31. package/dist/executors/execute-plan.js.map +1 -1
  32. package/dist/executors/execution-context.d.ts +3 -0
  33. package/dist/executors/execution-context.d.ts.map +1 -0
  34. package/dist/executors/execution-context.js +20 -0
  35. package/dist/executors/execution-context.js.map +1 -0
  36. package/dist/executors/retry.d.ts +1 -1
  37. package/dist/executors/retry.d.ts.map +1 -1
  38. package/dist/executors/retry.js +5 -2
  39. package/dist/executors/retry.js.map +1 -1
  40. package/dist/executors/review.js +1 -1
  41. package/dist/executors/review.js.map +1 -1
  42. package/dist/executors/shared-compute.d.ts +2 -1
  43. package/dist/executors/shared-compute.d.ts.map +1 -1
  44. package/dist/executors/shared-compute.js.map +1 -1
  45. package/dist/executors/types.d.ts +29 -26
  46. package/dist/executors/types.d.ts.map +1 -1
  47. package/dist/executors/verify.js +1 -1
  48. package/dist/executors/verify.js.map +1 -1
  49. package/dist/heartbeat.d.ts +8 -1
  50. package/dist/heartbeat.d.ts.map +1 -1
  51. package/dist/heartbeat.js +28 -1
  52. package/dist/heartbeat.js.map +1 -1
  53. package/dist/index.d.ts +7 -3
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +1 -1
  56. package/dist/index.js.map +1 -1
  57. package/dist/intake/compilers/delegate.d.ts +3 -1
  58. package/dist/intake/compilers/delegate.d.ts.map +1 -1
  59. package/dist/intake/compilers/delegate.js +23 -12
  60. package/dist/intake/compilers/delegate.js.map +1 -1
  61. package/dist/intake/compilers/execute-plan.d.ts +6 -1
  62. package/dist/intake/compilers/execute-plan.d.ts.map +1 -1
  63. package/dist/intake/compilers/execute-plan.js +8 -1
  64. package/dist/intake/compilers/execute-plan.js.map +1 -1
  65. package/dist/intake/resolve.js +1 -1
  66. package/dist/intake/resolve.js.map +1 -1
  67. package/dist/intake/types.d.ts +10 -0
  68. package/dist/intake/types.d.ts.map +1 -1
  69. package/dist/provider.d.ts.map +1 -1
  70. package/dist/provider.js.map +1 -1
  71. package/dist/readiness/readiness.d.ts +2 -1
  72. package/dist/readiness/readiness.d.ts.map +1 -1
  73. package/dist/readiness/readiness.js.map +1 -1
  74. package/dist/reporting/structured-report.d.ts +19 -0
  75. package/dist/reporting/structured-report.d.ts.map +1 -1
  76. package/dist/reporting/structured-report.js +50 -1
  77. package/dist/reporting/structured-report.js.map +1 -1
  78. package/dist/review/diff-review.d.ts +29 -0
  79. package/dist/review/diff-review.d.ts.map +1 -0
  80. package/dist/review/diff-review.js +53 -0
  81. package/dist/review/diff-review.js.map +1 -0
  82. package/dist/review/evidence.d.ts +15 -0
  83. package/dist/review/evidence.d.ts.map +1 -0
  84. package/dist/review/evidence.js +26 -0
  85. package/dist/review/evidence.js.map +1 -0
  86. package/dist/review/quality-reviewer.d.ts +1 -1
  87. package/dist/review/quality-reviewer.d.ts.map +1 -1
  88. package/dist/review/quality-reviewer.js +5 -3
  89. package/dist/review/quality-reviewer.js.map +1 -1
  90. package/dist/review/spec-reviewer.d.ts +1 -1
  91. package/dist/review/spec-reviewer.d.ts.map +1 -1
  92. package/dist/review/spec-reviewer.js +3 -2
  93. package/dist/review/spec-reviewer.js.map +1 -1
  94. package/dist/routing/model-profiles.d.ts +1 -1
  95. package/dist/routing/types.d.ts +15 -0
  96. package/dist/routing/types.d.ts.map +1 -0
  97. package/dist/routing/types.js +2 -0
  98. package/dist/routing/types.js.map +1 -0
  99. package/dist/run-tasks/commit-stage.d.ts +16 -0
  100. package/dist/run-tasks/commit-stage.d.ts.map +1 -0
  101. package/dist/run-tasks/commit-stage.js +43 -0
  102. package/dist/run-tasks/commit-stage.js.map +1 -0
  103. package/dist/run-tasks/execute-task.d.ts +20 -0
  104. package/dist/run-tasks/execute-task.d.ts.map +1 -0
  105. package/dist/run-tasks/execute-task.js +29 -0
  106. package/dist/run-tasks/execute-task.js.map +1 -0
  107. package/dist/run-tasks/fallback-report.d.ts +5 -0
  108. package/dist/run-tasks/fallback-report.d.ts.map +1 -0
  109. package/dist/run-tasks/fallback-report.js +33 -0
  110. package/dist/run-tasks/fallback-report.js.map +1 -0
  111. package/dist/{run-tasks.d.ts → run-tasks/index.d.ts} +8 -5
  112. package/dist/run-tasks/index.d.ts.map +1 -0
  113. package/dist/run-tasks/index.js +118 -0
  114. package/dist/run-tasks/index.js.map +1 -0
  115. package/dist/run-tasks/metadata-repair.d.ts +15 -0
  116. package/dist/run-tasks/metadata-repair.d.ts.map +1 -0
  117. package/dist/run-tasks/metadata-repair.js +30 -0
  118. package/dist/run-tasks/metadata-repair.js.map +1 -0
  119. package/dist/run-tasks/plan-extraction.d.ts +2 -0
  120. package/dist/run-tasks/plan-extraction.d.ts.map +1 -0
  121. package/dist/run-tasks/plan-extraction.js +44 -0
  122. package/dist/run-tasks/plan-extraction.js.map +1 -0
  123. package/dist/run-tasks/reviewed-lifecycle.d.ts +15 -0
  124. package/dist/run-tasks/reviewed-lifecycle.d.ts.map +1 -0
  125. package/dist/run-tasks/reviewed-lifecycle.js +839 -0
  126. package/dist/run-tasks/reviewed-lifecycle.js.map +1 -0
  127. package/dist/run-tasks/verify-stage.d.ts +25 -0
  128. package/dist/run-tasks/verify-stage.d.ts.map +1 -0
  129. package/dist/run-tasks/verify-stage.js +168 -0
  130. package/dist/run-tasks/verify-stage.js.map +1 -0
  131. package/dist/run-tasks/worker-status.d.ts +3 -0
  132. package/dist/run-tasks/worker-status.d.ts.map +1 -0
  133. package/dist/run-tasks/worker-status.js +13 -0
  134. package/dist/run-tasks/worker-status.js.map +1 -0
  135. package/dist/runners/base/result-builders.d.ts +81 -0
  136. package/dist/runners/base/result-builders.d.ts.map +1 -0
  137. package/dist/runners/base/result-builders.js +103 -0
  138. package/dist/runners/base/result-builders.js.map +1 -0
  139. package/dist/runners/base/types.d.ts +53 -0
  140. package/dist/runners/base/types.d.ts.map +1 -0
  141. package/dist/runners/base/types.js +2 -0
  142. package/dist/runners/base/types.js.map +1 -0
  143. package/dist/runners/claude-runner.d.ts +2 -1
  144. package/dist/runners/claude-runner.d.ts.map +1 -1
  145. package/dist/runners/claude-runner.js +44 -109
  146. package/dist/runners/claude-runner.js.map +1 -1
  147. package/dist/runners/codex-runner.d.ts +2 -1
  148. package/dist/runners/codex-runner.d.ts.map +1 -1
  149. package/dist/runners/codex-runner.js +45 -110
  150. package/dist/runners/codex-runner.js.map +1 -1
  151. package/dist/runners/error-classification.d.ts +1 -1
  152. package/dist/runners/error-classification.d.ts.map +1 -1
  153. package/dist/runners/openai-runner.d.ts +2 -1
  154. package/dist/runners/openai-runner.d.ts.map +1 -1
  155. package/dist/runners/openai-runner.js +34 -84
  156. package/dist/runners/openai-runner.js.map +1 -1
  157. package/dist/runners/prevention.d.ts.map +1 -1
  158. package/dist/runners/prevention.js +18 -0
  159. package/dist/runners/prevention.js.map +1 -1
  160. package/dist/runners/types.d.ts +126 -0
  161. package/dist/runners/types.d.ts.map +1 -0
  162. package/dist/runners/types.js +2 -0
  163. package/dist/runners/types.js.map +1 -0
  164. package/dist/tool-schemas/audit.d.ts +2 -2
  165. package/dist/tool-schemas/delegate.d.ts +9 -0
  166. package/dist/tool-schemas/delegate.d.ts.map +1 -1
  167. package/dist/tool-schemas/delegate.js +4 -0
  168. package/dist/tool-schemas/delegate.js.map +1 -1
  169. package/dist/tool-schemas/execute-plan.d.ts +13 -2
  170. package/dist/tool-schemas/execute-plan.d.ts.map +1 -1
  171. package/dist/tool-schemas/execute-plan.js +22 -4
  172. package/dist/tool-schemas/execute-plan.js.map +1 -1
  173. package/dist/tool-schemas/review.d.ts +1 -1
  174. package/dist/types.d.ts +36 -327
  175. package/dist/types.d.ts.map +1 -1
  176. package/dist/types.js +8 -37
  177. package/dist/types.js.map +1 -1
  178. package/package.json +35 -3
  179. package/dist/run-tasks.d.ts.map +0 -1
  180. package/dist/run-tasks.js +0 -687
  181. package/dist/run-tasks.js.map +0 -1
package/dist/run-tasks.js DELETED
@@ -1,687 +0,0 @@
1
- import { computeCostUSD, computeSavedCostUSD } from './types.js';
2
- import { createProvider } from './provider.js';
3
- import { resolveAgent } from './routing/resolve-agent.js';
4
- import { delegateWithEscalation } from './delegate-with-escalation.js';
5
- import { HeartbeatTimer } from './heartbeat.js';
6
- import { expandContextBlocks } from './context/expand-context-blocks.js';
7
- import { inferEffort } from './effort-inference.js';
8
- import { evaluateReadiness } from './readiness/readiness.js';
9
- import { runSpecReview } from './review/spec-reviewer.js';
10
- import { runQualityReview } from './review/quality-reviewer.js';
11
- import { aggregateResult } from './review/aggregate-result.js';
12
- import { parseStructuredReport } from './reporting/structured-report.js';
13
- import { autoCommitFiles } from './auto-commit.js';
14
- import { partitionFilePaths, checkOutputTargets } from './file-artifact-check.js';
15
- import fs from 'fs/promises';
16
- const PLAN_CONTEXT_MAX_CHARS = 10_000;
17
- export async function extractPlanSection(planFilePaths, taskDescriptor, cwd) {
18
- const basePath = cwd ?? process.cwd();
19
- for (const filePath of planFilePaths) {
20
- try {
21
- const resolved = filePath.startsWith('/') ? filePath : `${basePath}/${filePath}`;
22
- const content = await fs.readFile(resolved, 'utf-8');
23
- const lines = content.split('\n');
24
- let startIndex = -1;
25
- let headingLevel = 0;
26
- for (let i = 0; i < lines.length; i++) {
27
- const match = lines[i].match(/^(#{1,6})\s+(.*)/);
28
- if (match && match[2].trim() === taskDescriptor.trim()) {
29
- startIndex = i;
30
- headingLevel = match[1].length;
31
- break;
32
- }
33
- }
34
- if (startIndex === -1)
35
- continue;
36
- let endIndex = lines.length;
37
- for (let i = startIndex + 1; i < lines.length; i++) {
38
- const match = lines[i].match(/^(#{1,6})\s/);
39
- if (match && match[1].length <= headingLevel) {
40
- endIndex = i;
41
- break;
42
- }
43
- }
44
- let section = lines.slice(startIndex, endIndex).join('\n');
45
- if (section.length > PLAN_CONTEXT_MAX_CHARS) {
46
- section = section.slice(0, PLAN_CONTEXT_MAX_CHARS) + '\n[truncated at 10KB]';
47
- }
48
- return section;
49
- }
50
- catch {
51
- if (process.env.MULTI_MODEL_DEBUG === '1') {
52
- console.error(`[multi-model-agent] plan file not readable: ${filePath}`);
53
- }
54
- }
55
- }
56
- return undefined;
57
- }
58
- function errorResult(error) {
59
- return {
60
- output: `Sub-agent error: ${error}`,
61
- status: 'error',
62
- usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUSD: null },
63
- turns: 0,
64
- filesRead: [],
65
- filesWritten: [],
66
- toolCalls: [],
67
- outputIsDiagnostic: true,
68
- escalationLog: [],
69
- error,
70
- };
71
- }
72
- function withDoneCondition(task) {
73
- if (!task.done)
74
- return task;
75
- return { ...task, prompt: `${task.prompt}\n\n## Success Criteria\n${task.done}` };
76
- }
77
- async function executeTask(resolved, onProgress) {
78
- try {
79
- return await delegateWithEscalation(withDoneCondition(resolved.task), [resolved.resolved.provider], { explicitlyPinned: true, onProgress });
80
- }
81
- catch (err) {
82
- return errorResult(err instanceof Error ? err.message : String(err));
83
- }
84
- }
85
- function extractWorkerStatus(report) {
86
- if (!report || !report.summary)
87
- return 'done';
88
- const s = report.summary.toLowerCase();
89
- if (s.includes('needs_context'))
90
- return 'needs_context';
91
- if (s.includes('blocked'))
92
- return 'blocked';
93
- if (s.includes('done_with_concerns') || s.includes('concerns'))
94
- return 'done_with_concerns';
95
- return 'done';
96
- }
97
- async function readImplementerFileContents(filesWritten, cwd) {
98
- const contents = {};
99
- const basePath = cwd ?? process.cwd();
100
- for (const filePath of filesWritten) {
101
- try {
102
- const resolved = filePath.startsWith('/') ? filePath : `${basePath}/${filePath}`;
103
- const content = await fs.readFile(resolved, 'utf-8');
104
- contents[filePath] = content.length > 50_000
105
- ? content.slice(0, 50_000) + '\n[truncated at 50KB]'
106
- : content;
107
- }
108
- catch {
109
- contents[filePath] = '[file not readable]';
110
- }
111
- }
112
- return contents;
113
- }
114
- function buildFallbackImplReport(result) {
115
- const parsed = parseStructuredReport(result.output);
116
- if (parsed.summary) {
117
- return parsed;
118
- }
119
- return {
120
- summary: result.output.substring(0, 200),
121
- filesChanged: result.filesWritten.map(f => ({ path: f, summary: 'updated' })),
122
- validationsRun: [],
123
- deviationsFromBrief: [],
124
- unresolved: [],
125
- };
126
- }
127
- async function executeReviewedLifecycle(task, resolved, config, taskIndex, onProgress, heartbeatWiring, diagnostics) {
128
- const reviewPolicy = task.reviewPolicy ?? 'full';
129
- const otherSlot = resolved.slot === 'standard' ? 'complex' : 'standard';
130
- // Partition filePaths into output targets before the worker runs.
131
- // Output targets are paths that do not yet exist on disk.
132
- const { outputTargets } = partitionFilePaths(task.filePaths, task.cwd ?? process.cwd());
133
- let escalationProvider;
134
- try {
135
- escalationProvider = createProvider(otherSlot, config);
136
- }
137
- catch {
138
- // Other slot not configured — auto-escalation not available
139
- }
140
- const stageCount = reviewPolicy === 'off' ? 1 :
141
- reviewPolicy === 'spec_only' ? 3 :
142
- 5;
143
- const verbose = diagnostics?.verbose ?? false;
144
- let lastStageSeen;
145
- const verboseStreamRaw = verbose
146
- ? (diagnostics?.verboseStream ?? ((line) => { process.stderr.write(line + '\n'); }))
147
- : undefined;
148
- const verboseBatchIdEarly = heartbeatWiring?.batchId;
149
- const shortBatchEarly = verboseBatchIdEarly ? verboseBatchIdEarly.slice(0, 8) : '????????';
150
- // Start the heartbeat whenever there's a downstream consumer:
151
- // - onProgress (external progress callback from the runTasks caller)
152
- // - verbose (stderr stream needs the heartbeat's tool_call / turn_complete relay)
153
- // - recordHeartbeat (server needs heartbeat ticks to update BatchRegistry)
154
- // - logger (post-mortem JSONL logging needs the events too)
155
- // Otherwise there is no point creating a timer.
156
- const needHeartbeat = onProgress !== undefined ||
157
- verbose ||
158
- heartbeatWiring?.recordHeartbeat !== undefined ||
159
- diagnostics?.logger !== undefined;
160
- // Synthesize an onProgress sink when the caller didn't pass one — the
161
- // heartbeat needs a place to emit heartbeat events so the stage-change
162
- // detector below fires. Discards events if there is no external consumer.
163
- const synthOnProgress = onProgress ?? (() => { });
164
- const heartbeat = needHeartbeat
165
- ? new HeartbeatTimer((event) => {
166
- if (verboseStreamRaw && event.kind === 'heartbeat') {
167
- // Emit on every heartbeat tick so the operator can confirm
168
- // the timer is actually firing. Stage-change lines are richer
169
- // but fire only on transitions; plain ticks let you see
170
- // per-5s progress inside a long-running stage.
171
- if (event.stage !== lastStageSeen) {
172
- if (lastStageSeen !== undefined) {
173
- verboseStreamRaw(`[mmagent verbose] batch=${shortBatchEarly} task=${taskIndex} stage ${lastStageSeen} → ${event.stage}`);
174
- }
175
- lastStageSeen = event.stage;
176
- }
177
- const costStr = event.costUSD !== null ? ` cost=$${event.costUSD.toFixed(4)}` : '';
178
- const roundStr = event.reviewRound !== undefined && event.maxReviewRounds !== undefined
179
- ? ` round=${event.reviewRound}/${event.maxReviewRounds}`
180
- : '';
181
- const sinceLastMs = Date.now() - prevEventAtMs;
182
- verboseStreamRaw(`[mmagent verbose] batch=${shortBatchEarly} task=${taskIndex} heartbeat ${event.elapsed} stage=${event.stage}${roundStr} tools=${event.progress.toolCalls} read=${event.progress.filesRead} wrote=${event.progress.filesWritten} text=${textEmissionChars}c${costStr} idle=${sinceLastMs}ms`);
183
- }
184
- synthOnProgress(taskIndex, event);
185
- }, {
186
- provider: resolved.provider.config.model,
187
- parentModel: task.parentModel,
188
- ...(heartbeatWiring?.batchId !== undefined && { batchId: heartbeatWiring.batchId }),
189
- ...(heartbeatWiring?.recordHeartbeat !== undefined && { recordHeartbeat: heartbeatWiring.recordHeartbeat }),
190
- })
191
- : undefined;
192
- heartbeat?.start(stageCount);
193
- if (verboseStreamRaw) {
194
- verboseStreamRaw(`[mmagent verbose] batch=${shortBatchEarly} task=${taskIndex} heartbeat ` +
195
- (heartbeat ? `started (stageCount=${stageCount}, 5s tick)` : 'DISABLED (no consumer)'));
196
- }
197
- const implModel = resolved.provider.config.model;
198
- const progressCounters = { filesRead: 0, filesWritten: 0, toolCalls: 0 };
199
- const verboseLogger = verbose && diagnostics?.logger ? diagnostics.logger : undefined;
200
- const verboseBatchId = verboseBatchIdEarly;
201
- const verboseStream = verboseStreamRaw;
202
- const shortBatch = shortBatchEarly;
203
- if (verboseStream) {
204
- verboseStream(`[mmagent verbose] batch=${shortBatch} task=${taskIndex} start worker=${resolved.provider.config.model}`);
205
- }
206
- let prevEventAtMs = verbose ? Date.now() : 0;
207
- // Wrap whenever we have ANY consumer for InternalRunnerEvent (heartbeat,
208
- // verbose stream, or verbose logger). Previously this only wrapped when
209
- // the caller passed onProgress, so --verbose + HTTP handlers (which don't
210
- // pass onProgress) silently dropped every tool_call / turn_complete event.
211
- let textEmissionChars = 0;
212
- const wrappedOnProgress = needHeartbeat
213
- ? (event) => {
214
- if (event.kind === 'turn_start') {
215
- if (verbose)
216
- prevEventAtMs = Date.now();
217
- if (verboseStream) {
218
- verboseStream(`[mmagent verbose] batch=${shortBatch} task=${taskIndex} turn_start turn=${event.turn} provider=${event.provider}`);
219
- }
220
- }
221
- if (event.kind === 'text_emission') {
222
- textEmissionChars += event.chars;
223
- if (verboseStream && event.chars > 0) {
224
- const preview = event.preview.length > 60
225
- ? event.preview.slice(0, 57) + '...'
226
- : event.preview;
227
- verboseStream(`[mmagent verbose] batch=${shortBatch} task=${taskIndex} text +${event.chars}c (total ${textEmissionChars}) preview="${preview.replace(/\n/g, '\\n')}"`);
228
- }
229
- }
230
- if (event.kind === 'tool_call') {
231
- progressCounters.toolCalls++;
232
- const name = event.toolSummary.split('(')[0];
233
- if (name === 'readFile' || name === 'grep' || name === 'glob' || name === 'listFiles') {
234
- progressCounters.filesRead++;
235
- }
236
- else if (name === 'writeFile' || name === 'editFile') {
237
- progressCounters.filesWritten++;
238
- }
239
- heartbeat?.updateProgress(progressCounters.filesRead, progressCounters.filesWritten, progressCounters.toolCalls);
240
- const now = verbose ? Date.now() : 0;
241
- const sincePrevMs = verbose ? now - prevEventAtMs : 0;
242
- if (verbose)
243
- prevEventAtMs = now;
244
- if (verboseLogger && verboseBatchId) {
245
- verboseLogger.toolCall({
246
- batchId: verboseBatchId,
247
- taskIndex,
248
- tool: event.toolSummary,
249
- durationMs: sincePrevMs,
250
- });
251
- }
252
- if (verboseStream) {
253
- verboseStream(`[mmagent verbose] batch=${shortBatch} task=${taskIndex} tool=${event.toolSummary} +${sincePrevMs}ms`);
254
- }
255
- }
256
- if (event.kind === 'turn_complete') {
257
- const costUSD = computeCostUSD(event.cumulativeInputTokens, event.cumulativeOutputTokens, resolved.provider.config);
258
- const savedCostUSD = computeSavedCostUSD(costUSD, event.cumulativeInputTokens, event.cumulativeOutputTokens, task.parentModel);
259
- heartbeat?.updateCost(costUSD, savedCostUSD);
260
- const nowTurn = verbose ? Date.now() : 0;
261
- const turnDurMs = verbose ? nowTurn - prevEventAtMs : 0;
262
- if (verbose)
263
- prevEventAtMs = nowTurn;
264
- if (verboseLogger && verboseBatchId) {
265
- verboseLogger.llmTurn({
266
- batchId: verboseBatchId,
267
- taskIndex,
268
- turnIndex: progressCounters.toolCalls,
269
- provider: resolved.provider.config.model,
270
- inputTokens: event.cumulativeInputTokens,
271
- outputTokens: event.cumulativeOutputTokens,
272
- costUSD,
273
- });
274
- }
275
- if (verboseStream) {
276
- const costStr = costUSD !== null ? ` $${costUSD.toFixed(4)}` : '';
277
- verboseStream(`[mmagent verbose] batch=${shortBatch} task=${taskIndex} ` +
278
- `turn in=${event.cumulativeInputTokens} out=${event.cumulativeOutputTokens}${costStr} ` +
279
- `+${turnDurMs}ms (${resolved.provider.config.model})`);
280
- }
281
- }
282
- }
283
- : undefined;
284
- // Track auto-commit state across all rounds
285
- let commitSha;
286
- let commitError;
287
- try {
288
- const implResult = await delegateWithEscalation(withDoneCondition(task), [resolved.provider], { explicitlyPinned: false, escalateToProvider: escalationProvider, onProgress: wrappedOnProgress });
289
- const implReport = implResult.status === 'ok' ? parseStructuredReport(implResult.output) : undefined;
290
- const workerStatus = extractWorkerStatus(implReport);
291
- // Auto-commit: commit the worker's file changes
292
- if (task.autoCommit && implResult.status === 'ok' && implResult.filesWritten.length > 0) {
293
- const commitResult = autoCommitFiles(implResult.filesWritten, implReport?.summary ?? undefined, task.cwd ?? process.cwd());
294
- commitSha = commitResult.sha;
295
- commitError = commitResult.error;
296
- }
297
- const filePathsInteracted = task.filePaths && task.filePaths.length > 0
298
- ? [...(implResult.filesRead ?? []), ...implResult.filesWritten].some(f => task.filePaths.some(fp => f === fp || f.endsWith('/' + fp) || f.endsWith(fp)))
299
- : true;
300
- const filePathsSkipped = !filePathsInteracted;
301
- if (implResult.filesWritten.length === 0) {
302
- heartbeat?.updateStageCount(1);
303
- const effectiveImplReport = implReport ?? buildFallbackImplReport(implResult);
304
- const earlyFileArtifactsMissing = implResult.status === 'ok' ? checkOutputTargets(outputTargets) : undefined;
305
- const earlyStatus = implResult.status === 'ok' && earlyFileArtifactsMissing
306
- ? 'incomplete'
307
- : implResult.status;
308
- return {
309
- ...implResult,
310
- status: earlyStatus,
311
- workerStatus,
312
- specReviewStatus: 'not_applicable',
313
- qualityReviewStatus: 'not_applicable',
314
- specReviewReason: 'task produced no file artifacts to review',
315
- qualityReviewReason: 'task produced no file artifacts to review',
316
- implementationReport: effectiveImplReport,
317
- structuredReport: {
318
- summary: '[No artifacts] task produced no file artifacts to review',
319
- filesChanged: effectiveImplReport.filesChanged,
320
- validationsRun: effectiveImplReport.validationsRun,
321
- deviationsFromBrief: effectiveImplReport.deviationsFromBrief,
322
- unresolved: effectiveImplReport.unresolved,
323
- },
324
- filePathsSkipped,
325
- agents: {
326
- implementer: resolved.slot,
327
- specReviewer: 'not_applicable',
328
- qualityReviewer: 'not_applicable',
329
- },
330
- models: {
331
- implementer: implModel,
332
- specReviewer: null,
333
- qualityReviewer: null,
334
- },
335
- fileArtifactsMissing: earlyFileArtifactsMissing,
336
- commitSha,
337
- commitError,
338
- };
339
- }
340
- if (workerStatus === 'needs_context' || workerStatus === 'blocked') {
341
- return {
342
- ...implResult,
343
- workerStatus,
344
- specReviewStatus: 'skipped',
345
- qualityReviewStatus: 'skipped',
346
- specReviewReason: 'skipped: worker reported ' + workerStatus,
347
- qualityReviewReason: 'skipped: worker reported ' + workerStatus,
348
- agents: {
349
- implementer: resolved.slot,
350
- specReviewer: 'skipped',
351
- qualityReviewer: 'skipped',
352
- },
353
- models: {
354
- implementer: implModel,
355
- specReviewer: null,
356
- qualityReviewer: null,
357
- },
358
- fileArtifactsMissing: implResult.status === 'ok' ? checkOutputTargets(outputTargets) : undefined,
359
- commitSha,
360
- commitError,
361
- };
362
- }
363
- if (reviewPolicy === 'off') {
364
- return {
365
- ...implResult,
366
- workerStatus,
367
- specReviewStatus: 'skipped',
368
- qualityReviewStatus: 'skipped',
369
- specReviewReason: 'skipped: reviewPolicy is off',
370
- qualityReviewReason: 'skipped: reviewPolicy is off',
371
- agents: {
372
- implementer: resolved.slot,
373
- specReviewer: 'skipped',
374
- qualityReviewer: 'skipped',
375
- },
376
- models: {
377
- implementer: implModel,
378
- specReviewer: null,
379
- qualityReviewer: null,
380
- },
381
- implementationReport: implReport,
382
- fileArtifactsMissing: implResult.status === 'ok' ? checkOutputTargets(outputTargets) : undefined,
383
- commitSha,
384
- commitError,
385
- };
386
- }
387
- let otherProvider;
388
- try {
389
- otherProvider = createProvider(otherSlot, config);
390
- }
391
- catch {
392
- return {
393
- ...implResult,
394
- workerStatus,
395
- specReviewStatus: 'skipped',
396
- qualityReviewStatus: 'skipped',
397
- specReviewReason: 'skipped: no review agent configured',
398
- qualityReviewReason: 'skipped: no review agent configured',
399
- agents: {
400
- implementer: resolved.slot,
401
- specReviewer: 'skipped',
402
- qualityReviewer: 'skipped',
403
- },
404
- models: {
405
- implementer: implModel,
406
- specReviewer: null,
407
- qualityReviewer: null,
408
- },
409
- fileArtifactsMissing: implResult.status === 'ok' ? checkOutputTargets(outputTargets) : undefined,
410
- commitSha,
411
- commitError,
412
- };
413
- }
414
- const reviewModel = otherProvider.config.model;
415
- const packet = {
416
- prompt: task.prompt,
417
- scope: task.filePaths ?? [],
418
- doneCondition: task.done ?? 'tsc passes',
419
- };
420
- let fileContents = await readImplementerFileContents(implResult.filesWritten, task.cwd);
421
- const effectiveImplReport = implReport ?? buildFallbackImplReport(implResult);
422
- heartbeat?.transition({
423
- stage: 'spec_review', stageIndex: 2,
424
- reviewRound: 1, maxReviewRounds: task.maxReviewRounds ?? 5,
425
- });
426
- let specResult = await runSpecReview(otherProvider, packet, effectiveImplReport, fileContents, implResult.toolCalls, task.planContext);
427
- let finalImplResult = implResult;
428
- let finalImplReport = effectiveImplReport;
429
- let specStatus = specResult.status;
430
- let specReport = specResult.report;
431
- if (specStatus === 'changes_required') {
432
- let prevSpecFindings = [];
433
- let round = 0;
434
- while (true) {
435
- round++;
436
- heartbeat?.transition({
437
- stage: 'spec_rework', stageIndex: 3,
438
- reviewRound: round, maxReviewRounds: task.maxReviewRounds ?? 5,
439
- });
440
- const feedback = specResult.findings.length > 0
441
- ? `\n\n## Spec Review Feedback (round ${round}):\n${specResult.findings.map(f => `- ${f}`).join('\n')}`
442
- : '';
443
- const reworkPrompt = `${task.prompt}${feedback}`;
444
- const reworkTask = withDoneCondition({ ...task, prompt: reworkPrompt });
445
- const reworkResult = await delegateWithEscalation(reworkTask, [resolved.provider], { explicitlyPinned: true, onProgress: wrappedOnProgress });
446
- // Auto-commit rework changes
447
- if (task.autoCommit && reworkResult.status === 'ok' && reworkResult.filesWritten.length > 0) {
448
- const reworkReport = parseStructuredReport(reworkResult.output);
449
- const reworkCommit = autoCommitFiles(reworkResult.filesWritten, reworkReport.summary ?? undefined, task.cwd ?? process.cwd());
450
- if (reworkCommit.sha)
451
- commitSha = reworkCommit.sha;
452
- if (reworkCommit.error)
453
- commitError = reworkCommit.error;
454
- }
455
- finalImplResult = reworkResult;
456
- const reworkReport = parseStructuredReport(reworkResult.output);
457
- finalImplReport = reworkReport.summary ? reworkReport : buildFallbackImplReport(reworkResult);
458
- const reworkContents = await readImplementerFileContents(reworkResult.filesWritten, task.cwd);
459
- fileContents = reworkContents;
460
- heartbeat?.transition({
461
- stage: 'spec_review', stageIndex: 2,
462
- reviewRound: round + 1, maxReviewRounds: task.maxReviewRounds ?? 5,
463
- });
464
- specResult = await runSpecReview(otherProvider, packet, finalImplReport, reworkContents, reworkResult.toolCalls, task.planContext);
465
- specStatus = specResult.status;
466
- specReport = specResult.report;
467
- if (specStatus === 'approved')
468
- break;
469
- const currentFindings = [...specResult.findings].sort().join('\0');
470
- const prevFindings = prevSpecFindings.sort().join('\0');
471
- if (currentFindings === prevFindings && currentFindings !== '')
472
- break;
473
- prevSpecFindings = specResult.findings;
474
- if (round >= (task.maxReviewRounds ?? 5))
475
- break;
476
- }
477
- }
478
- let qualityResult = { status: 'skipped', report: undefined, findings: [] };
479
- if (reviewPolicy === 'full') {
480
- heartbeat?.transition({
481
- stage: 'quality_review', stageIndex: 4,
482
- reviewRound: 1, maxReviewRounds: task.maxReviewRounds ?? 5,
483
- });
484
- qualityResult = await runQualityReview(otherProvider, packet, specReport ?? finalImplReport, fileContents, finalImplResult.toolCalls, finalImplResult.filesWritten);
485
- if (qualityResult.status === 'changes_required') {
486
- let prevQualityFindings = [];
487
- let round = 0;
488
- while (true) {
489
- round++;
490
- heartbeat?.transition({
491
- stage: 'quality_rework', stageIndex: 5,
492
- reviewRound: round, maxReviewRounds: task.maxReviewRounds ?? 5,
493
- });
494
- const feedback = qualityResult.findings.length > 0
495
- ? `\n\n## Quality Review Feedback (round ${round}):\n${qualityResult.findings.map(f => `- ${f}`).join('\n')}`
496
- : '';
497
- const reworkPrompt = `${task.prompt}${feedback}`;
498
- const reworkTask = withDoneCondition({ ...task, prompt: reworkPrompt });
499
- const reworkResult = await delegateWithEscalation(reworkTask, [resolved.provider], { explicitlyPinned: true, onProgress: wrappedOnProgress });
500
- // Auto-commit rework changes
501
- if (task.autoCommit && reworkResult.status === 'ok' && reworkResult.filesWritten.length > 0) {
502
- const reworkReport = parseStructuredReport(reworkResult.output);
503
- const reworkCommit = autoCommitFiles(reworkResult.filesWritten, reworkReport.summary ?? undefined, task.cwd ?? process.cwd());
504
- if (reworkCommit.sha)
505
- commitSha = reworkCommit.sha;
506
- if (reworkCommit.error)
507
- commitError = reworkCommit.error;
508
- }
509
- finalImplResult = reworkResult;
510
- const reworkReport = parseStructuredReport(reworkResult.output);
511
- finalImplReport = reworkReport.summary ? reworkReport : buildFallbackImplReport(reworkResult);
512
- const reworkContents = await readImplementerFileContents(reworkResult.filesWritten, task.cwd);
513
- heartbeat?.transition({
514
- stage: 'quality_review', stageIndex: 4,
515
- reviewRound: round + 1, maxReviewRounds: task.maxReviewRounds ?? 5,
516
- });
517
- qualityResult = await runQualityReview(otherProvider, packet, finalImplReport, reworkContents, reworkResult.toolCalls, reworkResult.filesWritten);
518
- if (qualityResult.status === 'approved')
519
- break;
520
- const currentFindings = [...qualityResult.findings].sort().join('\0');
521
- const prevFindings = prevQualityFindings.sort().join('\0');
522
- if (currentFindings === prevFindings && currentFindings !== '')
523
- break;
524
- prevQualityFindings = qualityResult.findings;
525
- if (round >= (task.maxReviewRounds ?? 5))
526
- break;
527
- }
528
- }
529
- }
530
- const finalReport = specReport ?? finalImplReport;
531
- const aggregated = aggregateResult(finalReport, specReport, qualityResult.report, specStatus, qualityResult.status);
532
- // File artifact verification: check whether output targets exist on disk after all work.
533
- // Only applies when status is ok; non-ok statuses skip verification entirely.
534
- const fileArtifactsMissing = finalImplResult.status === 'ok' && outputTargets.length > 0
535
- ? checkOutputTargets(outputTargets)
536
- : undefined;
537
- // Status downgrade: review verdicts are authoritative. File artifact verification
538
- // is also authoritative — missing output targets downgrade ok → incomplete.
539
- const finalStatus = finalImplResult.status === 'ok' &&
540
- (specStatus === 'changes_required' || qualityResult.status === 'changes_required')
541
- ? 'incomplete'
542
- : finalImplResult.status === 'ok' && fileArtifactsMissing
543
- ? 'incomplete'
544
- : finalImplResult.status;
545
- return {
546
- ...finalImplResult,
547
- status: finalStatus,
548
- workerStatus,
549
- specReviewStatus: specStatus,
550
- qualityReviewStatus: qualityResult.status,
551
- specReviewReason: specResult.errorReason,
552
- qualityReviewReason: qualityResult.errorReason,
553
- structuredReport: aggregated,
554
- implementationReport: finalImplReport,
555
- specReviewReport: specReport,
556
- qualityReviewReport: qualityResult.report,
557
- filePathsSkipped,
558
- agents: {
559
- implementer: resolved.slot,
560
- specReviewer: otherSlot,
561
- qualityReviewer: reviewPolicy === 'full' ? otherSlot : 'skipped',
562
- },
563
- models: {
564
- implementer: implModel,
565
- specReviewer: reviewModel,
566
- qualityReviewer: reviewPolicy === 'full' ? reviewModel : null,
567
- },
568
- fileArtifactsMissing,
569
- commitSha,
570
- commitError,
571
- };
572
- }
573
- finally {
574
- heartbeat?.stop();
575
- }
576
- }
577
- export async function runTasks(tasks, config, options = {}) {
578
- if (tasks.length === 0)
579
- return [];
580
- const expandedTasks = tasks.map((task) => {
581
- try {
582
- return expandContextBlocks(task, options.runtime?.contextBlockStore);
583
- }
584
- catch (err) {
585
- return { error: err instanceof Error ? err.message : String(err) };
586
- }
587
- });
588
- const readinessResults = expandedTasks.map((entry) => {
589
- if ('error' in entry)
590
- return undefined;
591
- const task = entry;
592
- if (task.briefQualityPolicy === 'off') {
593
- return { action: 'ignored', missingPillars: [], layer2Warnings: [], layer3Hints: [], briefQualityWarnings: [] };
594
- }
595
- return evaluateReadiness(task, task.briefQualityPolicy ?? 'warn');
596
- });
597
- const refusedResults = expandedTasks.map((entry, idx) => {
598
- if ('error' in entry)
599
- return undefined;
600
- const readiness = readinessResults[idx];
601
- if (!readiness)
602
- return undefined;
603
- if (readiness.action === 'refuse') {
604
- return {
605
- output: `Brief too vague: missing ${readiness.missingPillars.join(', ')}`,
606
- status: 'brief_too_vague',
607
- usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUSD: null },
608
- turns: 0,
609
- filesRead: [],
610
- filesWritten: [],
611
- toolCalls: [],
612
- outputIsDiagnostic: true,
613
- escalationLog: [],
614
- errorCode: 'brief_too_vague',
615
- briefQualityWarnings: readiness.briefQualityWarnings,
616
- retryable: false,
617
- };
618
- }
619
- return undefined;
620
- });
621
- const resolved = expandedTasks.map((entry, idx) => {
622
- if ('error' in entry) {
623
- return { task: tasks[idx], error: entry.error, errorCode: 'context_block_not_found' };
624
- }
625
- const task = entry;
626
- const agentType = task.agentType ?? 'standard';
627
- try {
628
- const resolved_agent = resolveAgent(agentType, (task.requiredCapabilities ?? []), config);
629
- return { task, resolved: resolved_agent };
630
- }
631
- catch (err) {
632
- return {
633
- task,
634
- error: err instanceof Error ? err.message : String(err),
635
- errorCode: 'capability_missing',
636
- };
637
- }
638
- });
639
- for (const r of resolved) {
640
- if ('error' in r)
641
- continue;
642
- if (r.task.effort === undefined) {
643
- const inferred = inferEffort(r.task.prompt);
644
- if (inferred !== undefined) {
645
- r.task = { ...r.task, effort: inferred };
646
- }
647
- }
648
- }
649
- if (resolved.length > 1) {
650
- const PARALLEL_SAFETY_SUFFIX = '\n\nYou are running in parallel with other tasks. ' +
651
- 'Do NOT run full-project build commands (`npm run build`, `tsc`, `cargo build`). ' +
652
- 'Only run task-specific test commands if provided.';
653
- for (const r of resolved) {
654
- if ('error' in r)
655
- continue;
656
- r.task = {
657
- ...r.task,
658
- prompt: r.task.prompt + PARALLEL_SAFETY_SUFFIX +
659
- (r.task.testCommand ? `\nTo verify your work, run: \`${r.task.testCommand}\`` : ''),
660
- };
661
- }
662
- }
663
- return Promise.all(resolved.map((r, index) => {
664
- if ('error' in r) {
665
- return Promise.resolve({ ...errorResult(r.error), errorCode: r.errorCode });
666
- }
667
- const refused = refusedResults[index];
668
- if (refused) {
669
- return Promise.resolve(refused);
670
- }
671
- const readiness = readinessResults[index];
672
- return executeReviewedLifecycle(r.task, r.resolved, config, index, options.onProgress, {
673
- batchId: options.batchId,
674
- recordHeartbeat: options.recordHeartbeat,
675
- }, {
676
- logger: options.logger,
677
- verbose: options.verbose ?? config.diagnostics?.verbose ?? false,
678
- verboseStream: options.verboseStream,
679
- }).then((result) => {
680
- if (readiness && readiness.briefQualityWarnings.length > 0) {
681
- return { ...result, briefQualityWarnings: readiness.briefQualityWarnings };
682
- }
683
- return result;
684
- });
685
- }));
686
- }
687
- //# sourceMappingURL=run-tasks.js.map