ai-maestro 1.0.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 (81) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +15 -0
  3. package/README.md +727 -0
  4. package/bin/maestro.mjs +246 -0
  5. package/package.json +29 -0
  6. package/src/checkpoint.mjs +102 -0
  7. package/src/codex-diagnostics.mjs +682 -0
  8. package/src/codex-home-inspect.mjs +252 -0
  9. package/src/codex-home.mjs +258 -0
  10. package/src/commands.mjs +809 -0
  11. package/src/config.mjs +11 -0
  12. package/src/debug.mjs +164 -0
  13. package/src/encoding-guard.mjs +125 -0
  14. package/src/engines/ai-router-engine.mjs +37 -0
  15. package/src/engines/codex-engine.mjs +21 -0
  16. package/src/engines/engine.mjs +21 -0
  17. package/src/engines/registry.mjs +29 -0
  18. package/src/files.mjs +65 -0
  19. package/src/format.mjs +132 -0
  20. package/src/lock.mjs +439 -0
  21. package/src/memory-log.mjs +18 -0
  22. package/src/memory.mjs +1 -0
  23. package/src/orchestration/attempt-chain-runtime.mjs +627 -0
  24. package/src/orchestration/budget-manager.mjs +121 -0
  25. package/src/orchestration/capability-registry.mjs +108 -0
  26. package/src/orchestration/consolidator.mjs +772 -0
  27. package/src/orchestration/delegation-executor.mjs +262 -0
  28. package/src/orchestration/delegation-manager.mjs +116 -0
  29. package/src/orchestration/deployment-intent.mjs +36 -0
  30. package/src/orchestration/engine-history.mjs +110 -0
  31. package/src/orchestration/engine-policy.mjs +73 -0
  32. package/src/orchestration/engine-selector.mjs +187 -0
  33. package/src/orchestration/execution-context.mjs +45 -0
  34. package/src/orchestration/failure-classifier.mjs +136 -0
  35. package/src/orchestration/failure-evidence.mjs +237 -0
  36. package/src/orchestration/fallback-chain-lock.mjs +217 -0
  37. package/src/orchestration/fallback-chain-trail.mjs +218 -0
  38. package/src/orchestration/fallback-executor.mjs +146 -0
  39. package/src/orchestration/fallback-graph.mjs +106 -0
  40. package/src/orchestration/fallback-recommendation.mjs +73 -0
  41. package/src/orchestration/file-conflict-detector.mjs +126 -0
  42. package/src/orchestration/host-validation.mjs +241 -0
  43. package/src/orchestration/orchestration-loop.mjs +1971 -0
  44. package/src/orchestration/orchestration-runtime.mjs +1019 -0
  45. package/src/orchestration/orchestration-scheduler.mjs +135 -0
  46. package/src/orchestration/orchestration-trail.mjs +192 -0
  47. package/src/orchestration/preflight.mjs +212 -0
  48. package/src/orchestration/provider-health.mjs +566 -0
  49. package/src/orchestration/provider-router.mjs +352 -0
  50. package/src/orchestration/rc-check-adapters.mjs +817 -0
  51. package/src/orchestration/rc-check.mjs +544 -0
  52. package/src/orchestration/rc-policy.mjs +200 -0
  53. package/src/orchestration/runtime-gate.mjs +146 -0
  54. package/src/orchestration/sector-managers.mjs +215 -0
  55. package/src/orchestration/self-hosting-canary.mjs +231 -0
  56. package/src/orchestration/self-hosting-readiness.mjs +244 -0
  57. package/src/orchestration/spec-planner.mjs +877 -0
  58. package/src/orchestration/subtask-planner.mjs +176 -0
  59. package/src/orchestration/task-classifier.mjs +241 -0
  60. package/src/orchestration/verifier.mjs +1608 -0
  61. package/src/orchestration-commands.mjs +279 -0
  62. package/src/planner.mjs +38 -0
  63. package/src/project.mjs +1 -0
  64. package/src/run-diagnostics.mjs +641 -0
  65. package/src/runner.mjs +243 -0
  66. package/src/safety.mjs +116 -0
  67. package/src/schema.mjs +371 -0
  68. package/src/session-commands.mjs +521 -0
  69. package/src/shell.mjs +93 -0
  70. package/src/smart-planner.mjs +249 -0
  71. package/src/task-commands.mjs +1182 -0
  72. package/src/tasks.mjs +134 -0
  73. package/src/ui/commands.mjs +76 -0
  74. package/src/ui/events.mjs +45 -0
  75. package/src/ui/public/app.js +600 -0
  76. package/src/ui/public/index.html +88 -0
  77. package/src/ui/public/style.css +460 -0
  78. package/src/ui/server.mjs +112 -0
  79. package/src/ui/state.mjs +504 -0
  80. package/src/usage.mjs +178 -0
  81. package/src/workspace-diff.mjs +228 -0
@@ -0,0 +1,1019 @@
1
+ import { runTask, withAccountingLock } from '../task-commands.mjs';
2
+ import { addTask, getTaskById, getTasks, updateTaskStatus } from '../tasks.mjs';
3
+ import { getLatestRunForTask } from '../runner.mjs';
4
+ import { ensureSafetyConfig } from '../safety.mjs';
5
+ import { redactCredentials, redactObject } from '../format.mjs';
6
+ import { evaluateTaskRun, countRunsForTask } from './runtime-gate.mjs';
7
+ import { buildDelegationPlan } from './delegation-manager.mjs';
8
+ import { runOrchestrationLoop } from './orchestration-loop.mjs';
9
+ import { consolidateResults } from './consolidator.mjs';
10
+ import { verifyConsolidatedResult } from './verifier.mjs';
11
+ import { appendOrchestrationEvent, getOrchestrationEvents } from './orchestration-trail.mjs';
12
+ import { computeWaves, planWaveExecution } from './orchestration-scheduler.mjs';
13
+ import { detectFileConflicts } from './file-conflict-detector.mjs';
14
+
15
+ const SCHEMA_VERSION = 1;
16
+ const DEFAULT_LIMITS = Object.freeze({ maxDepth: 2, maxSubtasks: 8, maxAgentsPerManager: 4, maxTransitions: 40 });
17
+ const TERMINAL_STATUSES = new Set(['completed', 'completed_with_warnings', 'failed', 'blocked', 'needs_human', 'archived']);
18
+ const ACCEPTABLE_DEPENDENCY_STATUSES = new Set(['completed', 'completed_with_warnings']);
19
+ const SUCCESS_PARENT_STATUSES = new Set(['accepted', 'accepted_with_warnings']);
20
+ const SUCCESS_VERIFICATION_OUTCOMES = new Set(['verified', 'verified_with_warnings']);
21
+ const runtimeChains = new Map();
22
+
23
+ function isPlainObject(value) {
24
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
25
+ }
26
+
27
+ function cloneData(value) {
28
+ if (value === undefined) return undefined;
29
+ return JSON.parse(JSON.stringify(value));
30
+ }
31
+
32
+ function normalizeMode(mode) {
33
+ return mode === 'real' ? 'real' : 'dry-run';
34
+ }
35
+
36
+ function normalizeTaskId(taskId) {
37
+ if (taskId === undefined || taskId === null || String(taskId).trim().length === 0) {
38
+ throw new TypeError('taskId is required');
39
+ }
40
+ return String(taskId);
41
+ }
42
+
43
+ function mergeDefined(base, extra) {
44
+ const next = { ...base };
45
+ for (const [key, value] of Object.entries(extra || {})) {
46
+ if (value !== undefined) next[key] = value;
47
+ }
48
+ return next;
49
+ }
50
+
51
+ function buildResult({ taskId, mode, executed = false, plan = null, executionPlan = undefined, children = [], consolidation = null, verification = null, parentStatus = null, statusOverrideReason = null, trace = [], warnings = [], errors = [], summary = '' }) {
52
+ return redactObject({
53
+ schemaVersion: SCHEMA_VERSION,
54
+ taskId: String(taskId),
55
+ mode,
56
+ executed,
57
+ plan: cloneData(plan),
58
+ ...(executionPlan !== undefined ? { executionPlan: cloneData(executionPlan) } : {}),
59
+ children: cloneData(children),
60
+ consolidation: cloneData(consolidation),
61
+ verification: cloneData(verification),
62
+ parentStatus,
63
+ statusOverrideReason,
64
+ trace: cloneData(trace),
65
+ warnings: [...warnings],
66
+ errors: errors.map(error => typeof error === 'string' ? error : (error && error.message) || String(error)),
67
+ summary
68
+ });
69
+ }
70
+
71
+ async function runSerialized(taskId, fn) {
72
+ const key = String(taskId);
73
+ const previous = runtimeChains.get(key) || Promise.resolve();
74
+ let release;
75
+ const current = new Promise(resolve => { release = resolve; });
76
+ runtimeChains.set(key, previous.then(() => current, () => current));
77
+ await previous.catch(() => {});
78
+ try {
79
+ return await fn();
80
+ } finally {
81
+ release();
82
+ if (runtimeChains.get(key) === current) {
83
+ runtimeChains.delete(key);
84
+ }
85
+ }
86
+ }
87
+
88
+ async function planSerialOrchestration({ task, mode, force, rawInput }) {
89
+ const suppliedContext = cloneData(rawInput.context || {});
90
+ const suppliedModules = cloneData(rawInput.moduleResults || {});
91
+ let runtimeGate = suppliedModules.runtimeGate || suppliedContext.runtimeGateResult || null;
92
+
93
+ if (!runtimeGate) {
94
+ const runsSoFar = await countRunsForTask(task.id);
95
+ runtimeGate = await evaluateTaskRun({
96
+ task,
97
+ projectRoot: process.cwd(),
98
+ mode,
99
+ force,
100
+ runsSoFar
101
+ });
102
+ }
103
+
104
+ const classification = suppliedModules.classification || runtimeGate.taskClassification || null;
105
+ const manager = suppliedModules.manager || runtimeGate.selectedManager || null;
106
+ const engineCandidate = suppliedModules.engineCandidate || runtimeGate.selectedEngine || null;
107
+ const preflight = suppliedModules.preflight || runtimeGate.preflight || null;
108
+ const budget = suppliedModules.budget || (runtimeGate.budget && runtimeGate.budget.evaluation) || runtimeGate.budget || null;
109
+ const policy = suppliedModules.policy || runtimeGate.policy || null;
110
+
111
+ const moduleResults = mergeDefined({
112
+ classification,
113
+ manager,
114
+ engineCandidate,
115
+ preflight,
116
+ budget,
117
+ policy,
118
+ runtimeGate
119
+ }, suppliedModules);
120
+
121
+ const context = {
122
+ ...suppliedContext,
123
+ runtimeGateResult: runtimeGate,
124
+ acceptanceCriteria: suppliedContext.acceptanceCriteria || task.acceptance || []
125
+ };
126
+
127
+ if ((runtimeGate.decision === 'DELEGATE' || (manager && manager.decision === 'decompose')) && !moduleResults.delegation) {
128
+ moduleResults.delegation = buildDelegationPlan(task, classification || {}, {
129
+ depth: Number(context.orchestrationDepth || 0)
130
+ });
131
+ }
132
+
133
+ const plan = runOrchestrationLoop({
134
+ task: cloneData(task),
135
+ context,
136
+ limits: { ...DEFAULT_LIMITS, ...(rawInput.limits || {}) },
137
+ moduleResults,
138
+ adapters: rawInput.adapters || {}
139
+ });
140
+
141
+ return plan;
142
+ }
143
+
144
+ function hasBlockingConflict(plan) {
145
+ return Array.isArray(plan && plan.conflicts) && plan.conflicts.some(conflict => {
146
+ const target = String(conflict.file || conflict.path || '').replace(/\\/g, '/').toLowerCase();
147
+ return conflict.severity === 'critical' ||
148
+ conflict.type === 'parallel-write' ||
149
+ conflict.parallel === true ||
150
+ target === '.env' || target.endsWith('/.env') || target.startsWith('../') || target.includes('/../');
151
+ });
152
+ }
153
+
154
+ function normalizeConcurrency(concurrency) {
155
+ if (!concurrency || concurrency.enabled !== true) {
156
+ return { enabled: false, strategy: 'serial', maxConcurrent: 1 };
157
+ }
158
+ const parsed = Number.parseInt(concurrency.maxConcurrent, 10);
159
+ const requested = Number.isFinite(parsed) ? parsed : 2;
160
+ return {
161
+ enabled: true,
162
+ strategy: 'controlled-parallel',
163
+ maxConcurrent: Math.max(1, Math.min(4, requested))
164
+ };
165
+ }
166
+
167
+ function plannedSubtasks(plan) {
168
+ return Array.isArray(plan && plan.plannedSubtasks) ? cloneData(plan.plannedSubtasks) : [];
169
+ }
170
+
171
+ function subtaskId(subtask) {
172
+ return String(subtask && subtask.id);
173
+ }
174
+
175
+ function sortSubtasksById(subtasks) {
176
+ return [...subtasks].sort((a, b) => subtaskId(a).localeCompare(subtaskId(b)));
177
+ }
178
+
179
+ function auditWavePlan(waves, subtasks, concurrency) {
180
+ const byId = new Map(subtasks.map(subtask => [subtaskId(subtask), subtask]));
181
+ return waves.map(wave => {
182
+ const waveSubtasks = wave.subtaskIds.map(id => byId.get(String(id))).filter(Boolean);
183
+ const waveConflicts = detectFileConflicts(waveSubtasks, { projectRoot: process.cwd() });
184
+ const planned = planWaveExecution({ ...wave, subtasks: waveSubtasks }, subtasks, { waveConflicts, maxConcurrent: concurrency.maxConcurrent });
185
+ return {
186
+ waveIndex: wave.waveIndex,
187
+ parallelSubtaskIds: planned.parallel.map(subtaskId),
188
+ serialSubtaskIds: planned.serial.map(subtaskId)
189
+ };
190
+ });
191
+ }
192
+
193
+ function dryRunExecutionPlan(plan, concurrencyInput) {
194
+ const concurrency = normalizeConcurrency(concurrencyInput);
195
+ if (!concurrency.enabled) return undefined;
196
+ const subtasks = plannedSubtasks(plan);
197
+ try {
198
+ const waves = computeWaves(subtasks);
199
+ return {
200
+ strategy: concurrency.strategy,
201
+ concurrency: { maxConcurrent: concurrency.maxConcurrent, observedMaxActive: 0 },
202
+ waves: auditWavePlan(waves, subtasks, concurrency)
203
+ };
204
+ } catch (error) {
205
+ return {
206
+ strategy: concurrency.strategy,
207
+ concurrency: { maxConcurrent: concurrency.maxConcurrent, observedMaxActive: 0 },
208
+ waves: [],
209
+ error: (error && error.message) || String(error)
210
+ };
211
+ }
212
+ }
213
+
214
+ function dependencyList(subtask) {
215
+ return Array.isArray(subtask.blockedBy) ? subtask.blockedBy.map(String) : [];
216
+ }
217
+
218
+ function serialOrder(subtasks, plan) {
219
+ const byId = new Map(subtasks.map(subtask => [String(subtask.id), subtask]));
220
+ const plannedOrder = plan && plan.simulation && Array.isArray(plan.simulation.order)
221
+ ? plan.simulation.order.map(String)
222
+ : subtasks.map(subtask => String(subtask.id));
223
+ const orderRank = new Map(plannedOrder.map((id, index) => [id, index]));
224
+ const remaining = new Set(subtasks.map(subtask => String(subtask.id)));
225
+ const result = [];
226
+
227
+ while (remaining.size > 0) {
228
+ const ready = [...remaining]
229
+ .filter(id => dependencyList(byId.get(id)).every(dep => !remaining.has(dep)))
230
+ .sort((a, b) => (orderRank.get(a) ?? 9999) - (orderRank.get(b) ?? 9999) || a.localeCompare(b));
231
+ if (ready.length === 0) {
232
+ throw new Error('dependency cycle remained after 03C planning');
233
+ }
234
+ const id = ready[0];
235
+ remaining.delete(id);
236
+ result.push(byId.get(id));
237
+ }
238
+
239
+ return result;
240
+ }
241
+
242
+ function childDescription(subtask) {
243
+ return String(subtask.description || subtask.title || subtask.id);
244
+ }
245
+
246
+ function assignmentFor(plan, subtaskId) {
247
+ const assignments = plan && plan.plannedDelegation && Array.isArray(plan.plannedDelegation.assignments)
248
+ ? plan.plannedDelegation.assignments
249
+ : [];
250
+ return assignments.find(item => String(item.subtaskId) === String(subtaskId)) || null;
251
+ }
252
+
253
+ async function materializeChild(parentTask, subtask, plan) {
254
+ const existing = (await getTasks()).find(task =>
255
+ String(task.parentTaskId) === String(parentTask.id) && String(task.subtaskId) === String(subtask.id)
256
+ );
257
+ if (existing) return existing;
258
+
259
+ const assignment = assignmentFor(plan, subtask.id);
260
+ return addTask(childDescription(subtask), {
261
+ title: subtask.title || childDescription(subtask),
262
+ status: 'pending',
263
+ priority: parentTask.priority || 'medium',
264
+ engine: (assignment && assignment.candidateEngine) || subtask.engine || parentTask.engine || 'codex9',
265
+ files: Array.isArray(subtask.expectedFiles) ? subtask.expectedFiles : (Array.isArray(subtask.files) ? subtask.files : []),
266
+ acceptance: Array.isArray(subtask.acceptance) ? subtask.acceptance : (Array.isArray(parentTask.acceptance) ? parentTask.acceptance : []),
267
+ requestKind: parentTask.requestKind || null,
268
+ sourceRequest: parentTask.sourceRequest || null,
269
+ sourceSpec: parentTask.sourceSpec || null,
270
+ parentTaskId: parentTask.id,
271
+ subtaskId: String(subtask.id),
272
+ blockedBy: dependencyList(subtask)
273
+ });
274
+ }
275
+
276
+ async function materializeChildren(parentTask, subtasks, plan) {
277
+ const children = [];
278
+ for (const subtask of subtasks) {
279
+ children.push(await materializeChild(parentTask, subtask, plan));
280
+ }
281
+ return children;
282
+ }
283
+
284
+ function childResultEntry({ subtask, task, status, run, skipped = false, reason = null }) {
285
+ return {
286
+ subtaskId: String(subtask.id),
287
+ taskId: task ? task.id : null,
288
+ status: status || (task && task.status) || 'unknown',
289
+ runId: run && run.runId ? run.runId : null,
290
+ run: run ? cloneData(run) : null,
291
+ noChangeExpected: subtask.noChangeExpected === true ||
292
+ subtask.expectedNoChange === true ||
293
+ ((subtask.expectedFiles === undefined || (Array.isArray(subtask.expectedFiles) && subtask.expectedFiles.length === 0)) &&
294
+ subtask.requires && subtask.requires.filesystemWrite === false),
295
+ skipped,
296
+ reason
297
+ };
298
+ }
299
+
300
+ function evidenceFromRun(run) {
301
+ if (!run || !run.runId) return [];
302
+ const evidence = [{ type: 'run-status', sourceId: run.runId, field: 'status', value: { status: run.status } }];
303
+ if (Object.prototype.hasOwnProperty.call(run, 'workspaceChanged')) {
304
+ evidence.push({
305
+ type: 'workspace-diff',
306
+ sourceId: run.runId,
307
+ field: 'workspace',
308
+ value: {
309
+ workspaceChanged: run.workspaceChanged,
310
+ changedFiles: Array.isArray(run.changedFiles) ? run.changedFiles : [],
311
+ createdFiles: Array.isArray(run.createdFiles) ? run.createdFiles : [],
312
+ deletedFiles: Array.isArray(run.deletedFiles) ? run.deletedFiles : []
313
+ }
314
+ });
315
+ }
316
+ if (run.syntaxValidation !== undefined || run.syntaxValidationFailed !== undefined) {
317
+ evidence.push({ type: 'syntax-validation', sourceId: run.runId, field: 'syntaxValidation', value: { syntaxValidation: run.syntaxValidation || [], syntaxValidationFailed: run.syntaxValidationFailed === true } });
318
+ }
319
+ if (run.hostValidationFallback !== undefined) {
320
+ evidence.push({ type: 'host-validation', sourceId: run.runId, field: 'hostValidationFallback', value: run.hostValidationFallback });
321
+ }
322
+ if (run.failureCategory !== undefined || run.infrastructureFailure !== undefined) {
323
+ evidence.push({ type: 'failure-classification', sourceId: run.runId, field: 'failureCategory', value: { failureCategory: run.failureCategory || null, infrastructureFailure: run.infrastructureFailure === true } });
324
+ }
325
+ if (Array.isArray(run.warnings) && run.warnings.length > 0) {
326
+ evidence.push({ type: 'diagnostics', sourceId: run.runId, field: 'warnings', value: { warnings: run.warnings } });
327
+ }
328
+ return evidence;
329
+ }
330
+
331
+ function subtaskResultFromChild(child) {
332
+ const status = child.status;
333
+ const run = child.run || null;
334
+ if (run && run.runId && ['completed', 'completed_with_warnings', 'failed'].includes(status)) {
335
+ return {
336
+ subtaskId: child.subtaskId,
337
+ runId: run.runId,
338
+ runStatus: status,
339
+ noChangeExpected: child.noChangeExpected === true,
340
+ evidence: evidenceFromRun(run),
341
+ warnings: Array.isArray(run.warnings) ? run.warnings : [],
342
+ infrastructureFailure: run.infrastructureFailure === true
343
+ };
344
+ }
345
+ if (status === 'blocked') {
346
+ return { subtaskId: child.subtaskId, runId: null, runStatus: 'blocked', noExecutionReason: 'blocked_before_execution', evidence: [], warnings: child.reason ? [child.reason] : [] };
347
+ }
348
+ if (status === 'needs_human') {
349
+ return { subtaskId: child.subtaskId, runId: null, runStatus: 'needs_human', noExecutionReason: 'needs_human_before_execution', evidence: [], warnings: child.reason ? [child.reason] : [] };
350
+ }
351
+ return { subtaskId: child.subtaskId, runId: null, runStatus: null, noExecutionReason: 'result_missing', evidence: [], warnings: child.reason ? [child.reason] : [] };
352
+ }
353
+
354
+ async function latestRunAndTask(taskId) {
355
+ return withAccountingLock(async () => {
356
+ const task = await getTaskById(taskId);
357
+ const run = await getLatestRunForTask(taskId);
358
+ return { task, run };
359
+ });
360
+ }
361
+
362
+ function dependencyBlockReason(subtask, statusBySubtask) {
363
+ for (const dependency of dependencyList(subtask)) {
364
+ const dependencyKey = String(dependency);
365
+ if (!statusBySubtask.has(dependencyKey)) {
366
+ continue;
367
+ }
368
+ const dependencyStatus = statusBySubtask.get(dependencyKey);
369
+ if (!ACCEPTABLE_DEPENDENCY_STATUSES.has(dependencyStatus)) {
370
+ return 'dependency ' + dependency + ' finished as ' + (dependencyStatus || 'missing');
371
+ }
372
+ }
373
+ return null;
374
+ }
375
+
376
+ async function runChildrenSerially({ parentTask, subtasks, plan, force, suppressOutput, engine }) {
377
+ const ordered = serialOrder(subtasks, plan);
378
+ const materialized = await materializeChildren(parentTask, subtasks, plan);
379
+ const taskBySubtask = new Map(materialized.map(task => [String(task.subtaskId), task]));
380
+ const statusBySubtask = new Map();
381
+ const children = [];
382
+ const errors = [];
383
+ const warnings = [];
384
+
385
+ for (const subtask of ordered) {
386
+ const childTask = taskBySubtask.get(String(subtask.id));
387
+ const blockReason = dependencyBlockReason(subtask, statusBySubtask);
388
+ if (blockReason) {
389
+ const latest = await withAccountingLock(async () => {
390
+ await updateTaskStatus(childTask.id, 'blocked');
391
+ return getTaskById(childTask.id);
392
+ });
393
+ statusBySubtask.set(String(subtask.id), 'blocked');
394
+ children.push(childResultEntry({ subtask, task: latest || childTask, status: 'blocked', run: null, skipped: true, reason: blockReason }));
395
+ continue;
396
+ }
397
+
398
+ let latest = await latestRunAndTask(childTask.id);
399
+ if (latest.task && TERMINAL_STATUSES.has(latest.task.status) && !(latest.task.status === 'needs_human' && force === true)) {
400
+ statusBySubtask.set(String(subtask.id), latest.task.status);
401
+ children.push(childResultEntry({ subtask, task: latest.task, status: latest.task.status, run: latest.run, skipped: true, reason: 'already_terminal' }));
402
+ continue;
403
+ }
404
+
405
+ let runOutcome = null;
406
+ try {
407
+ runOutcome = await runTask(childTask.id, { force, suppressOutput: true, engine });
408
+ } catch (error) {
409
+ const message = 'child ' + subtask.id + ' execution error: ' + ((error && error.message) || String(error));
410
+ errors.push(message);
411
+ warnings.push(message);
412
+ await withAccountingLock(() => updateTaskStatus(childTask.id, 'needs_human'));
413
+ }
414
+
415
+ latest = await latestRunAndTask(childTask.id);
416
+ const status = (runOutcome && runOutcome.status) || (latest.task ? latest.task.status : 'needs_human');
417
+ const run = (runOutcome && runOutcome.run) || latest.run;
418
+ statusBySubtask.set(String(subtask.id), status);
419
+ children.push(childResultEntry({ subtask, task: latest.task || childTask, status, run, skipped: false, reason: null }));
420
+ }
421
+
422
+ return { children, errors, warnings };
423
+ }
424
+
425
+ async function executeChildRun({ subtask, childTask, force, engine }) {
426
+ let runOutcome = null;
427
+ const errors = [];
428
+ const warnings = [];
429
+ try {
430
+ runOutcome = await runTask(childTask.id, { force, suppressOutput: true, engine });
431
+ } catch (error) {
432
+ const message = 'child ' + subtask.id + ' execution error: ' + ((error && error.message) || String(error));
433
+ errors.push(message);
434
+ warnings.push(message);
435
+ await withAccountingLock(() => updateTaskStatus(childTask.id, 'needs_human'));
436
+ }
437
+
438
+ const latest = await latestRunAndTask(childTask.id);
439
+ const status = (runOutcome && runOutcome.status) || (latest.task ? latest.task.status : 'needs_human');
440
+ const run = (runOutcome && runOutcome.run) || latest.run;
441
+ return {
442
+ entry: childResultEntry({ subtask, task: latest.task || childTask, status, run, skipped: false, reason: null }),
443
+ status,
444
+ errors,
445
+ warnings
446
+ };
447
+ }
448
+
449
+ async function markDependencyBlocked({ subtask, childTask, reason }) {
450
+ const latest = await withAccountingLock(async () => {
451
+ await updateTaskStatus(childTask.id, 'blocked');
452
+ return getTaskById(childTask.id);
453
+ });
454
+ return childResultEntry({ subtask, task: latest || childTask, status: 'blocked', run: null, skipped: true, reason });
455
+ }
456
+
457
+ function removeSatisfiedDependencies(subtask, statusBySubtask, pendingIds) {
458
+ const blockedBy = dependencyList(subtask).filter(dependency => pendingIds.has(String(dependency)) && !ACCEPTABLE_DEPENDENCY_STATUSES.has(statusBySubtask.get(String(dependency))));
459
+ return { ...cloneData(subtask), blockedBy };
460
+ }
461
+
462
+ async function settleKnownBlockedSubtasks({ pending, taskBySubtask, statusBySubtask, childrenBySubtask }) {
463
+ let changed = true;
464
+ while (changed) {
465
+ changed = false;
466
+ for (const subtask of [...pending.values()].sort((a, b) => subtaskId(a).localeCompare(subtaskId(b)))) {
467
+ const blockReason = dependencyBlockReason(subtask, statusBySubtask);
468
+ if (!blockReason) continue;
469
+ const childTask = taskBySubtask.get(subtaskId(subtask));
470
+ const entry = await markDependencyBlocked({ subtask, childTask, reason: blockReason });
471
+ statusBySubtask.set(subtaskId(subtask), 'blocked');
472
+ childrenBySubtask.set(subtaskId(subtask), entry);
473
+ pending.delete(subtaskId(subtask));
474
+ changed = true;
475
+ }
476
+ }
477
+ }
478
+
479
+ async function runLimitedSubtasks(subtasks, limit, worker) {
480
+ const ordered = sortSubtasksById(subtasks);
481
+ const results = [];
482
+ const active = new Set();
483
+ let nextIndex = 0;
484
+ let activeCount = 0;
485
+ let observedMaxActive = 0;
486
+
487
+ async function launch(subtask) {
488
+ activeCount += 1;
489
+ observedMaxActive = Math.max(observedMaxActive, activeCount);
490
+ try {
491
+ results.push(await worker(subtask));
492
+ } finally {
493
+ activeCount -= 1;
494
+ }
495
+ }
496
+
497
+ while (nextIndex < ordered.length || active.size > 0) {
498
+ while (nextIndex < ordered.length && active.size < limit) {
499
+ const taskPromise = launch(ordered[nextIndex]).finally(() => {
500
+ active.delete(taskPromise);
501
+ });
502
+ active.add(taskPromise);
503
+ nextIndex += 1;
504
+ }
505
+ if (active.size > 0) {
506
+ await Promise.race(active);
507
+ }
508
+ }
509
+
510
+ return { results: results.sort((a, b) => subtaskId(a.subtask).localeCompare(subtaskId(b.subtask))), observedMaxActive };
511
+ }
512
+
513
+ async function runChildrenControlledParallel({ parentTask, subtasks, plan, force, suppressOutput, engine, concurrency }) {
514
+ const materialized = await materializeChildren(parentTask, subtasks, plan);
515
+ await withAccountingLock(() => ensureSafetyConfig());
516
+ const taskBySubtask = new Map(materialized.map(task => [String(task.subtaskId), task]));
517
+ const statusBySubtask = new Map();
518
+ const childrenBySubtask = new Map();
519
+ const errors = [];
520
+ const warnings = [];
521
+ let observedMaxActive = 0;
522
+
523
+ for (const subtask of sortSubtasksById(subtasks)) {
524
+ const childTask = taskBySubtask.get(subtaskId(subtask));
525
+ const latest = await latestRunAndTask(childTask.id);
526
+ if (latest.task && TERMINAL_STATUSES.has(latest.task.status) && !(latest.task.status === 'needs_human' && force === true)) {
527
+ statusBySubtask.set(subtaskId(subtask), latest.task.status);
528
+ childrenBySubtask.set(subtaskId(subtask), childResultEntry({ subtask, task: latest.task, status: latest.task.status, run: latest.run, skipped: true, reason: 'already_terminal' }));
529
+ }
530
+ }
531
+
532
+ const pending = new Map(sortSubtasksById(subtasks)
533
+ .filter(subtask => !statusBySubtask.has(subtaskId(subtask)))
534
+ .map(subtask => [subtaskId(subtask), subtask]));
535
+ await settleKnownBlockedSubtasks({ pending, taskBySubtask, statusBySubtask, childrenBySubtask });
536
+
537
+ const pendingIds = new Set(pending.keys());
538
+ const schedulableSubtasks = [...pending.values()].map(subtask => removeSatisfiedDependencies(subtask, statusBySubtask, pendingIds));
539
+ const waves = computeWaves(schedulableSubtasks);
540
+ const auditWaves = [];
541
+
542
+ for (const wave of waves) {
543
+ await settleKnownBlockedSubtasks({ pending, taskBySubtask, statusBySubtask, childrenBySubtask });
544
+ const waveSubtasks = wave.subtaskIds.map(id => pending.get(String(id))).filter(Boolean);
545
+ if (waveSubtasks.length === 0) {
546
+ auditWaves.push({ waveIndex: wave.waveIndex, parallelSubtaskIds: [], serialSubtaskIds: [] });
547
+ continue;
548
+ }
549
+ const normalizedWaveSubtasks = waveSubtasks.map(subtask => removeSatisfiedDependencies(subtask, statusBySubtask, new Set(waveSubtasks.map(subtaskId))));
550
+ const waveConflicts = detectFileConflicts(normalizedWaveSubtasks, { projectRoot: process.cwd() });
551
+ const planned = planWaveExecution({ ...wave, subtasks: normalizedWaveSubtasks }, normalizedWaveSubtasks, { waveConflicts, maxConcurrent: concurrency.maxConcurrent });
552
+ auditWaves.push({
553
+ waveIndex: wave.waveIndex,
554
+ parallelSubtaskIds: planned.parallel.map(subtaskId),
555
+ serialSubtaskIds: planned.serial.map(subtaskId)
556
+ });
557
+
558
+ const worker = async subtask => {
559
+ const originalSubtask = pending.get(subtaskId(subtask)) || subtask;
560
+ const childTask = taskBySubtask.get(subtaskId(subtask));
561
+ const blockReason = dependencyBlockReason(originalSubtask, statusBySubtask);
562
+ if (blockReason) {
563
+ const entry = await markDependencyBlocked({ subtask: originalSubtask, childTask, reason: blockReason });
564
+ return { subtask: originalSubtask, entry, status: 'blocked', errors: [], warnings: [] };
565
+ }
566
+ const result = await executeChildRun({ subtask: originalSubtask, childTask, force, suppressOutput, engine });
567
+ return { subtask: originalSubtask, ...result };
568
+ };
569
+
570
+ const parallelRun = await runLimitedSubtasks(planned.parallel, concurrency.maxConcurrent, worker);
571
+ observedMaxActive = Math.max(observedMaxActive, parallelRun.observedMaxActive);
572
+ for (const result of parallelRun.results) {
573
+ statusBySubtask.set(subtaskId(result.subtask), result.status);
574
+ childrenBySubtask.set(subtaskId(result.subtask), result.entry);
575
+ pending.delete(subtaskId(result.subtask));
576
+ warnings.push(...result.warnings);
577
+ errors.push(...result.errors);
578
+ }
579
+
580
+ for (const subtask of planned.serial) {
581
+ const result = await worker(subtask);
582
+ observedMaxActive = Math.max(observedMaxActive, 1);
583
+ statusBySubtask.set(subtaskId(result.subtask), result.status);
584
+ childrenBySubtask.set(subtaskId(result.subtask), result.entry);
585
+ pending.delete(subtaskId(result.subtask));
586
+ warnings.push(...result.warnings);
587
+ errors.push(...result.errors);
588
+ }
589
+ }
590
+
591
+ await settleKnownBlockedSubtasks({ pending, taskBySubtask, statusBySubtask, childrenBySubtask });
592
+ const children = sortSubtasksById(subtasks).map(subtask => childrenBySubtask.get(subtaskId(subtask))).filter(Boolean);
593
+ return { children, errors, warnings, waves: auditWaves, observedMaxActive };
594
+ }
595
+
596
+ function fallbackConsolidation(task, reason, children = []) {
597
+ return {
598
+ schemaVersion: 1,
599
+ taskId: String(task.id),
600
+ status: 'inconclusive',
601
+ consolidationCompleteness: 'invalid',
602
+ perSubtask: children.map(child => ({ subtaskId: child.subtaskId, observedState: child.status, runId: child.runId || null })),
603
+ evidence: [],
604
+ missingEvidence: [],
605
+ warnings: [reason],
606
+ failures: [reason],
607
+ codeFailures: [],
608
+ infrastructureFailures: [reason],
609
+ conflicts: [],
610
+ inconsistencies: [],
611
+ invalidReferences: [],
612
+ remainingIssues: [reason],
613
+ inputErrors: [reason],
614
+ recommendedNextAction: 'block',
615
+ verifierInputHints: {},
616
+ summary: reason
617
+ };
618
+ }
619
+
620
+ function isValidConsolidation(value) {
621
+ return isPlainObject(value) && value.schemaVersion === 1 && typeof value.taskId === 'string' &&
622
+ ['accepted', 'accepted_with_warnings', 'rejected', 'inconclusive', 'needs_human'].includes(value.status) &&
623
+ ['complete', 'partial', 'invalid'].includes(value.consolidationCompleteness) &&
624
+ ['perSubtask', 'evidence', 'missingEvidence', 'warnings', 'failures', 'codeFailures', 'infrastructureFailures', 'conflicts', 'inconsistencies', 'invalidReferences', 'remainingIssues', 'inputErrors'].every(field => value[field] === undefined || Array.isArray(value[field]));
625
+ }
626
+
627
+ function isValidVerification(value) {
628
+ const outcome = value && (value.verificationOutcome || value.outcome);
629
+ return isPlainObject(value) && value.schemaVersion === 1 && ['verified', 'verified_with_warnings', 'rejected', 'inconclusive', 'blocked', 'needs_human'].includes(outcome);
630
+ }
631
+
632
+ function fallbackVerification(reason) {
633
+ return {
634
+ schemaVersion: 1,
635
+ scope: 'consolidated',
636
+ verificationOutcome: 'inconclusive',
637
+ outcome: 'inconclusive',
638
+ verified: false,
639
+ warnings: [reason],
640
+ failures: [reason],
641
+ errors: [reason],
642
+ recommendations: [{ action: 'block', reason, consultative: false }],
643
+ summary: reason
644
+ };
645
+ }
646
+
647
+ function deriveParentOutcome(consolidation, verification) {
648
+ const consolidatedStatus = consolidation && consolidation.status;
649
+ const completeness = consolidation && consolidation.consolidationCompleteness;
650
+ const outcome = verification && (verification.verificationOutcome || verification.outcome);
651
+
652
+ if (outcome === 'blocked') {
653
+ return { parentStatus: 'blocked', reason: 'verifier blocked the consolidated result' };
654
+ }
655
+ if (outcome === 'needs_human' || consolidatedStatus === 'needs_human') {
656
+ return { parentStatus: 'needs_human', reason: 'verifier or consolidator requires human review' };
657
+ }
658
+ if (outcome === 'rejected') {
659
+ return { parentStatus: 'failed', reason: 'verifier rejected the consolidated result' };
660
+ }
661
+ if (outcome === 'inconclusive') {
662
+ return { parentStatus: 'blocked', reason: 'verifier was inconclusive' };
663
+ }
664
+ if (SUCCESS_VERIFICATION_OUTCOMES.has(outcome) && SUCCESS_PARENT_STATUSES.has(consolidatedStatus) && ['complete', 'partial'].includes(completeness)) {
665
+ if (outcome === 'verified_with_warnings' || consolidatedStatus === 'accepted_with_warnings' || completeness === 'partial') {
666
+ return { parentStatus: 'completed_with_warnings', reason: 'verified with warnings or partial consolidation' };
667
+ }
668
+ return { parentStatus: 'completed', reason: 'verified consolidated result' };
669
+ }
670
+ return { parentStatus: 'blocked', reason: 'parent completion requires accepted consolidation and verified outcome' };
671
+ }
672
+
673
+ async function consolidateAndVerify({ task, subtasks, children, plan, rawInput }) {
674
+ const subtaskResults = children.map(subtaskResultFromChild);
675
+ let consolidation;
676
+ let verification;
677
+ const warnings = [];
678
+ const errors = [];
679
+ const consolidate = rawInput.consolidateResults || consolidateResults;
680
+ const verify = rawInput.verifyConsolidatedResult || verifyConsolidatedResult;
681
+
682
+ try {
683
+ consolidation = consolidate({
684
+ task: cloneData(task),
685
+ subtasks: cloneData(subtasks),
686
+ subtaskResults: cloneData(subtaskResults),
687
+ warnings: [],
688
+ classification: cloneData(plan.decisions ? plan.decisions.classification : {})
689
+ });
690
+ if (!isValidConsolidation(consolidation)) {
691
+ const reason = 'Consolidator returned malformed result';
692
+ warnings.push(reason);
693
+ errors.push(reason);
694
+ consolidation = fallbackConsolidation(task, reason, children);
695
+ }
696
+ } catch (error) {
697
+ const reason = 'Consolidator failed: ' + ((error && error.message) || String(error));
698
+ warnings.push(reason);
699
+ errors.push(reason);
700
+ consolidation = fallbackConsolidation(task, reason, children);
701
+ }
702
+
703
+ try {
704
+ verification = verify({
705
+ consolidatedResult: cloneData(consolidation),
706
+ acceptanceCriteria: Array.isArray(task.acceptance) ? cloneData(task.acceptance) : [],
707
+ options: cloneData(rawInput.verifierOptions || {})
708
+ });
709
+ if (!isValidVerification(verification)) {
710
+ const reason = 'Verifier returned malformed result';
711
+ warnings.push(reason);
712
+ errors.push(reason);
713
+ verification = fallbackVerification(reason);
714
+ }
715
+ } catch (error) {
716
+ const reason = 'Verifier failed: ' + ((error && error.message) || String(error));
717
+ warnings.push(reason);
718
+ errors.push(reason);
719
+ verification = fallbackVerification(reason);
720
+ }
721
+
722
+ const outcome = deriveParentOutcome(consolidation, verification);
723
+ return { consolidation, verification, ...outcome, warnings, errors, subtaskResults };
724
+ }
725
+
726
+ async function nextOrchestrationAttempt(parentTaskId) {
727
+ const events = await getOrchestrationEvents(parentTaskId);
728
+ return events.filter(event =>
729
+ event && !event.parseError &&
730
+ (event.eventType === 'orchestration_started' || event.eventType === 'orchestration_blocked') &&
731
+ String(event.parentTaskId) === String(parentTaskId)
732
+ ).length + 1;
733
+ }
734
+
735
+ function orchestrationEnvelope(taskId, attempt, startTime, eventType, fields = {}) {
736
+ return {
737
+ orchestrationId: String(taskId) + ':' + attempt,
738
+ parentTaskId: String(taskId),
739
+ attempt,
740
+ mode: 'real',
741
+ eventType,
742
+ startTime,
743
+ timestamp: new Date().toISOString(),
744
+ ...fields
745
+ };
746
+ }
747
+
748
+ async function executeReal(input, task, plan) {
749
+ const warnings = [];
750
+ const errors = [];
751
+ const subtasks = plannedSubtasks(plan);
752
+ const concurrency = normalizeConcurrency(input.concurrency);
753
+ const attempt = await nextOrchestrationAttempt(task.id);
754
+ const startTime = new Date().toISOString();
755
+ let observedMaxActive = 0;
756
+ let auditWaves = [];
757
+ const auditFields = () => ({
758
+ strategy: concurrency.strategy,
759
+ ...(concurrency.enabled ? { concurrency: { maxConcurrent: concurrency.maxConcurrent, observedMaxActive }, waves: cloneData(auditWaves) } : {})
760
+ });
761
+ const appendFinished = fields => appendOrchestrationEvent(orchestrationEnvelope(task.id, attempt, startTime, 'orchestration_finished', {
762
+ endTime: new Date().toISOString(),
763
+ trace: plan.trace || [],
764
+ ...auditFields(),
765
+ ...fields
766
+ }));
767
+
768
+ if (!plan.completedPlanning || plan.finalLoopState !== 'loop.completed' || hasBlockingConflict(plan)) {
769
+ const reason = plan.errors && plan.errors.length > 0
770
+ ? plan.errors.map(error => error.message || error.code || String(error)).join('; ')
771
+ : 'real mode requires completed planning without blocking conflicts';
772
+ await appendOrchestrationEvent(orchestrationEnvelope(task.id, attempt, startTime, 'orchestration_blocked', {
773
+ status: plan.finalLoopState === 'loop.needs_human' ? 'needs_human' : 'blocked',
774
+ reason,
775
+ trace: plan.trace || [],
776
+ warnings,
777
+ ...auditFields()
778
+ }));
779
+ return buildResult({
780
+ taskId: task.id,
781
+ mode: 'real',
782
+ executed: false,
783
+ plan,
784
+ parentStatus: plan.finalLoopState === 'loop.needs_human' ? 'needs_human' : 'blocked',
785
+ statusOverrideReason: reason,
786
+ trace: plan.trace || [],
787
+ warnings,
788
+ errors: [reason],
789
+ summary: 'Real orchestration refused: ' + reason
790
+ });
791
+ }
792
+
793
+ if (concurrency.enabled && subtasks.length > 0) {
794
+ const projection = dryRunExecutionPlan(plan, input.concurrency);
795
+ auditWaves = projection && Array.isArray(projection.waves) ? projection.waves : [];
796
+ }
797
+
798
+ await appendOrchestrationEvent(orchestrationEnvelope(task.id, attempt, startTime, 'orchestration_started', auditFields()));
799
+
800
+ if (subtasks.length === 0) {
801
+ let runResult;
802
+ try {
803
+ runResult = await runTask(task.id, { force: input.force === true, suppressOutput: input.suppressOutput === true, engine: input.engine });
804
+ } catch (error) {
805
+ const reason = 'parent direct execution error: ' + ((error && error.message) || String(error));
806
+ await appendFinished({
807
+ status: 'needs_human',
808
+ children: [],
809
+ consolidation: null,
810
+ verification: null,
811
+ warnings: [reason],
812
+ reason
813
+ });
814
+ return buildResult({
815
+ taskId: task.id,
816
+ mode: 'real',
817
+ executed: true,
818
+ plan,
819
+ parentStatus: 'needs_human',
820
+ statusOverrideReason: reason,
821
+ trace: plan.trace || [],
822
+ warnings: [reason],
823
+ errors: [reason],
824
+ summary: reason
825
+ });
826
+ }
827
+ const latest = await getTaskById(task.id);
828
+ const status = (runResult && runResult.status) || (latest && latest.status) || null;
829
+ await appendFinished({
830
+ status: status || 'blocked',
831
+ children: [],
832
+ consolidation: null,
833
+ verification: null,
834
+ warnings,
835
+ reason: status ? 'direct runTask outcome' : 'runTask returned without terminal status'
836
+ });
837
+ return buildResult({
838
+ taskId: task.id,
839
+ mode: 'real',
840
+ executed: true,
841
+ plan,
842
+ children: [],
843
+ parentStatus: status,
844
+ statusOverrideReason: status ? 'direct runTask outcome' : 'runTask returned without terminal status',
845
+ trace: plan.trace || [],
846
+ warnings,
847
+ errors,
848
+ summary: 'Direct orchestration executed through runTask for task #' + task.id + '.'
849
+ });
850
+ }
851
+
852
+ let childRun;
853
+ try {
854
+ childRun = concurrency.enabled
855
+ ? await runChildrenControlledParallel({
856
+ parentTask: task,
857
+ subtasks,
858
+ plan,
859
+ force: input.force === true,
860
+ suppressOutput: input.suppressOutput === true,
861
+ engine: input.engine,
862
+ concurrency
863
+ })
864
+ : await runChildrenSerially({
865
+ parentTask: task,
866
+ subtasks,
867
+ plan,
868
+ force: input.force === true,
869
+ suppressOutput: input.suppressOutput === true,
870
+ engine: input.engine
871
+ });
872
+ observedMaxActive = childRun.observedMaxActive || observedMaxActive;
873
+ if (Array.isArray(childRun.waves)) {
874
+ auditWaves = childRun.waves;
875
+ }
876
+ } catch (error) {
877
+ const reason = 'child persistence or ordering error: ' + ((error && error.message) || String(error));
878
+ await appendFinished({
879
+ status: 'blocked',
880
+ children: [],
881
+ consolidation: null,
882
+ verification: null,
883
+ warnings: [reason],
884
+ reason
885
+ });
886
+ return buildResult({
887
+ taskId: task.id,
888
+ mode: 'real',
889
+ executed: false,
890
+ plan,
891
+ parentStatus: 'blocked',
892
+ statusOverrideReason: reason,
893
+ trace: plan.trace || [],
894
+ warnings: [reason],
895
+ errors: [reason],
896
+ summary: reason
897
+ });
898
+ }
899
+
900
+ warnings.push(...childRun.warnings);
901
+ errors.push(...childRun.errors);
902
+ const verified = await consolidateAndVerify({ task, subtasks, children: childRun.children, plan, rawInput: input });
903
+ warnings.push(...verified.warnings);
904
+ errors.push(...verified.errors);
905
+
906
+ const updated = await updateTaskStatus(task.id, verified.parentStatus);
907
+ if (!updated) {
908
+ errors.push('failed to update parent task status');
909
+ }
910
+ await appendFinished({
911
+ status: updated ? verified.parentStatus : 'blocked',
912
+ children: childRun.children,
913
+ consolidation: verified.consolidation,
914
+ verification: verified.verification,
915
+ warnings,
916
+ reason: updated ? verified.reason : 'failed to update parent task status'
917
+ });
918
+
919
+ return buildResult({
920
+ taskId: task.id,
921
+ mode: 'real',
922
+ executed: true,
923
+ plan,
924
+ children: childRun.children,
925
+ consolidation: verified.consolidation,
926
+ verification: verified.verification,
927
+ parentStatus: updated ? verified.parentStatus : 'blocked',
928
+ statusOverrideReason: updated ? verified.reason : 'failed to update parent task status',
929
+ trace: plan.trace || [],
930
+ warnings,
931
+ errors,
932
+ summary: (concurrency.enabled ? 'Controlled-parallel' : 'Serial') + ' orchestration executed ' + childRun.children.length + ' child task(s); parent status ' + (updated ? verified.parentStatus : 'blocked') + '.'
933
+ });
934
+ }
935
+
936
+ async function runSerialOrchestrationInner(rawInput) {
937
+ const sourceInput = rawInput || {};
938
+ const input = {
939
+ ...sourceInput,
940
+ context: cloneData(sourceInput.context || {}),
941
+ moduleResults: cloneData(sourceInput.moduleResults || {}),
942
+ limits: cloneData(sourceInput.limits || {}),
943
+ adapters: sourceInput.adapters || {},
944
+ engine: sourceInput.engine,
945
+ consolidateResults: sourceInput.consolidateResults,
946
+ verifyConsolidatedResult: sourceInput.verifyConsolidatedResult,
947
+ concurrency: cloneData(sourceInput.concurrency)
948
+ };
949
+ const taskId = normalizeTaskId(input.taskId);
950
+ const mode = normalizeMode(input.mode);
951
+ let task;
952
+ try {
953
+ task = await getTaskById(taskId);
954
+ } catch (error) {
955
+ const reason = 'failed to read task #' + taskId + ': ' + ((error && error.message) || String(error));
956
+ return buildResult({ taskId, mode, errors: [reason], summary: reason });
957
+ }
958
+
959
+ if (!task) {
960
+ return buildResult({
961
+ taskId,
962
+ mode,
963
+ errors: ['Task #' + taskId + ' not found.'],
964
+ summary: 'Task #' + taskId + ' not found.'
965
+ });
966
+ }
967
+
968
+ let plan;
969
+ try {
970
+ plan = await planSerialOrchestration({ task, mode, force: input.force === true, rawInput: input });
971
+ } catch (error) {
972
+ const reason = 'planning failed: ' + ((error && error.message) || String(error));
973
+ return buildResult({
974
+ taskId,
975
+ mode,
976
+ errors: [reason],
977
+ summary: reason
978
+ });
979
+ }
980
+
981
+ if (mode !== 'real') {
982
+ return buildResult({
983
+ taskId,
984
+ mode: 'dry-run',
985
+ executed: false,
986
+ plan,
987
+ executionPlan: dryRunExecutionPlan(plan, input.concurrency),
988
+ trace: plan.trace || [],
989
+ warnings: plan.warnings || [],
990
+ errors: plan.errors || [],
991
+ summary: 'Dry-run orchestration completed without side effects.'
992
+ });
993
+ }
994
+
995
+ return executeReal(input, task, plan);
996
+ }
997
+
998
+ export async function runSerialOrchestration(input) {
999
+ const frozenInput = input || {};
1000
+ const taskId = normalizeTaskId(frozenInput.taskId);
1001
+ const mode = normalizeMode(frozenInput.mode);
1002
+ if (mode !== 'real') {
1003
+ return runSerialOrchestrationInner(frozenInput);
1004
+ }
1005
+ return runSerialized(taskId, () => runSerialOrchestrationInner(frozenInput));
1006
+ }
1007
+
1008
+ export async function resumeSerialOrchestration(input) {
1009
+ const taskId = normalizeTaskId(input && input.taskId);
1010
+ const task = await getTaskById(taskId);
1011
+ if (!task) {
1012
+ return { resumed: false, reason: 'Task #' + redactCredentials(taskId) + ' not found.' };
1013
+ }
1014
+ const existingChildren = (await getTasks()).filter(item => String(item.parentTaskId) === String(taskId));
1015
+ if (existingChildren.length === 0 && TERMINAL_STATUSES.has(task.status)) {
1016
+ return { resumed: false, reason: 'No serial orchestration to resume for terminal task #' + redactCredentials(taskId) + '.' };
1017
+ }
1018
+ return runSerialOrchestration({ ...input, taskId, mode: 'real', suppressOutput: true });
1019
+ }