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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +15 -0
- package/README.md +727 -0
- package/bin/maestro.mjs +246 -0
- package/package.json +29 -0
- package/src/checkpoint.mjs +102 -0
- package/src/codex-diagnostics.mjs +682 -0
- package/src/codex-home-inspect.mjs +252 -0
- package/src/codex-home.mjs +258 -0
- package/src/commands.mjs +809 -0
- package/src/config.mjs +11 -0
- package/src/debug.mjs +164 -0
- package/src/encoding-guard.mjs +125 -0
- package/src/engines/ai-router-engine.mjs +37 -0
- package/src/engines/codex-engine.mjs +21 -0
- package/src/engines/engine.mjs +21 -0
- package/src/engines/registry.mjs +29 -0
- package/src/files.mjs +65 -0
- package/src/format.mjs +132 -0
- package/src/lock.mjs +439 -0
- package/src/memory-log.mjs +18 -0
- package/src/memory.mjs +1 -0
- package/src/orchestration/attempt-chain-runtime.mjs +627 -0
- package/src/orchestration/budget-manager.mjs +121 -0
- package/src/orchestration/capability-registry.mjs +108 -0
- package/src/orchestration/consolidator.mjs +772 -0
- package/src/orchestration/delegation-executor.mjs +262 -0
- package/src/orchestration/delegation-manager.mjs +116 -0
- package/src/orchestration/deployment-intent.mjs +36 -0
- package/src/orchestration/engine-history.mjs +110 -0
- package/src/orchestration/engine-policy.mjs +73 -0
- package/src/orchestration/engine-selector.mjs +187 -0
- package/src/orchestration/execution-context.mjs +45 -0
- package/src/orchestration/failure-classifier.mjs +136 -0
- package/src/orchestration/failure-evidence.mjs +237 -0
- package/src/orchestration/fallback-chain-lock.mjs +217 -0
- package/src/orchestration/fallback-chain-trail.mjs +218 -0
- package/src/orchestration/fallback-executor.mjs +146 -0
- package/src/orchestration/fallback-graph.mjs +106 -0
- package/src/orchestration/fallback-recommendation.mjs +73 -0
- package/src/orchestration/file-conflict-detector.mjs +126 -0
- package/src/orchestration/host-validation.mjs +241 -0
- package/src/orchestration/orchestration-loop.mjs +1971 -0
- package/src/orchestration/orchestration-runtime.mjs +1019 -0
- package/src/orchestration/orchestration-scheduler.mjs +135 -0
- package/src/orchestration/orchestration-trail.mjs +192 -0
- package/src/orchestration/preflight.mjs +212 -0
- package/src/orchestration/provider-health.mjs +566 -0
- package/src/orchestration/provider-router.mjs +352 -0
- package/src/orchestration/rc-check-adapters.mjs +817 -0
- package/src/orchestration/rc-check.mjs +544 -0
- package/src/orchestration/rc-policy.mjs +200 -0
- package/src/orchestration/runtime-gate.mjs +146 -0
- package/src/orchestration/sector-managers.mjs +215 -0
- package/src/orchestration/self-hosting-canary.mjs +231 -0
- package/src/orchestration/self-hosting-readiness.mjs +244 -0
- package/src/orchestration/spec-planner.mjs +877 -0
- package/src/orchestration/subtask-planner.mjs +176 -0
- package/src/orchestration/task-classifier.mjs +241 -0
- package/src/orchestration/verifier.mjs +1608 -0
- package/src/orchestration-commands.mjs +279 -0
- package/src/planner.mjs +38 -0
- package/src/project.mjs +1 -0
- package/src/run-diagnostics.mjs +641 -0
- package/src/runner.mjs +243 -0
- package/src/safety.mjs +116 -0
- package/src/schema.mjs +371 -0
- package/src/session-commands.mjs +521 -0
- package/src/shell.mjs +93 -0
- package/src/smart-planner.mjs +249 -0
- package/src/task-commands.mjs +1182 -0
- package/src/tasks.mjs +134 -0
- package/src/ui/commands.mjs +76 -0
- package/src/ui/events.mjs +45 -0
- package/src/ui/public/app.js +600 -0
- package/src/ui/public/index.html +88 -0
- package/src/ui/public/style.css +460 -0
- package/src/ui/server.mjs +112 -0
- package/src/ui/state.mjs +504 -0
- package/src/usage.mjs +178 -0
- package/src/workspace-diff.mjs +228 -0
|
@@ -0,0 +1,1971 @@
|
|
|
1
|
+
const SCHEMA_VERSION = 1;
|
|
2
|
+
|
|
3
|
+
const LOOP_STATES = new Set([
|
|
4
|
+
'loop.initialized',
|
|
5
|
+
'loop.input_validated',
|
|
6
|
+
'loop.classified',
|
|
7
|
+
'loop.manager_selected',
|
|
8
|
+
'loop.engine_candidate_selected',
|
|
9
|
+
'loop.preflight_evaluated',
|
|
10
|
+
'loop.budget_evaluated',
|
|
11
|
+
'loop.policy_evaluated',
|
|
12
|
+
'loop.runtime_gate_evaluated',
|
|
13
|
+
'loop.execution_planned',
|
|
14
|
+
'loop.delegation_planned',
|
|
15
|
+
'loop.subtasks_planned',
|
|
16
|
+
'loop.conflicts_evaluated',
|
|
17
|
+
'loop.execution_simulated',
|
|
18
|
+
'loop.results_consolidated',
|
|
19
|
+
'loop.result_verified',
|
|
20
|
+
'loop.completed',
|
|
21
|
+
'loop.failed',
|
|
22
|
+
'loop.blocked',
|
|
23
|
+
'loop.needs_human',
|
|
24
|
+
'loop.cancelled_limit',
|
|
25
|
+
'loop.cancelled_cycle',
|
|
26
|
+
'loop.cancelled_invalid_transition',
|
|
27
|
+
'loop.cancelled_invalid_input'
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const TERMINAL_STATES = new Set([
|
|
31
|
+
'loop.completed',
|
|
32
|
+
'loop.failed',
|
|
33
|
+
'loop.blocked',
|
|
34
|
+
'loop.needs_human',
|
|
35
|
+
'loop.cancelled_limit',
|
|
36
|
+
'loop.cancelled_cycle',
|
|
37
|
+
'loop.cancelled_invalid_transition',
|
|
38
|
+
'loop.cancelled_invalid_input'
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const EVENTS = new Set([
|
|
42
|
+
'START',
|
|
43
|
+
'INPUT_VALID',
|
|
44
|
+
'INPUT_INVALID',
|
|
45
|
+
'CLASSIFICATION_READY',
|
|
46
|
+
'CLASSIFICATION_INVALID',
|
|
47
|
+
'MANAGER_READY',
|
|
48
|
+
'MANAGER_INVALID',
|
|
49
|
+
'ENGINE_CANDIDATE_READY',
|
|
50
|
+
'ENGINE_CANDIDATE_INVALID',
|
|
51
|
+
'PREFLIGHT_READY',
|
|
52
|
+
'PREFLIGHT_INVALID',
|
|
53
|
+
'BUDGET_READY',
|
|
54
|
+
'BUDGET_INVALID',
|
|
55
|
+
'POLICY_READY',
|
|
56
|
+
'POLICY_INVALID',
|
|
57
|
+
'RUNTIME_GATE_READY',
|
|
58
|
+
'GATE_EXECUTE',
|
|
59
|
+
'GATE_BLOCK',
|
|
60
|
+
'GATE_NEEDS_HUMAN',
|
|
61
|
+
'GATE_DELEGATE',
|
|
62
|
+
'GATE_FALLBACK',
|
|
63
|
+
'GATE_UNKNOWN',
|
|
64
|
+
'PLAN_READY',
|
|
65
|
+
'DELEGATION_READY',
|
|
66
|
+
'DELEGATION_INVALID',
|
|
67
|
+
'SUBTASKS_READY',
|
|
68
|
+
'SUBTASKS_INVALID',
|
|
69
|
+
'CONFLICTS_CLEAR',
|
|
70
|
+
'CONFLICTS_FOUND',
|
|
71
|
+
'CONFLICTS_BLOCKING',
|
|
72
|
+
'CONFLICTS_INVALID',
|
|
73
|
+
'SIMULATION_READY',
|
|
74
|
+
'CONSOLIDATION_READY',
|
|
75
|
+
'CONSOLIDATION_SKIPPED',
|
|
76
|
+
'CONSOLIDATION_INVALID',
|
|
77
|
+
'VERIFICATION_READY',
|
|
78
|
+
'VERIFICATION_SKIPPED',
|
|
79
|
+
'VERIFICATION_INVALID',
|
|
80
|
+
'LIMIT_EXCEEDED',
|
|
81
|
+
'CYCLE_DETECTED',
|
|
82
|
+
'INVALID_TRANSITION',
|
|
83
|
+
'TERMINAL_STATE'
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const TRANSITIONS = {
|
|
87
|
+
'loop.initialized': {
|
|
88
|
+
INPUT_VALID: 'loop.input_validated',
|
|
89
|
+
INPUT_INVALID: 'loop.cancelled_invalid_input'
|
|
90
|
+
},
|
|
91
|
+
'loop.input_validated': {
|
|
92
|
+
CLASSIFICATION_READY: 'loop.classified',
|
|
93
|
+
CLASSIFICATION_INVALID: 'loop.failed'
|
|
94
|
+
},
|
|
95
|
+
'loop.classified': {
|
|
96
|
+
MANAGER_READY: 'loop.manager_selected',
|
|
97
|
+
MANAGER_INVALID: 'loop.failed'
|
|
98
|
+
},
|
|
99
|
+
'loop.manager_selected': {
|
|
100
|
+
ENGINE_CANDIDATE_READY: 'loop.engine_candidate_selected',
|
|
101
|
+
ENGINE_CANDIDATE_INVALID: 'loop.failed'
|
|
102
|
+
},
|
|
103
|
+
'loop.engine_candidate_selected': {
|
|
104
|
+
PREFLIGHT_READY: 'loop.preflight_evaluated',
|
|
105
|
+
PREFLIGHT_INVALID: 'loop.failed'
|
|
106
|
+
},
|
|
107
|
+
'loop.preflight_evaluated': {
|
|
108
|
+
BUDGET_READY: 'loop.budget_evaluated',
|
|
109
|
+
BUDGET_INVALID: 'loop.failed'
|
|
110
|
+
},
|
|
111
|
+
'loop.budget_evaluated': {
|
|
112
|
+
POLICY_READY: 'loop.policy_evaluated',
|
|
113
|
+
POLICY_INVALID: 'loop.failed'
|
|
114
|
+
},
|
|
115
|
+
'loop.policy_evaluated': {
|
|
116
|
+
RUNTIME_GATE_READY: 'loop.runtime_gate_evaluated',
|
|
117
|
+
GATE_UNKNOWN: 'loop.failed'
|
|
118
|
+
},
|
|
119
|
+
'loop.runtime_gate_evaluated': {
|
|
120
|
+
GATE_EXECUTE: 'loop.execution_planned',
|
|
121
|
+
GATE_DELEGATE: 'loop.execution_planned',
|
|
122
|
+
GATE_BLOCK: 'loop.blocked',
|
|
123
|
+
GATE_NEEDS_HUMAN: 'loop.needs_human',
|
|
124
|
+
GATE_FALLBACK: 'loop.blocked'
|
|
125
|
+
},
|
|
126
|
+
'loop.execution_planned': {
|
|
127
|
+
PLAN_READY: 'loop.conflicts_evaluated',
|
|
128
|
+
DELEGATION_READY: 'loop.delegation_planned',
|
|
129
|
+
DELEGATION_INVALID: 'loop.failed'
|
|
130
|
+
},
|
|
131
|
+
'loop.delegation_planned': {
|
|
132
|
+
SUBTASKS_READY: 'loop.subtasks_planned',
|
|
133
|
+
SUBTASKS_INVALID: 'loop.failed'
|
|
134
|
+
},
|
|
135
|
+
'loop.subtasks_planned': {
|
|
136
|
+
CONFLICTS_CLEAR: 'loop.conflicts_evaluated',
|
|
137
|
+
CONFLICTS_FOUND: 'loop.conflicts_evaluated',
|
|
138
|
+
CONFLICTS_BLOCKING: 'loop.blocked',
|
|
139
|
+
CONFLICTS_INVALID: 'loop.failed'
|
|
140
|
+
},
|
|
141
|
+
'loop.conflicts_evaluated': {
|
|
142
|
+
SIMULATION_READY: 'loop.execution_simulated'
|
|
143
|
+
},
|
|
144
|
+
'loop.execution_simulated': {
|
|
145
|
+
CONSOLIDATION_READY: 'loop.results_consolidated',
|
|
146
|
+
CONSOLIDATION_SKIPPED: 'loop.results_consolidated',
|
|
147
|
+
CONSOLIDATION_INVALID: 'loop.failed'
|
|
148
|
+
},
|
|
149
|
+
'loop.results_consolidated': {
|
|
150
|
+
VERIFICATION_READY: 'loop.result_verified',
|
|
151
|
+
VERIFICATION_SKIPPED: 'loop.result_verified',
|
|
152
|
+
VERIFICATION_INVALID: 'loop.failed'
|
|
153
|
+
},
|
|
154
|
+
'loop.result_verified': {
|
|
155
|
+
PLAN_READY: 'loop.completed'
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const GATE_DECISIONS = new Set(['EXECUTE', 'BLOCK', 'NEEDS_HUMAN', 'DELEGATE', 'FALLBACK']);
|
|
160
|
+
const ADAPTER_NAMES = new Set([
|
|
161
|
+
'classifyTask',
|
|
162
|
+
'selectManager',
|
|
163
|
+
'selectEngineCandidate',
|
|
164
|
+
'evaluatePreflight',
|
|
165
|
+
'evaluateBudget',
|
|
166
|
+
'evaluatePolicy',
|
|
167
|
+
'evaluateRuntimeGate',
|
|
168
|
+
'planDelegation',
|
|
169
|
+
'planSubtasks',
|
|
170
|
+
'detectConflicts',
|
|
171
|
+
'consolidate',
|
|
172
|
+
'verify'
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
const GLOBAL_LIMITS = Object.freeze({
|
|
176
|
+
maxTransitions: 40,
|
|
177
|
+
maxAttempts: 3,
|
|
178
|
+
maxDepth: 2,
|
|
179
|
+
maxSubtasks: 8,
|
|
180
|
+
maxAgentsPerManager: 4
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const DEFAULT_DECISIONS = Object.freeze({
|
|
184
|
+
classification: null,
|
|
185
|
+
manager: null,
|
|
186
|
+
engineCandidate: null,
|
|
187
|
+
preflight: null,
|
|
188
|
+
budget: null,
|
|
189
|
+
policy: null,
|
|
190
|
+
runtimeGate: null
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const SENSITIVE_KEYS = ['token', 'secret', 'password', 'authorization', 'cookie', 'apikey', 'env'];
|
|
194
|
+
|
|
195
|
+
export function planOrchestrationStep(input) {
|
|
196
|
+
assertPlainApiObject(input, 'input');
|
|
197
|
+
const {
|
|
198
|
+
state,
|
|
199
|
+
signal = null,
|
|
200
|
+
task,
|
|
201
|
+
context = {},
|
|
202
|
+
limits = {},
|
|
203
|
+
counters = {},
|
|
204
|
+
decisions = {},
|
|
205
|
+
moduleResults = {},
|
|
206
|
+
adapters = {},
|
|
207
|
+
previousTrace = [],
|
|
208
|
+
seenCycleSignatures = []
|
|
209
|
+
} = input;
|
|
210
|
+
|
|
211
|
+
if (typeof state !== 'string') {
|
|
212
|
+
throw new TypeError('state must be a string');
|
|
213
|
+
}
|
|
214
|
+
assertPlainApiObject(context, 'context');
|
|
215
|
+
assertPlainApiObject(limits, 'limits');
|
|
216
|
+
assertPlainApiObject(counters, 'counters');
|
|
217
|
+
assertPlainApiObject(decisions, 'decisions');
|
|
218
|
+
assertPlainApiObject(moduleResults, 'moduleResults');
|
|
219
|
+
assertPlainApiObject(adapters, 'adapters');
|
|
220
|
+
if (!Array.isArray(previousTrace)) {
|
|
221
|
+
throw new TypeError('previousTrace must be an array');
|
|
222
|
+
}
|
|
223
|
+
if (!Array.isArray(seenCycleSignatures)) {
|
|
224
|
+
throw new TypeError('seenCycleSignatures must be an array');
|
|
225
|
+
}
|
|
226
|
+
for (const [name, value] of Object.entries(adapters)) {
|
|
227
|
+
if (ADAPTER_NAMES.has(name) && typeof value !== 'function') {
|
|
228
|
+
throw new TypeError(`adapter ${name} must be a function`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (LOOP_STATES.has(state) && state !== 'loop.initialized' && !TERMINAL_STATES.has(state) && !isValidTask(task)) {
|
|
232
|
+
throw new TypeError('task must be an object with id for non-terminal states');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const normalizedLimits = normalizeLimits(limits);
|
|
236
|
+
const normalizedContext = cloneData(context);
|
|
237
|
+
const normalizedCounters = normalizeCounters(counters, normalizedContext);
|
|
238
|
+
const currentDecisions = normalizeDecisions(decisions);
|
|
239
|
+
const currentModules = isPlainObject(moduleResults) ? cloneData(moduleResults) : {};
|
|
240
|
+
const guards = [];
|
|
241
|
+
const warnings = [...normalizedLimits.warnings];
|
|
242
|
+
const errors = [];
|
|
243
|
+
const inconsistencies = [];
|
|
244
|
+
let nextContext = normalizedContext;
|
|
245
|
+
let nextDecisions = currentDecisions;
|
|
246
|
+
let nextModules = currentModules;
|
|
247
|
+
let nextCounters = normalizedCounters;
|
|
248
|
+
let event = signal;
|
|
249
|
+
let nextState = state;
|
|
250
|
+
let reason = '';
|
|
251
|
+
let terminalReason = null;
|
|
252
|
+
let provenance = [];
|
|
253
|
+
let conflicts = extractConflicts(nextModules, nextContext);
|
|
254
|
+
|
|
255
|
+
if (TERMINAL_STATES.has(state)) {
|
|
256
|
+
event = 'TERMINAL_STATE';
|
|
257
|
+
nextState = state;
|
|
258
|
+
reason = 'terminal state does not advance';
|
|
259
|
+
terminalReason = state;
|
|
260
|
+
guards.push('terminal-state');
|
|
261
|
+
return buildStepResult({
|
|
262
|
+
state,
|
|
263
|
+
event,
|
|
264
|
+
nextState,
|
|
265
|
+
reason,
|
|
266
|
+
terminalReason,
|
|
267
|
+
guards,
|
|
268
|
+
decisions: nextDecisions,
|
|
269
|
+
moduleResults: nextModules,
|
|
270
|
+
context: nextContext,
|
|
271
|
+
counters: incrementTransitions(normalizedCounters),
|
|
272
|
+
limits: normalizedLimits.limits,
|
|
273
|
+
previousTrace,
|
|
274
|
+
warnings,
|
|
275
|
+
errors,
|
|
276
|
+
inconsistencies,
|
|
277
|
+
conflicts,
|
|
278
|
+
provenance
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (normalizedCounters.transitions >= normalizedLimits.limits.maxTransitions ||
|
|
283
|
+
normalizedCounters.attempts > normalizedLimits.limits.maxAttempts ||
|
|
284
|
+
normalizedCounters.depth > normalizedLimits.limits.maxDepth) {
|
|
285
|
+
event = 'LIMIT_EXCEEDED';
|
|
286
|
+
nextState = 'loop.cancelled_limit';
|
|
287
|
+
reason = 'orchestration loop limit exceeded';
|
|
288
|
+
terminalReason = 'limit_exceeded';
|
|
289
|
+
guards.push('limit');
|
|
290
|
+
errors.push(loopError('LIMIT_EXCEEDED', reason, state, event, 'limits', 'blocking'));
|
|
291
|
+
return buildStepResult({
|
|
292
|
+
state,
|
|
293
|
+
event,
|
|
294
|
+
nextState,
|
|
295
|
+
reason,
|
|
296
|
+
terminalReason,
|
|
297
|
+
guards,
|
|
298
|
+
decisions: nextDecisions,
|
|
299
|
+
moduleResults: nextModules,
|
|
300
|
+
context: nextContext,
|
|
301
|
+
counters: incrementTransitions(normalizedCounters),
|
|
302
|
+
limits: normalizedLimits.limits,
|
|
303
|
+
previousTrace,
|
|
304
|
+
warnings,
|
|
305
|
+
errors,
|
|
306
|
+
inconsistencies,
|
|
307
|
+
conflicts,
|
|
308
|
+
provenance
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!LOOP_STATES.has(state)) {
|
|
313
|
+
event = signal === null ? 'INVALID_TRANSITION' : String(signal);
|
|
314
|
+
nextState = 'loop.cancelled_invalid_transition';
|
|
315
|
+
reason = `unknown loop state: ${state}`;
|
|
316
|
+
terminalReason = 'invalid_transition';
|
|
317
|
+
guards.push('known-state');
|
|
318
|
+
errors.push(loopError('UNKNOWN_STATE', reason, state, event, 'state', 'error'));
|
|
319
|
+
return buildStepResult({
|
|
320
|
+
state,
|
|
321
|
+
event,
|
|
322
|
+
nextState,
|
|
323
|
+
reason,
|
|
324
|
+
terminalReason,
|
|
325
|
+
guards,
|
|
326
|
+
decisions: nextDecisions,
|
|
327
|
+
moduleResults: nextModules,
|
|
328
|
+
context: nextContext,
|
|
329
|
+
counters: incrementTransitions(normalizedCounters),
|
|
330
|
+
limits: normalizedLimits.limits,
|
|
331
|
+
previousTrace,
|
|
332
|
+
warnings,
|
|
333
|
+
errors,
|
|
334
|
+
inconsistencies,
|
|
335
|
+
conflicts,
|
|
336
|
+
provenance
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (signal !== null && !EVENTS.has(signal)) {
|
|
341
|
+
event = String(signal);
|
|
342
|
+
nextState = 'loop.cancelled_invalid_transition';
|
|
343
|
+
reason = `unknown event: ${String(signal)}`;
|
|
344
|
+
terminalReason = 'invalid_transition';
|
|
345
|
+
guards.push('known-event');
|
|
346
|
+
errors.push(loopError('UNKNOWN_EVENT', reason, state, event, 'signal', 'error'));
|
|
347
|
+
return buildStepResult({
|
|
348
|
+
state,
|
|
349
|
+
event,
|
|
350
|
+
nextState,
|
|
351
|
+
reason,
|
|
352
|
+
terminalReason,
|
|
353
|
+
guards,
|
|
354
|
+
decisions: nextDecisions,
|
|
355
|
+
moduleResults: nextModules,
|
|
356
|
+
context: nextContext,
|
|
357
|
+
counters: incrementTransitions(normalizedCounters),
|
|
358
|
+
limits: normalizedLimits.limits,
|
|
359
|
+
previousTrace,
|
|
360
|
+
warnings,
|
|
361
|
+
errors,
|
|
362
|
+
inconsistencies,
|
|
363
|
+
conflicts,
|
|
364
|
+
provenance
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (signal !== null && !isTransitionAllowed(state, signal)) {
|
|
369
|
+
event = signal;
|
|
370
|
+
nextState = 'loop.cancelled_invalid_transition';
|
|
371
|
+
reason = `invalid transition from ${state} using ${signal}`;
|
|
372
|
+
terminalReason = 'invalid_transition';
|
|
373
|
+
guards.push('valid-transition');
|
|
374
|
+
errors.push(loopError('INVALID_TRANSITION', reason, state, signal, 'signal', 'error'));
|
|
375
|
+
return buildStepResult({
|
|
376
|
+
state,
|
|
377
|
+
event,
|
|
378
|
+
nextState,
|
|
379
|
+
reason,
|
|
380
|
+
terminalReason,
|
|
381
|
+
guards,
|
|
382
|
+
decisions: nextDecisions,
|
|
383
|
+
moduleResults: nextModules,
|
|
384
|
+
context: nextContext,
|
|
385
|
+
counters: incrementTransitions(normalizedCounters),
|
|
386
|
+
limits: normalizedLimits.limits,
|
|
387
|
+
previousTrace,
|
|
388
|
+
warnings,
|
|
389
|
+
errors,
|
|
390
|
+
inconsistencies,
|
|
391
|
+
conflicts,
|
|
392
|
+
provenance
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const derived = deriveTransition({
|
|
397
|
+
state,
|
|
398
|
+
task,
|
|
399
|
+
context: nextContext,
|
|
400
|
+
limits: normalizedLimits.limits,
|
|
401
|
+
counters: normalizedCounters,
|
|
402
|
+
decisions: nextDecisions,
|
|
403
|
+
moduleResults: nextModules,
|
|
404
|
+
adapters
|
|
405
|
+
});
|
|
406
|
+
if (derived.warnings.length > 0) warnings.push(...derived.warnings);
|
|
407
|
+
if (derived.errors.length > 0) errors.push(...derived.errors);
|
|
408
|
+
if (derived.inconsistencies.length > 0) inconsistencies.push(...derived.inconsistencies);
|
|
409
|
+
nextContext = derived.context;
|
|
410
|
+
nextDecisions = derived.decisions;
|
|
411
|
+
nextModules = derived.moduleResults;
|
|
412
|
+
nextCounters = derived.counters || normalizedCounters;
|
|
413
|
+
provenance = derived.provenance;
|
|
414
|
+
conflicts = derived.conflicts;
|
|
415
|
+
|
|
416
|
+
event = signal === null ? derived.event : signal;
|
|
417
|
+
if (!isTransitionAllowed(state, event)) {
|
|
418
|
+
const received = event;
|
|
419
|
+
event = received;
|
|
420
|
+
nextState = 'loop.cancelled_invalid_transition';
|
|
421
|
+
reason = `invalid transition from ${state} using ${received}`;
|
|
422
|
+
terminalReason = 'invalid_transition';
|
|
423
|
+
guards.push('valid-transition');
|
|
424
|
+
errors.push(loopError('INVALID_TRANSITION', reason, state, received, 'signal', 'error'));
|
|
425
|
+
} else {
|
|
426
|
+
nextState = TRANSITIONS[state][event];
|
|
427
|
+
reason = derived.reason;
|
|
428
|
+
terminalReason = TERMINAL_STATES.has(nextState) ? reasonToTerminal(nextState, derived) : null;
|
|
429
|
+
guards.push(...derived.guards);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const candidateSignature = createCycleSignature({
|
|
433
|
+
state,
|
|
434
|
+
event,
|
|
435
|
+
task,
|
|
436
|
+
context: nextContext,
|
|
437
|
+
counters: nextCounters,
|
|
438
|
+
decisions: nextDecisions,
|
|
439
|
+
moduleResults: nextModules,
|
|
440
|
+
conflicts
|
|
441
|
+
});
|
|
442
|
+
if (seenCycleSignatures.includes(candidateSignature) && nextState !== 'loop.cancelled_invalid_transition') {
|
|
443
|
+
event = 'CYCLE_DETECTED';
|
|
444
|
+
nextState = 'loop.cancelled_cycle';
|
|
445
|
+
reason = 'cycle signature was seen previously in this loop';
|
|
446
|
+
terminalReason = 'cycle_detected';
|
|
447
|
+
guards.push('cycle');
|
|
448
|
+
errors.push(loopError('CYCLE_DETECTED', reason, state, event, 'cycleSignature', 'blocking'));
|
|
449
|
+
nextContext = {
|
|
450
|
+
...nextContext,
|
|
451
|
+
cycleInfo: {
|
|
452
|
+
signature: candidateSignature,
|
|
453
|
+
state,
|
|
454
|
+
event
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return buildStepResult({
|
|
460
|
+
state,
|
|
461
|
+
event,
|
|
462
|
+
nextState,
|
|
463
|
+
reason,
|
|
464
|
+
terminalReason,
|
|
465
|
+
guards,
|
|
466
|
+
decisions: nextDecisions,
|
|
467
|
+
moduleResults: nextModules,
|
|
468
|
+
context: nextContext,
|
|
469
|
+
counters: incrementTransitions(nextCounters),
|
|
470
|
+
limits: normalizedLimits.limits,
|
|
471
|
+
previousTrace,
|
|
472
|
+
warnings: uniqueMessages(warnings),
|
|
473
|
+
errors,
|
|
474
|
+
inconsistencies,
|
|
475
|
+
conflicts,
|
|
476
|
+
provenance,
|
|
477
|
+
forcedCycleSignature: candidateSignature
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function runOrchestrationLoop(input) {
|
|
482
|
+
assertPlainApiObject(input, 'input');
|
|
483
|
+
const {
|
|
484
|
+
task,
|
|
485
|
+
context = {},
|
|
486
|
+
limits = {},
|
|
487
|
+
adapters = {},
|
|
488
|
+
moduleResults = {},
|
|
489
|
+
initialState = 'loop.initialized',
|
|
490
|
+
loopId = null
|
|
491
|
+
} = input;
|
|
492
|
+
|
|
493
|
+
if (typeof initialState !== 'string') {
|
|
494
|
+
throw new TypeError('initialState must be a string');
|
|
495
|
+
}
|
|
496
|
+
if (loopId !== null && (typeof loopId !== 'string' || loopId.trim().length === 0)) {
|
|
497
|
+
throw new TypeError('loopId must be a non-empty string when provided');
|
|
498
|
+
}
|
|
499
|
+
assertPlainApiObject(context, 'context');
|
|
500
|
+
assertPlainApiObject(limits, 'limits');
|
|
501
|
+
assertPlainApiObject(adapters, 'adapters');
|
|
502
|
+
assertPlainApiObject(moduleResults, 'moduleResults');
|
|
503
|
+
|
|
504
|
+
let state = initialState;
|
|
505
|
+
let trace = [];
|
|
506
|
+
let decisions = normalizeDecisions({});
|
|
507
|
+
let modules = cloneData(moduleResults);
|
|
508
|
+
let currentContext = cloneData(context);
|
|
509
|
+
let counters = normalizeCounters({}, currentContext);
|
|
510
|
+
let finalStep = null;
|
|
511
|
+
let seen = [];
|
|
512
|
+
const normalizedLimits = normalizeLimits(limits);
|
|
513
|
+
|
|
514
|
+
while (true) {
|
|
515
|
+
const step = planOrchestrationStep({
|
|
516
|
+
state,
|
|
517
|
+
task,
|
|
518
|
+
context: currentContext,
|
|
519
|
+
limits,
|
|
520
|
+
counters,
|
|
521
|
+
decisions,
|
|
522
|
+
moduleResults: modules,
|
|
523
|
+
adapters,
|
|
524
|
+
previousTrace: trace,
|
|
525
|
+
seenCycleSignatures: seen
|
|
526
|
+
});
|
|
527
|
+
finalStep = step;
|
|
528
|
+
trace = [...trace, cloneData(step.traceEntry)];
|
|
529
|
+
decisions = cloneData(step.decisions);
|
|
530
|
+
modules = cloneData(step.moduleResults);
|
|
531
|
+
currentContext = cloneData(step.context);
|
|
532
|
+
counters = cloneData(step.counters);
|
|
533
|
+
if (step.event !== 'CYCLE_DETECTED') {
|
|
534
|
+
seen = [...seen, step.cycleSignature];
|
|
535
|
+
}
|
|
536
|
+
state = step.nextState;
|
|
537
|
+
if (step.terminal) {
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
if (counters.transitions > normalizedLimits.limits.maxTransitions + 1) {
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return buildLoopResult({
|
|
546
|
+
task,
|
|
547
|
+
context: currentContext,
|
|
548
|
+
initialState,
|
|
549
|
+
loopId,
|
|
550
|
+
trace,
|
|
551
|
+
finalStep,
|
|
552
|
+
decisions,
|
|
553
|
+
moduleResults: modules,
|
|
554
|
+
counters,
|
|
555
|
+
limits: normalizedLimits.limits
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function deriveTransition(input) {
|
|
560
|
+
const base = {
|
|
561
|
+
event: 'INVALID_TRANSITION',
|
|
562
|
+
reason: 'no transition derived',
|
|
563
|
+
guards: [],
|
|
564
|
+
decisions: cloneData(input.decisions),
|
|
565
|
+
moduleResults: cloneData(input.moduleResults),
|
|
566
|
+
context: cloneData(input.context),
|
|
567
|
+
counters: cloneData(input.counters),
|
|
568
|
+
warnings: [],
|
|
569
|
+
errors: [],
|
|
570
|
+
inconsistencies: [],
|
|
571
|
+
conflicts: extractConflicts(input.moduleResults, input.context),
|
|
572
|
+
provenance: []
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const fail = (event, code, message, field, severity = 'error') => ({
|
|
576
|
+
...base,
|
|
577
|
+
event,
|
|
578
|
+
reason: message,
|
|
579
|
+
errors: [loopError(code, message, input.state, event, field, severity)]
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
switch (input.state) {
|
|
584
|
+
case 'loop.initialized':
|
|
585
|
+
if (!isValidTask(input.task)) {
|
|
586
|
+
return fail('INPUT_INVALID', 'INVALID_INPUT', 'task must be an object with id', 'task');
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
...base,
|
|
590
|
+
event: 'INPUT_VALID',
|
|
591
|
+
reason: 'input task shape is valid',
|
|
592
|
+
guards: ['task-shape'],
|
|
593
|
+
context: {
|
|
594
|
+
...base.context,
|
|
595
|
+
taskId: String(input.task.id),
|
|
596
|
+
parentTaskId: getParentTaskId(input.task, input.context)
|
|
597
|
+
},
|
|
598
|
+
provenance: [provenance('input', null, 'task', false)]
|
|
599
|
+
};
|
|
600
|
+
case 'loop.input_validated':
|
|
601
|
+
return deriveObjectDecision({
|
|
602
|
+
...input,
|
|
603
|
+
base,
|
|
604
|
+
eventReady: 'CLASSIFICATION_READY',
|
|
605
|
+
eventInvalid: 'CLASSIFICATION_INVALID',
|
|
606
|
+
decisionKey: 'classification',
|
|
607
|
+
moduleKey: 'classification',
|
|
608
|
+
contextKey: 'classification',
|
|
609
|
+
adapterName: 'classifyTask',
|
|
610
|
+
reasonReady: 'classification is available',
|
|
611
|
+
validate: isPlainObject
|
|
612
|
+
});
|
|
613
|
+
case 'loop.classified':
|
|
614
|
+
return deriveObjectDecision({
|
|
615
|
+
...input,
|
|
616
|
+
base,
|
|
617
|
+
eventReady: 'MANAGER_READY',
|
|
618
|
+
eventInvalid: 'MANAGER_INVALID',
|
|
619
|
+
decisionKey: 'manager',
|
|
620
|
+
moduleKey: 'manager',
|
|
621
|
+
contextKey: 'manager',
|
|
622
|
+
adapterName: 'selectManager',
|
|
623
|
+
reasonReady: 'manager plan is available',
|
|
624
|
+
validate: value => value === null || isPlainObject(value)
|
|
625
|
+
});
|
|
626
|
+
case 'loop.manager_selected':
|
|
627
|
+
return deriveObjectDecision({
|
|
628
|
+
...input,
|
|
629
|
+
base,
|
|
630
|
+
eventReady: 'ENGINE_CANDIDATE_READY',
|
|
631
|
+
eventInvalid: 'ENGINE_CANDIDATE_INVALID',
|
|
632
|
+
decisionKey: 'engineCandidate',
|
|
633
|
+
moduleKey: 'engineCandidate',
|
|
634
|
+
contextKey: 'engineCandidate',
|
|
635
|
+
adapterName: 'selectEngineCandidate',
|
|
636
|
+
reasonReady: 'engine candidate is available',
|
|
637
|
+
validate: value => value === null || isPlainObject(value)
|
|
638
|
+
});
|
|
639
|
+
case 'loop.engine_candidate_selected':
|
|
640
|
+
return deriveObjectDecision({
|
|
641
|
+
...input,
|
|
642
|
+
base,
|
|
643
|
+
eventReady: 'PREFLIGHT_READY',
|
|
644
|
+
eventInvalid: 'PREFLIGHT_INVALID',
|
|
645
|
+
decisionKey: 'preflight',
|
|
646
|
+
moduleKey: 'preflight',
|
|
647
|
+
contextKey: 'preflight',
|
|
648
|
+
adapterName: 'evaluatePreflight',
|
|
649
|
+
reasonReady: 'preflight result is available',
|
|
650
|
+
validate: isPlainObject
|
|
651
|
+
});
|
|
652
|
+
case 'loop.preflight_evaluated':
|
|
653
|
+
return deriveObjectDecision({
|
|
654
|
+
...input,
|
|
655
|
+
base,
|
|
656
|
+
eventReady: 'BUDGET_READY',
|
|
657
|
+
eventInvalid: 'BUDGET_INVALID',
|
|
658
|
+
decisionKey: 'budget',
|
|
659
|
+
moduleKey: 'budget',
|
|
660
|
+
contextKey: 'budget',
|
|
661
|
+
adapterName: 'evaluateBudget',
|
|
662
|
+
reasonReady: 'budget result is available',
|
|
663
|
+
validate: isPlainObject
|
|
664
|
+
});
|
|
665
|
+
case 'loop.budget_evaluated':
|
|
666
|
+
return deriveObjectDecision({
|
|
667
|
+
...input,
|
|
668
|
+
base,
|
|
669
|
+
eventReady: 'POLICY_READY',
|
|
670
|
+
eventInvalid: 'POLICY_INVALID',
|
|
671
|
+
decisionKey: 'policy',
|
|
672
|
+
moduleKey: 'policy',
|
|
673
|
+
contextKey: 'policy',
|
|
674
|
+
adapterName: 'evaluatePolicy',
|
|
675
|
+
reasonReady: 'policy result is available',
|
|
676
|
+
validate: isPlainObject
|
|
677
|
+
});
|
|
678
|
+
case 'loop.policy_evaluated':
|
|
679
|
+
return deriveRuntimeGate(input, base);
|
|
680
|
+
case 'loop.runtime_gate_evaluated':
|
|
681
|
+
return deriveGateBranch(input, base);
|
|
682
|
+
case 'loop.execution_planned':
|
|
683
|
+
return deriveExecutionPlan(input, base);
|
|
684
|
+
case 'loop.delegation_planned':
|
|
685
|
+
return deriveSubtasks(input, base);
|
|
686
|
+
case 'loop.subtasks_planned':
|
|
687
|
+
return deriveConflicts(input, base);
|
|
688
|
+
case 'loop.conflicts_evaluated':
|
|
689
|
+
return deriveSimulation(input, base);
|
|
690
|
+
case 'loop.execution_simulated':
|
|
691
|
+
return deriveConsolidation(input, base);
|
|
692
|
+
case 'loop.results_consolidated':
|
|
693
|
+
return deriveVerification(input, base);
|
|
694
|
+
case 'loop.result_verified':
|
|
695
|
+
return {
|
|
696
|
+
...base,
|
|
697
|
+
event: 'PLAN_READY',
|
|
698
|
+
reason: 'planning pipeline reached terminal completion',
|
|
699
|
+
guards: ['planning-complete']
|
|
700
|
+
};
|
|
701
|
+
default:
|
|
702
|
+
return fail('INVALID_TRANSITION', 'UNKNOWN_STATE', `unknown loop state: ${input.state}`, 'state');
|
|
703
|
+
}
|
|
704
|
+
} catch (error) {
|
|
705
|
+
return fail('INVALID_TRANSITION', 'DERIVE_TRANSITION_FAILED', errorMessage(error), null);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function deriveObjectDecision(config) {
|
|
710
|
+
const source = resolveProvidedOrAdapter(config);
|
|
711
|
+
if (source.error) {
|
|
712
|
+
return {
|
|
713
|
+
...config.base,
|
|
714
|
+
event: config.eventInvalid,
|
|
715
|
+
reason: source.error.message,
|
|
716
|
+
errors: [source.error]
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
if (!config.validate(source.value)) {
|
|
720
|
+
return {
|
|
721
|
+
...config.base,
|
|
722
|
+
event: config.eventInvalid,
|
|
723
|
+
reason: `${config.decisionKey} result is malformed`,
|
|
724
|
+
errors: [loopError('MALFORMED_RESULT', `${config.decisionKey} result is malformed`, config.state, config.eventInvalid, config.decisionKey, 'error')]
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
...config.base,
|
|
729
|
+
event: config.eventReady,
|
|
730
|
+
reason: config.reasonReady,
|
|
731
|
+
guards: [`${config.decisionKey}-valid`],
|
|
732
|
+
decisions: {
|
|
733
|
+
...config.base.decisions,
|
|
734
|
+
[config.decisionKey]: cloneData(source.value)
|
|
735
|
+
},
|
|
736
|
+
moduleResults: {
|
|
737
|
+
...config.base.moduleResults,
|
|
738
|
+
[config.moduleKey]: cloneData(source.value)
|
|
739
|
+
},
|
|
740
|
+
provenance: [provenance(source.provenance, source.adapterName, config.decisionKey, false)]
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function deriveRuntimeGate(input, base) {
|
|
745
|
+
const source = resolveProvidedOrAdapter({
|
|
746
|
+
...input,
|
|
747
|
+
base,
|
|
748
|
+
decisionKey: 'runtimeGate',
|
|
749
|
+
moduleKey: 'runtimeGate',
|
|
750
|
+
contextKey: 'runtimeGateResult',
|
|
751
|
+
adapterName: 'evaluateRuntimeGate'
|
|
752
|
+
});
|
|
753
|
+
if (source.error) {
|
|
754
|
+
return {
|
|
755
|
+
...base,
|
|
756
|
+
event: 'GATE_UNKNOWN',
|
|
757
|
+
reason: source.error.message,
|
|
758
|
+
errors: [source.error]
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
if (!isPlainObject(source.value) || !GATE_DECISIONS.has(source.value.decision)) {
|
|
762
|
+
return {
|
|
763
|
+
...base,
|
|
764
|
+
event: 'GATE_UNKNOWN',
|
|
765
|
+
reason: 'runtime gate decision is unknown or malformed',
|
|
766
|
+
errors: [loopError('UNKNOWN_RUNTIME_GATE_DECISION', 'runtime gate decision is unknown or malformed', input.state, 'GATE_UNKNOWN', 'runtimeGate.decision', 'error')]
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
...base,
|
|
771
|
+
event: 'RUNTIME_GATE_READY',
|
|
772
|
+
reason: 'runtime gate decision is available',
|
|
773
|
+
guards: ['runtime-gate-valid'],
|
|
774
|
+
decisions: {
|
|
775
|
+
...base.decisions,
|
|
776
|
+
runtimeGate: cloneData(source.value)
|
|
777
|
+
},
|
|
778
|
+
moduleResults: {
|
|
779
|
+
...base.moduleResults,
|
|
780
|
+
runtimeGate: cloneData(source.value)
|
|
781
|
+
},
|
|
782
|
+
provenance: [provenance(source.provenance, source.adapterName, 'runtimeGate', false)]
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function deriveGateBranch(input, base) {
|
|
787
|
+
const gate = input.decisions.runtimeGate || input.moduleResults.runtimeGate || input.context.runtimeGateResult;
|
|
788
|
+
const decision = isPlainObject(gate) ? gate.decision : null;
|
|
789
|
+
if (decision === 'EXECUTE') {
|
|
790
|
+
return {
|
|
791
|
+
...base,
|
|
792
|
+
event: 'GATE_EXECUTE',
|
|
793
|
+
reason: 'runtime gate permits simulated execution planning',
|
|
794
|
+
guards: ['gate-execute'],
|
|
795
|
+
moduleResults: {
|
|
796
|
+
...base.moduleResults,
|
|
797
|
+
executionPlan: directExecutionPlan(input.task)
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
if (decision === 'DELEGATE') {
|
|
802
|
+
return {
|
|
803
|
+
...base,
|
|
804
|
+
event: 'GATE_DELEGATE',
|
|
805
|
+
reason: 'runtime gate requires delegation planning only',
|
|
806
|
+
guards: ['gate-delegate'],
|
|
807
|
+
moduleResults: {
|
|
808
|
+
...base.moduleResults,
|
|
809
|
+
executionPlan: {
|
|
810
|
+
mode: 'delegate',
|
|
811
|
+
executionMode: 'serial',
|
|
812
|
+
order: [String(input.task.id)]
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
if (decision === 'BLOCK') {
|
|
818
|
+
return { ...base, event: 'GATE_BLOCK', reason: gateReason(gate, 'runtime gate blocked planning'), guards: ['gate-block'] };
|
|
819
|
+
}
|
|
820
|
+
if (decision === 'NEEDS_HUMAN') {
|
|
821
|
+
return { ...base, event: 'GATE_NEEDS_HUMAN', reason: gateReason(gate, 'runtime gate needs human decision'), guards: ['gate-needs-human'] };
|
|
822
|
+
}
|
|
823
|
+
if (decision === 'FALLBACK') {
|
|
824
|
+
return {
|
|
825
|
+
...base,
|
|
826
|
+
event: 'GATE_FALLBACK',
|
|
827
|
+
reason: gateReason(gate, 'runtime gate requires fallback integration'),
|
|
828
|
+
guards: ['gate-fallback'],
|
|
829
|
+
moduleResults: {
|
|
830
|
+
...base.moduleResults,
|
|
831
|
+
fallbackRequired: {
|
|
832
|
+
required: true,
|
|
833
|
+
applied: false,
|
|
834
|
+
decision: 'FALLBACK'
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
...base,
|
|
841
|
+
event: 'GATE_UNKNOWN',
|
|
842
|
+
reason: 'runtime gate decision is unknown',
|
|
843
|
+
errors: [loopError('UNKNOWN_RUNTIME_GATE_DECISION', 'runtime gate decision is unknown', input.state, 'GATE_UNKNOWN', 'runtimeGate.decision', 'error')]
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function deriveExecutionPlan(input, base) {
|
|
848
|
+
const gate = input.decisions.runtimeGate || {};
|
|
849
|
+
const manager = input.decisions.manager || {};
|
|
850
|
+
if (gate.decision === 'DELEGATE' || manager.decision === 'decompose') {
|
|
851
|
+
const source = resolveProvidedOrAdapter({
|
|
852
|
+
...input,
|
|
853
|
+
base,
|
|
854
|
+
decisionKey: 'plannedDelegation',
|
|
855
|
+
moduleKey: 'delegation',
|
|
856
|
+
contextKey: 'delegation',
|
|
857
|
+
adapterName: 'planDelegation'
|
|
858
|
+
});
|
|
859
|
+
if (source.error || !isPlainObject(source.value)) {
|
|
860
|
+
return {
|
|
861
|
+
...base,
|
|
862
|
+
event: 'DELEGATION_INVALID',
|
|
863
|
+
reason: source.error ? source.error.message : 'delegation result is malformed',
|
|
864
|
+
errors: [source.error || loopError('MALFORMED_DELEGATION', 'delegation result is malformed', input.state, 'DELEGATION_INVALID', 'delegation', 'error')]
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
const validation = validateDelegation(source.value, input.limits);
|
|
868
|
+
if (validation.errors.length > 0) {
|
|
869
|
+
return {
|
|
870
|
+
...base,
|
|
871
|
+
event: validation.blocking ? 'DELEGATION_INVALID' : 'DELEGATION_INVALID',
|
|
872
|
+
reason: validation.errors[0].message,
|
|
873
|
+
errors: validation.errors,
|
|
874
|
+
conflicts: validation.conflicts
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
return {
|
|
878
|
+
...base,
|
|
879
|
+
event: 'DELEGATION_READY',
|
|
880
|
+
reason: 'delegation plan is available',
|
|
881
|
+
guards: ['delegation-valid', 'serial-only'],
|
|
882
|
+
moduleResults: {
|
|
883
|
+
...base.moduleResults,
|
|
884
|
+
delegation: cloneData(source.value),
|
|
885
|
+
plannedSubtasks: cloneData(source.value.subtasks || []),
|
|
886
|
+
assignments: cloneData(source.value.assignments || [])
|
|
887
|
+
},
|
|
888
|
+
counters: {
|
|
889
|
+
...base.counters,
|
|
890
|
+
subtasks: Array.isArray(source.value.subtasks) ? source.value.subtasks.length : 0,
|
|
891
|
+
agents: Array.isArray(source.value.assignments) ? source.value.assignments.length : 0
|
|
892
|
+
},
|
|
893
|
+
provenance: [provenance(source.provenance, source.adapterName, 'delegation', true)]
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
return {
|
|
897
|
+
...base,
|
|
898
|
+
event: 'PLAN_READY',
|
|
899
|
+
reason: 'direct serial execution plan is ready',
|
|
900
|
+
guards: ['serial-plan'],
|
|
901
|
+
moduleResults: {
|
|
902
|
+
...base.moduleResults,
|
|
903
|
+
executionPlan: directExecutionPlan(input.task)
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function deriveSubtasks(input, base) {
|
|
909
|
+
const source = resolveProvidedOrAdapter({
|
|
910
|
+
...input,
|
|
911
|
+
base,
|
|
912
|
+
decisionKey: 'plannedSubtasks',
|
|
913
|
+
moduleKey: 'plannedSubtasks',
|
|
914
|
+
contextKey: 'plannedSubtasks',
|
|
915
|
+
adapterName: 'planSubtasks',
|
|
916
|
+
fallbackValue: input.moduleResults.plannedSubtasks
|
|
917
|
+
});
|
|
918
|
+
const subtasks = Array.isArray(source.value)
|
|
919
|
+
? source.value
|
|
920
|
+
: isPlainObject(source.value) && Array.isArray(source.value.subtasks)
|
|
921
|
+
? source.value.subtasks
|
|
922
|
+
: source.value;
|
|
923
|
+
if (source.error || !Array.isArray(subtasks)) {
|
|
924
|
+
return {
|
|
925
|
+
...base,
|
|
926
|
+
event: 'SUBTASKS_INVALID',
|
|
927
|
+
reason: source.error ? source.error.message : 'subtasks result is malformed',
|
|
928
|
+
errors: [source.error || loopError('MALFORMED_SUBTASKS', 'subtasks result is malformed', input.state, 'SUBTASKS_INVALID', 'subtasks', 'error')]
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
if (input.moduleResults.assignments !== undefined && !Array.isArray(input.moduleResults.assignments)) {
|
|
932
|
+
return {
|
|
933
|
+
...base,
|
|
934
|
+
event: 'SUBTASKS_INVALID',
|
|
935
|
+
reason: 'assignments must be an array',
|
|
936
|
+
errors: [loopError('MALFORMED_ASSIGNMENTS', 'assignments must be an array', input.state, 'SUBTASKS_INVALID', 'assignments', 'error')]
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
const validation = validateSubtasks(subtasks, input.moduleResults.assignments || [], input.limits, input.context);
|
|
940
|
+
if (validation.errors.length > 0) {
|
|
941
|
+
return {
|
|
942
|
+
...base,
|
|
943
|
+
event: validation.blocking ? 'SUBTASKS_INVALID' : 'SUBTASKS_INVALID',
|
|
944
|
+
reason: validation.errors[0].message,
|
|
945
|
+
errors: validation.errors,
|
|
946
|
+
conflicts: validation.conflicts
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
return {
|
|
950
|
+
...base,
|
|
951
|
+
event: 'SUBTASKS_READY',
|
|
952
|
+
reason: 'subtask plan is valid and serial',
|
|
953
|
+
guards: ['subtasks-valid', 'subtask-limits'],
|
|
954
|
+
moduleResults: {
|
|
955
|
+
...base.moduleResults,
|
|
956
|
+
plannedSubtasks: cloneData(subtasks),
|
|
957
|
+
serialOrder: subtasks.map(item => String(item.id))
|
|
958
|
+
},
|
|
959
|
+
counters: {
|
|
960
|
+
...base.counters,
|
|
961
|
+
subtasks: subtasks.length
|
|
962
|
+
},
|
|
963
|
+
provenance: [provenance(source.provenance, source.adapterName, 'subtasks', true)]
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function deriveConflicts(input, base) {
|
|
968
|
+
const source = resolveProvidedOrAdapter({
|
|
969
|
+
...input,
|
|
970
|
+
base,
|
|
971
|
+
decisionKey: 'conflicts',
|
|
972
|
+
moduleKey: 'conflicts',
|
|
973
|
+
contextKey: 'conflicts',
|
|
974
|
+
adapterName: 'detectConflicts',
|
|
975
|
+
fallbackValue: []
|
|
976
|
+
});
|
|
977
|
+
if (source.error || !Array.isArray(source.value)) {
|
|
978
|
+
return {
|
|
979
|
+
...base,
|
|
980
|
+
event: 'CONFLICTS_INVALID',
|
|
981
|
+
reason: source.error ? source.error.message : 'conflict result is malformed',
|
|
982
|
+
errors: [source.error || loopError('MALFORMED_CONFLICT', 'conflict result is malformed', input.state, 'CONFLICTS_INVALID', 'conflicts', 'error')]
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
const normalized = normalizeConflicts(source.value, input.moduleResults.plannedSubtasks || []);
|
|
986
|
+
if (normalized.errors.length > 0) {
|
|
987
|
+
return {
|
|
988
|
+
...base,
|
|
989
|
+
event: 'CONFLICTS_INVALID',
|
|
990
|
+
reason: normalized.errors[0].message,
|
|
991
|
+
errors: normalized.errors,
|
|
992
|
+
conflicts: normalized.conflicts
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
if (normalized.blocking) {
|
|
996
|
+
return {
|
|
997
|
+
...base,
|
|
998
|
+
event: 'CONFLICTS_BLOCKING',
|
|
999
|
+
reason: 'blocking file conflict prevents safe planning',
|
|
1000
|
+
guards: ['conflicts-blocking'],
|
|
1001
|
+
errors: [loopError('BLOCKING_CONFLICT', 'blocking file conflict prevents safe planning', input.state, 'CONFLICTS_BLOCKING', 'conflicts', 'blocking')],
|
|
1002
|
+
conflicts: normalized.conflicts,
|
|
1003
|
+
moduleResults: {
|
|
1004
|
+
...base.moduleResults,
|
|
1005
|
+
conflicts: cloneData(normalized.conflicts)
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
return {
|
|
1010
|
+
...base,
|
|
1011
|
+
event: normalized.conflicts.length > 0 ? 'CONFLICTS_FOUND' : 'CONFLICTS_CLEAR',
|
|
1012
|
+
reason: normalized.conflicts.length > 0 ? 'serializable conflicts were recorded' : 'no file conflicts were found',
|
|
1013
|
+
guards: ['conflicts-evaluated'],
|
|
1014
|
+
conflicts: normalized.conflicts,
|
|
1015
|
+
moduleResults: {
|
|
1016
|
+
...base.moduleResults,
|
|
1017
|
+
conflicts: cloneData(normalized.conflicts)
|
|
1018
|
+
},
|
|
1019
|
+
provenance: [provenance(source.provenance, source.adapterName, 'conflicts', true)]
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function deriveSimulation(input, base) {
|
|
1024
|
+
const order = serialOrderFor(input.task, input.moduleResults);
|
|
1025
|
+
const simulation = {
|
|
1026
|
+
simulated: true,
|
|
1027
|
+
executed: false,
|
|
1028
|
+
executionMode: 'serial',
|
|
1029
|
+
order,
|
|
1030
|
+
engineCandidate: cloneData(input.decisions.engineCandidate),
|
|
1031
|
+
workerCalled: false,
|
|
1032
|
+
engineCalled: false,
|
|
1033
|
+
commandsCalled: false,
|
|
1034
|
+
hostValidationCalled: false,
|
|
1035
|
+
fallbackApplied: false,
|
|
1036
|
+
evidenceCreated: false,
|
|
1037
|
+
workspaceDiffCreated: false
|
|
1038
|
+
};
|
|
1039
|
+
return {
|
|
1040
|
+
...base,
|
|
1041
|
+
event: 'SIMULATION_READY',
|
|
1042
|
+
reason: 'dry-run execution summary was simulated without effects',
|
|
1043
|
+
guards: ['dry-run', 'no-effects'],
|
|
1044
|
+
moduleResults: {
|
|
1045
|
+
...base.moduleResults,
|
|
1046
|
+
simulation
|
|
1047
|
+
},
|
|
1048
|
+
provenance: [provenance('loop', null, 'simulation', true)]
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function deriveConsolidation(input, base) {
|
|
1053
|
+
const supplied = firstDefined(input.context.consolidatedResult, input.moduleResults.consolidation);
|
|
1054
|
+
if (supplied !== undefined) {
|
|
1055
|
+
if (!isValidConsolidation(supplied)) {
|
|
1056
|
+
return {
|
|
1057
|
+
...base,
|
|
1058
|
+
event: 'CONSOLIDATION_INVALID',
|
|
1059
|
+
reason: 'consolidated result is malformed',
|
|
1060
|
+
errors: [loopError('MALFORMED_CONSOLIDATION', 'consolidated result is malformed', input.state, 'CONSOLIDATION_INVALID', 'consolidation', 'error')]
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
return {
|
|
1064
|
+
...base,
|
|
1065
|
+
event: 'CONSOLIDATION_READY',
|
|
1066
|
+
reason: 'supplied consolidated result was preserved',
|
|
1067
|
+
guards: ['consolidation-supplied'],
|
|
1068
|
+
moduleResults: {
|
|
1069
|
+
...base.moduleResults,
|
|
1070
|
+
consolidation: cloneData(supplied)
|
|
1071
|
+
},
|
|
1072
|
+
provenance: [provenance(Object.prototype.hasOwnProperty.call(input.context, 'consolidatedResult') ? 'input' : 'module-result', null, 'consolidation', false)]
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
const subtaskResults = firstDefined(input.context.simulatedSubtaskResults, input.moduleResults.simulatedSubtaskResults);
|
|
1076
|
+
if (subtaskResults !== undefined) {
|
|
1077
|
+
const source = callAdapter('consolidate', input.adapters.consolidate, {
|
|
1078
|
+
task: cloneData(input.task),
|
|
1079
|
+
subtasks: cloneData(input.moduleResults.plannedSubtasks || []),
|
|
1080
|
+
subtaskResults: cloneData(subtaskResults),
|
|
1081
|
+
simulated: true,
|
|
1082
|
+
context: cloneData(input.context)
|
|
1083
|
+
}, input.state, 'CONSOLIDATION_INVALID');
|
|
1084
|
+
if (source.error || !isValidConsolidation(source.value)) {
|
|
1085
|
+
return {
|
|
1086
|
+
...base,
|
|
1087
|
+
event: 'CONSOLIDATION_INVALID',
|
|
1088
|
+
reason: source.error ? source.error.message : 'consolidated result is malformed',
|
|
1089
|
+
errors: [source.error || loopError('MALFORMED_CONSOLIDATION', 'consolidated result is malformed', input.state, 'CONSOLIDATION_INVALID', 'consolidation', 'error')]
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
return {
|
|
1093
|
+
...base,
|
|
1094
|
+
event: 'CONSOLIDATION_READY',
|
|
1095
|
+
reason: 'simulated subtask results were consolidated by adapter',
|
|
1096
|
+
guards: ['simulated-results-supplied'],
|
|
1097
|
+
moduleResults: {
|
|
1098
|
+
...base.moduleResults,
|
|
1099
|
+
consolidation: cloneData(source.value)
|
|
1100
|
+
},
|
|
1101
|
+
provenance: [provenance('adapter', 'consolidate', 'consolidation', true)]
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
return {
|
|
1105
|
+
...base,
|
|
1106
|
+
event: 'CONSOLIDATION_SKIPPED',
|
|
1107
|
+
reason: 'no subtask results were supplied',
|
|
1108
|
+
guards: ['no-consolidation-input'],
|
|
1109
|
+
moduleResults: {
|
|
1110
|
+
...base.moduleResults,
|
|
1111
|
+
consolidation: {
|
|
1112
|
+
mode: 'not_run',
|
|
1113
|
+
reason: 'no subtask results were supplied'
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
provenance: [provenance('loop', null, 'consolidation', true)]
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function deriveVerification(input, base) {
|
|
1121
|
+
const supplied = firstDefined(input.context.verificationResult, input.moduleResults.verification);
|
|
1122
|
+
if (supplied !== undefined) {
|
|
1123
|
+
if (!isValidVerification(supplied)) {
|
|
1124
|
+
return {
|
|
1125
|
+
...base,
|
|
1126
|
+
event: 'VERIFICATION_INVALID',
|
|
1127
|
+
reason: 'verification result is malformed',
|
|
1128
|
+
errors: [loopError('MALFORMED_VERIFICATION', 'verification result is malformed', input.state, 'VERIFICATION_INVALID', 'verification', 'error')]
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
return {
|
|
1132
|
+
...base,
|
|
1133
|
+
event: 'VERIFICATION_READY',
|
|
1134
|
+
reason: 'supplied verification result was preserved',
|
|
1135
|
+
guards: ['verification-supplied'],
|
|
1136
|
+
moduleResults: {
|
|
1137
|
+
...base.moduleResults,
|
|
1138
|
+
verification: cloneData(supplied)
|
|
1139
|
+
},
|
|
1140
|
+
provenance: [provenance(Object.prototype.hasOwnProperty.call(input.context, 'verificationResult') ? 'input' : 'module-result', null, 'verification', false)]
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
const consolidation = input.moduleResults.consolidation;
|
|
1144
|
+
if (isValidConsolidation(consolidation) && typeof input.adapters.verify === 'function') {
|
|
1145
|
+
const source = callAdapter('verify', input.adapters.verify, {
|
|
1146
|
+
task: cloneData(input.task),
|
|
1147
|
+
consolidatedResult: cloneData(consolidation),
|
|
1148
|
+
acceptanceCriteria: cloneData(input.context.acceptanceCriteria || [])
|
|
1149
|
+
}, input.state, 'VERIFICATION_INVALID');
|
|
1150
|
+
if (source.error || !isValidVerification(source.value)) {
|
|
1151
|
+
return {
|
|
1152
|
+
...base,
|
|
1153
|
+
event: 'VERIFICATION_INVALID',
|
|
1154
|
+
reason: source.error ? source.error.message : 'verification result is malformed',
|
|
1155
|
+
errors: [source.error || loopError('MALFORMED_VERIFICATION', 'verification result is malformed', input.state, 'VERIFICATION_INVALID', 'verification', 'error')]
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
return {
|
|
1159
|
+
...base,
|
|
1160
|
+
event: 'VERIFICATION_READY',
|
|
1161
|
+
reason: 'consolidated result was verified by adapter',
|
|
1162
|
+
guards: ['consolidated-result-supplied'],
|
|
1163
|
+
moduleResults: {
|
|
1164
|
+
...base.moduleResults,
|
|
1165
|
+
verification: cloneData(source.value)
|
|
1166
|
+
},
|
|
1167
|
+
provenance: [provenance('adapter', 'verify', 'verification', true)]
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
...base,
|
|
1172
|
+
event: 'VERIFICATION_SKIPPED',
|
|
1173
|
+
reason: 'no consolidated result was supplied',
|
|
1174
|
+
guards: ['no-verification-input'],
|
|
1175
|
+
moduleResults: {
|
|
1176
|
+
...base.moduleResults,
|
|
1177
|
+
verification: {
|
|
1178
|
+
mode: 'not_run',
|
|
1179
|
+
reason: 'no consolidated result was supplied'
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
provenance: [provenance('loop', null, 'verification', true)]
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function resolveProvidedOrAdapter(config) {
|
|
1187
|
+
const supplied = firstDefined(
|
|
1188
|
+
config.context[config.contextKey],
|
|
1189
|
+
config.moduleResults[config.moduleKey]
|
|
1190
|
+
);
|
|
1191
|
+
if (supplied !== undefined) {
|
|
1192
|
+
return {
|
|
1193
|
+
value: cloneData(supplied),
|
|
1194
|
+
provenance: Object.prototype.hasOwnProperty.call(config.context, config.contextKey) ? 'input' : 'module-result',
|
|
1195
|
+
adapterName: null
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
const adapter = config.adapters[config.adapterName];
|
|
1199
|
+
if (typeof adapter !== 'function' && config.fallbackValue !== undefined) {
|
|
1200
|
+
return {
|
|
1201
|
+
value: cloneData(config.fallbackValue),
|
|
1202
|
+
provenance: 'loop',
|
|
1203
|
+
adapterName: null
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
const called = callAdapter(config.adapterName, adapter, {
|
|
1207
|
+
task: cloneData(config.task),
|
|
1208
|
+
context: cloneData(config.context),
|
|
1209
|
+
decisions: cloneData(config.decisions),
|
|
1210
|
+
moduleResults: cloneData(config.moduleResults),
|
|
1211
|
+
limits: cloneData(config.limits),
|
|
1212
|
+
counters: cloneData(config.counters)
|
|
1213
|
+
}, config.state, config.eventInvalid || 'INVALID_TRANSITION');
|
|
1214
|
+
if (called.error) {
|
|
1215
|
+
return called;
|
|
1216
|
+
}
|
|
1217
|
+
return {
|
|
1218
|
+
value: called.value,
|
|
1219
|
+
provenance: 'adapter',
|
|
1220
|
+
adapterName: config.adapterName
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function callAdapter(name, adapter, payload, state, event) {
|
|
1225
|
+
if (typeof adapter !== 'function') {
|
|
1226
|
+
return {
|
|
1227
|
+
error: loopError('ADAPTER_MISSING', `adapter ${name} is required for this state`, state, event, name, 'error')
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
let value;
|
|
1231
|
+
try {
|
|
1232
|
+
value = adapter(payload);
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
return {
|
|
1235
|
+
error: loopError('ADAPTER_THROWN', `adapter ${name} failed: ${errorMessage(error)}`, state, event, name, 'error')
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
if (value === undefined) {
|
|
1239
|
+
return {
|
|
1240
|
+
error: loopError('ADAPTER_RETURNED_UNDEFINED', `adapter ${name} returned undefined`, state, event, name, 'error')
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
if (isThenable(value)) {
|
|
1244
|
+
return {
|
|
1245
|
+
error: loopError('ADAPTER_RETURNED_THENABLE', `adapter ${name} returned a Promise or thenable`, state, event, name, 'error')
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
return { value: cloneData(value) };
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function validateDelegation(plan, limits) {
|
|
1252
|
+
const errors = [];
|
|
1253
|
+
const conflicts = [];
|
|
1254
|
+
if (plan.executionMode !== 'serial') {
|
|
1255
|
+
errors.push(loopError('NON_SERIAL_DELEGATION', 'delegation executionMode must be serial', 'loop.execution_planned', 'DELEGATION_INVALID', 'delegation.executionMode', 'blocking'));
|
|
1256
|
+
}
|
|
1257
|
+
if (!Array.isArray(plan.subtasks)) {
|
|
1258
|
+
errors.push(loopError('MALFORMED_DELEGATION', 'delegation subtasks must be an array', 'loop.execution_planned', 'DELEGATION_INVALID', 'delegation.subtasks', 'error'));
|
|
1259
|
+
}
|
|
1260
|
+
if (!Array.isArray(plan.assignments)) {
|
|
1261
|
+
errors.push(loopError('MALFORMED_DELEGATION', 'delegation assignments must be an array', 'loop.execution_planned', 'DELEGATION_INVALID', 'delegation.assignments', 'error'));
|
|
1262
|
+
}
|
|
1263
|
+
if (Array.isArray(plan.assignments)) {
|
|
1264
|
+
const count = plan.assignments.length;
|
|
1265
|
+
if (count > limits.maxAgentsPerManager) {
|
|
1266
|
+
errors.push(loopError('AGENT_LIMIT_EXCEEDED', `assignments exceed maxAgentsPerManager ${limits.maxAgentsPerManager}`, 'loop.execution_planned', 'DELEGATION_INVALID', 'assignments', 'blocking'));
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
if (Array.isArray(plan.subtasks) && plan.subtasks.length > limits.maxSubtasks) {
|
|
1270
|
+
errors.push(loopError('SUBTASK_LIMIT_EXCEEDED', `subtasks exceed maxSubtasks ${limits.maxSubtasks}`, 'loop.execution_planned', 'DELEGATION_INVALID', 'subtasks', 'blocking'));
|
|
1271
|
+
}
|
|
1272
|
+
return { errors, conflicts, blocking: errors.some(error => error.severity === 'blocking') };
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function validateSubtasks(subtasks, assignments, limits, context) {
|
|
1276
|
+
const errors = [];
|
|
1277
|
+
const conflicts = [];
|
|
1278
|
+
if (Number(context.orchestrationDepth || 0) > limits.maxDepth) {
|
|
1279
|
+
errors.push(loopError('DEPTH_LIMIT_EXCEEDED', `orchestrationDepth exceeds maxDepth ${limits.maxDepth}`, 'loop.delegation_planned', 'SUBTASKS_INVALID', 'context.orchestrationDepth', 'blocking'));
|
|
1280
|
+
}
|
|
1281
|
+
if (subtasks.length > limits.maxSubtasks) {
|
|
1282
|
+
errors.push(loopError('SUBTASK_LIMIT_EXCEEDED', `subtasks exceed maxSubtasks ${limits.maxSubtasks}`, 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks', 'blocking'));
|
|
1283
|
+
}
|
|
1284
|
+
const ids = subtasks.map(item => isPlainObject(item) ? item.id : null);
|
|
1285
|
+
const idSet = new Set(ids.filter(value => typeof value === 'string' && value.length > 0));
|
|
1286
|
+
if (idSet.size !== ids.length) {
|
|
1287
|
+
errors.push(loopError('INVALID_SUBTASK_REFERENCE', 'subtask ids must be unique non-empty strings', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.id', 'error'));
|
|
1288
|
+
}
|
|
1289
|
+
for (const subtask of subtasks) {
|
|
1290
|
+
if (!isPlainObject(subtask)) {
|
|
1291
|
+
errors.push(loopError('MALFORMED_SUBTASK', 'subtask must be an object', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks', 'error'));
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
if (subtask.parentTaskId !== undefined && subtask.parentTaskId !== context.taskId) {
|
|
1295
|
+
errors.push(loopError('INVALID_SUBTASK_REFERENCE', 'subtask parentTaskId does not match parent context', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.parentTaskId', 'error'));
|
|
1296
|
+
}
|
|
1297
|
+
if (subtask.expectedFiles !== undefined && !Array.isArray(subtask.expectedFiles)) {
|
|
1298
|
+
errors.push(loopError('MALFORMED_SUBTASK', 'expectedFiles must be an array when present', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.expectedFiles', 'error'));
|
|
1299
|
+
}
|
|
1300
|
+
if (Array.isArray(subtask.expectedFiles)) {
|
|
1301
|
+
for (const file of subtask.expectedFiles) {
|
|
1302
|
+
if (typeof file !== 'string' || file.length === 0) {
|
|
1303
|
+
errors.push(loopError('MALFORMED_SUBTASK', 'expectedFiles entries must be non-empty strings', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.expectedFiles', 'error'));
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if (Array.isArray(subtask.blockedBy)) {
|
|
1308
|
+
for (const dependency of subtask.blockedBy) {
|
|
1309
|
+
if (!idSet.has(dependency)) {
|
|
1310
|
+
errors.push(loopError('INVALID_SUBTASK_REFERENCE', `blockedBy references missing subtask ${String(dependency)}`, 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.blockedBy', 'error'));
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
} else if (subtask.blockedBy !== undefined) {
|
|
1314
|
+
errors.push(loopError('MALFORMED_SUBTASK', 'blockedBy must be an array when present', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.blockedBy', 'error'));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
if (hasDependencyCycle(subtasks)) {
|
|
1318
|
+
errors.push(loopError('SUBTASK_DEPENDENCY_CYCLE', 'blockedBy references contain a cycle', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.blockedBy', 'blocking'));
|
|
1319
|
+
}
|
|
1320
|
+
for (const assignment of assignments) {
|
|
1321
|
+
if (!isPlainObject(assignment) || typeof assignment.subtaskId !== 'string' || !idSet.has(assignment.subtaskId)) {
|
|
1322
|
+
errors.push(loopError('INVALID_ASSIGNMENT_REFERENCE', 'assignment references a missing subtask', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'assignments.subtaskId', 'error'));
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (!(typeof assignment.candidateEngine === 'string' || assignment.candidateEngine === null)) {
|
|
1326
|
+
errors.push(loopError('INVALID_ASSIGNMENT_REFERENCE', 'assignment candidateEngine must be a string or null', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'assignments.candidateEngine', 'error'));
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return { errors, conflicts, blocking: errors.some(error => error.severity === 'blocking') };
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function normalizeConflicts(conflicts, subtasks) {
|
|
1333
|
+
const errors = [];
|
|
1334
|
+
const normalized = [];
|
|
1335
|
+
for (const conflict of conflicts) {
|
|
1336
|
+
if (!isPlainObject(conflict) ||
|
|
1337
|
+
typeof conflict.type !== 'string' ||
|
|
1338
|
+
!['low', 'medium', 'high', 'critical'].includes(conflict.severity) ||
|
|
1339
|
+
typeof conflict.reason !== 'string') {
|
|
1340
|
+
errors.push(loopError('MALFORMED_CONFLICT', 'conflict entry is malformed', 'loop.subtasks_planned', 'CONFLICTS_INVALID', 'conflicts', 'error'));
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
normalized.push(cloneData(conflict));
|
|
1344
|
+
}
|
|
1345
|
+
for (const conflict of inferConflictRisks(subtasks)) {
|
|
1346
|
+
normalized.push(conflict);
|
|
1347
|
+
}
|
|
1348
|
+
const blocking = normalized.some(conflict => {
|
|
1349
|
+
const target = String(conflict.file || conflict.path || '').replace(/\\/g, '/').toLowerCase();
|
|
1350
|
+
return conflict.severity === 'critical' ||
|
|
1351
|
+
conflict.type === 'parallel-write' ||
|
|
1352
|
+
isSensitivePath(target) ||
|
|
1353
|
+
/^[a-z]:[\\/]/i.test(target);
|
|
1354
|
+
});
|
|
1355
|
+
return {
|
|
1356
|
+
errors,
|
|
1357
|
+
conflicts: sortConflicts(normalized),
|
|
1358
|
+
blocking
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function inferConflictRisks(subtasks) {
|
|
1363
|
+
const conflicts = [];
|
|
1364
|
+
const fileOwners = new Map();
|
|
1365
|
+
for (const subtask of Array.isArray(subtasks) ? subtasks : []) {
|
|
1366
|
+
if (!isPlainObject(subtask)) continue;
|
|
1367
|
+
if (Array.isArray(subtask.expectedFiles) && subtask.expectedFiles.length === 0 && hasWriteFiles(subtask)) {
|
|
1368
|
+
conflicts.push({
|
|
1369
|
+
type: 'missing-expected-files',
|
|
1370
|
+
severity: 'medium',
|
|
1371
|
+
reason: 'write subtask does not declare expectedFiles',
|
|
1372
|
+
subtaskId: subtask.id
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
const expectedFiles = Array.isArray(subtask.expectedFiles) ? subtask.expectedFiles : [];
|
|
1376
|
+
for (const file of expectedFiles) {
|
|
1377
|
+
const key = String(file);
|
|
1378
|
+
const owners = fileOwners.get(key) || [];
|
|
1379
|
+
owners.push(String(subtask.id));
|
|
1380
|
+
fileOwners.set(key, owners);
|
|
1381
|
+
if (isSensitivePath(key)) {
|
|
1382
|
+
conflicts.push({
|
|
1383
|
+
type: 'sensitive-path',
|
|
1384
|
+
severity: 'critical',
|
|
1385
|
+
reason: 'sensitive or outside path is not safe for planned delegation',
|
|
1386
|
+
subtaskId: subtask.id,
|
|
1387
|
+
file: key
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
if (subtask.parallelizable === true && hasWriteFiles(subtask)) {
|
|
1392
|
+
conflicts.push({
|
|
1393
|
+
type: 'parallel-write',
|
|
1394
|
+
severity: 'critical',
|
|
1395
|
+
reason: 'parallelizable file writes are not allowed',
|
|
1396
|
+
subtaskId: subtask.id
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
for (const [file, owners] of fileOwners.entries()) {
|
|
1401
|
+
if (owners.length > 1) {
|
|
1402
|
+
conflicts.push({
|
|
1403
|
+
type: 'file-write-conflict',
|
|
1404
|
+
severity: 'high',
|
|
1405
|
+
reason: 'multiple subtasks target the same file',
|
|
1406
|
+
subtaskIds: owners,
|
|
1407
|
+
file
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
const files = [...fileOwners.keys()].sort(compareStrings);
|
|
1412
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
1413
|
+
for (let other = index + 1; other < files.length; other += 1) {
|
|
1414
|
+
const left = normalizePathForCompare(files[index]);
|
|
1415
|
+
const right = normalizePathForCompare(files[other]);
|
|
1416
|
+
if (left && right && (right.startsWith(`${left}/`) || left.startsWith(`${right}/`))) {
|
|
1417
|
+
conflicts.push({
|
|
1418
|
+
type: 'path-overlap',
|
|
1419
|
+
severity: 'critical',
|
|
1420
|
+
reason: 'parent/child file paths require explicit precedence before delegation',
|
|
1421
|
+
subtaskIds: uniqueSortedStrings([...(fileOwners.get(files[index]) || []), ...(fileOwners.get(files[other]) || [])]),
|
|
1422
|
+
file: files[index],
|
|
1423
|
+
path: files[other]
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
return conflicts;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function buildStepResult(input) {
|
|
1432
|
+
const terminal = TERMINAL_STATES.has(input.nextState);
|
|
1433
|
+
const counters = cloneData(input.counters);
|
|
1434
|
+
const cycleSignature = input.forcedCycleSignature || createCycleSignature({
|
|
1435
|
+
state: input.state,
|
|
1436
|
+
event: input.event,
|
|
1437
|
+
task: null,
|
|
1438
|
+
context: input.context,
|
|
1439
|
+
counters,
|
|
1440
|
+
decisions: input.decisions,
|
|
1441
|
+
moduleResults: input.moduleResults,
|
|
1442
|
+
conflicts: input.conflicts
|
|
1443
|
+
});
|
|
1444
|
+
const traceEntry = {
|
|
1445
|
+
schemaVersion: SCHEMA_VERSION,
|
|
1446
|
+
sequence: input.previousTrace.length,
|
|
1447
|
+
previousState: input.state,
|
|
1448
|
+
event: input.event,
|
|
1449
|
+
nextState: input.nextState,
|
|
1450
|
+
terminal,
|
|
1451
|
+
terminalReason: input.terminalReason,
|
|
1452
|
+
reason: input.reason,
|
|
1453
|
+
guards: cloneData(input.guards),
|
|
1454
|
+
decisions: redactSensitive(input.decisions),
|
|
1455
|
+
taskId: stringOrNull(input.context.taskId),
|
|
1456
|
+
parentTaskId: stringOrNull(input.context.parentTaskId),
|
|
1457
|
+
manager: managerIdFrom(input.decisions.manager),
|
|
1458
|
+
engineCandidate: engineIdFrom(input.decisions.engineCandidate),
|
|
1459
|
+
runtimeGateDecision: runtimeGateDecision(input.decisions.runtimeGate),
|
|
1460
|
+
limits: cloneData(input.limits),
|
|
1461
|
+
counters,
|
|
1462
|
+
warnings: uniqueMessages(input.warnings),
|
|
1463
|
+
errors: sortErrors(input.errors),
|
|
1464
|
+
conflicts: redactSensitive(sortConflicts(input.conflicts)),
|
|
1465
|
+
provenance: cloneData(input.provenance),
|
|
1466
|
+
cycleSignature
|
|
1467
|
+
};
|
|
1468
|
+
return {
|
|
1469
|
+
schemaVersion: SCHEMA_VERSION,
|
|
1470
|
+
previousState: input.state,
|
|
1471
|
+
event: input.event,
|
|
1472
|
+
nextState: input.nextState,
|
|
1473
|
+
terminal,
|
|
1474
|
+
terminalReason: input.terminalReason,
|
|
1475
|
+
reason: input.reason,
|
|
1476
|
+
guards: cloneData(input.guards),
|
|
1477
|
+
decisions: cloneData(input.decisions),
|
|
1478
|
+
moduleResults: cloneData(input.moduleResults),
|
|
1479
|
+
context: cloneData(input.context),
|
|
1480
|
+
counters,
|
|
1481
|
+
cycleSignature,
|
|
1482
|
+
traceEntry,
|
|
1483
|
+
warnings: uniqueMessages(input.warnings),
|
|
1484
|
+
errors: sortErrors(input.errors),
|
|
1485
|
+
inconsistencies: sortErrors(input.inconsistencies)
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function buildLoopResult(input) {
|
|
1490
|
+
const finalState = input.finalStep ? input.finalStep.nextState : input.initialState;
|
|
1491
|
+
const warnings = uniqueMessages(input.trace.flatMap(entry => entry.warnings));
|
|
1492
|
+
const errors = sortErrors(input.trace.flatMap(entry => entry.errors));
|
|
1493
|
+
const inconsistencies = sortErrors(input.finalStep ? input.finalStep.inconsistencies : []);
|
|
1494
|
+
const consolidation = cloneData(input.moduleResults.consolidation || null);
|
|
1495
|
+
const verification = cloneData(input.moduleResults.verification || null);
|
|
1496
|
+
const verificationOutcome = isPlainObject(verification) && verification.mode !== 'not_run'
|
|
1497
|
+
? stringOrNull(verification.verificationOutcome || verification.outcome)
|
|
1498
|
+
: null;
|
|
1499
|
+
const conflicts = sortConflicts(input.moduleResults.conflicts || []);
|
|
1500
|
+
const result = {
|
|
1501
|
+
schemaVersion: SCHEMA_VERSION,
|
|
1502
|
+
loopId: input.loopId || defaultLoopId(input.task, input.context),
|
|
1503
|
+
dryRun: true,
|
|
1504
|
+
executed: false,
|
|
1505
|
+
taskId: isValidTask(input.task) ? String(input.task.id) : null,
|
|
1506
|
+
parentTaskId: getParentTaskId(input.task, input.context),
|
|
1507
|
+
initialState: input.initialState,
|
|
1508
|
+
finalLoopState: finalState,
|
|
1509
|
+
terminalReason: input.finalStep ? input.finalStep.terminalReason : null,
|
|
1510
|
+
completedPlanning: finalState === 'loop.completed',
|
|
1511
|
+
trace: cloneData(input.trace),
|
|
1512
|
+
decisions: normalizeDecisions(input.decisions),
|
|
1513
|
+
plannedSubtasks: cloneData(input.moduleResults.plannedSubtasks || []),
|
|
1514
|
+
plannedDelegation: cloneData(input.moduleResults.delegation || null),
|
|
1515
|
+
conflicts,
|
|
1516
|
+
selectedManager: managerIdFrom(input.decisions.manager),
|
|
1517
|
+
engineCandidate: engineIdFrom(input.decisions.engineCandidate),
|
|
1518
|
+
runtimeGateDecision: runtimeGateDecision(input.decisions.runtimeGate),
|
|
1519
|
+
simulation: cloneData(input.moduleResults.simulation || null),
|
|
1520
|
+
consolidation,
|
|
1521
|
+
verification,
|
|
1522
|
+
verificationOutcome,
|
|
1523
|
+
warnings,
|
|
1524
|
+
errors,
|
|
1525
|
+
inconsistencies,
|
|
1526
|
+
limits: cloneData(input.limits),
|
|
1527
|
+
limitsConsumed: cloneData(input.counters),
|
|
1528
|
+
cycleInfo: cloneData(input.context.cycleInfo || null),
|
|
1529
|
+
recommendedNextAction: recommendedNextAction(finalState, input.decisions.runtimeGate, consolidation, verification, conflicts),
|
|
1530
|
+
summary: ''
|
|
1531
|
+
};
|
|
1532
|
+
result.summary = `Loop ${stateSummary(finalState)}: planning=${String(result.completedPlanning)}, executed=false, gate=${result.runtimeGateDecision || 'none'}, verification=${verificationOutcome || 'not_run'}.`;
|
|
1533
|
+
return cloneData(result);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
function isTransitionAllowed(state, event) {
|
|
1537
|
+
return Object.prototype.hasOwnProperty.call(TRANSITIONS, state) &&
|
|
1538
|
+
Object.prototype.hasOwnProperty.call(TRANSITIONS[state], event);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function normalizeLimits(limits) {
|
|
1542
|
+
const resolved = {};
|
|
1543
|
+
const warnings = [];
|
|
1544
|
+
for (const [key, ceiling] of Object.entries(GLOBAL_LIMITS)) {
|
|
1545
|
+
const value = limits[key];
|
|
1546
|
+
if (value === undefined) {
|
|
1547
|
+
resolved[key] = ceiling;
|
|
1548
|
+
} else if (Number.isInteger(value) && value > 0) {
|
|
1549
|
+
if (value > ceiling) {
|
|
1550
|
+
resolved[key] = ceiling;
|
|
1551
|
+
warnings.push(`${key} clamped to ${ceiling}`);
|
|
1552
|
+
} else {
|
|
1553
|
+
resolved[key] = value;
|
|
1554
|
+
}
|
|
1555
|
+
} else {
|
|
1556
|
+
throw new TypeError(`limits.${key} must be a positive integer`);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return { limits: resolved, warnings };
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function normalizeCounters(counters, context) {
|
|
1563
|
+
const defaults = {
|
|
1564
|
+
transitions: 0,
|
|
1565
|
+
attempts: Number(context.attemptNumber || counters.attempts || 1),
|
|
1566
|
+
depth: Number(context.orchestrationDepth || counters.depth || 0),
|
|
1567
|
+
subtasks: Number(counters.subtasks || 0),
|
|
1568
|
+
agents: Number(counters.agents || 0)
|
|
1569
|
+
};
|
|
1570
|
+
const normalized = {};
|
|
1571
|
+
for (const [key, value] of Object.entries({ ...defaults, ...counters })) {
|
|
1572
|
+
if (!Number.isInteger(Number(value)) || Number(value) < 0) {
|
|
1573
|
+
throw new TypeError(`counters.${key} must be a non-negative integer`);
|
|
1574
|
+
}
|
|
1575
|
+
normalized[key] = Number(value);
|
|
1576
|
+
}
|
|
1577
|
+
if (normalized.attempts === 0) normalized.attempts = 1;
|
|
1578
|
+
return normalized;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function incrementTransitions(counters) {
|
|
1582
|
+
return {
|
|
1583
|
+
...cloneData(counters),
|
|
1584
|
+
transitions: counters.transitions + 1
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function normalizeDecisions(decisions) {
|
|
1589
|
+
return {
|
|
1590
|
+
...cloneData(DEFAULT_DECISIONS),
|
|
1591
|
+
...cloneData(decisions)
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
function createCycleSignature(input) {
|
|
1596
|
+
const stable = {
|
|
1597
|
+
classification: summarizeObject(input.decisions.classification, ['taskKind', 'complexity', 'risk', 'canDelegate']),
|
|
1598
|
+
manager: summarizeObject(input.decisions.manager, ['id', 'sector', 'decision']),
|
|
1599
|
+
engineCandidate: engineIdFrom(input.decisions.engineCandidate),
|
|
1600
|
+
preflight: summarizeObject(input.decisions.preflight, ['decision', 'reason']),
|
|
1601
|
+
budget: summarizeObject(input.decisions.budget, ['allowed', 'ok', 'reason']),
|
|
1602
|
+
policy: summarizeObject(input.decisions.policy, ['allowed', 'ok', 'reason']),
|
|
1603
|
+
gate: runtimeGateDecision(input.decisions.runtimeGate),
|
|
1604
|
+
subtasks: idsFrom(input.moduleResults.plannedSubtasks),
|
|
1605
|
+
assignments: idsFrom(input.moduleResults.assignments, 'subtaskId'),
|
|
1606
|
+
conflicts: sortConflicts(input.conflicts).map(conflict => ({
|
|
1607
|
+
type: conflict.type,
|
|
1608
|
+
file: redactCycleScalar(conflict.file || conflict.path || null),
|
|
1609
|
+
ids: conflict.subtaskIds || [conflict.subtaskId].filter(Boolean)
|
|
1610
|
+
})),
|
|
1611
|
+
consolidationCompleteness: input.moduleResults.consolidation && input.moduleResults.consolidation.consolidationCompleteness || null,
|
|
1612
|
+
verificationOutcome: input.moduleResults.verification && (input.moduleResults.verification.verificationOutcome || input.moduleResults.verification.outcome) || null
|
|
1613
|
+
};
|
|
1614
|
+
return [
|
|
1615
|
+
'cycle',
|
|
1616
|
+
input.state,
|
|
1617
|
+
input.event,
|
|
1618
|
+
taskIdFrom(input.task, input.context),
|
|
1619
|
+
stringOrNull(input.context.parentTaskId) || 'root',
|
|
1620
|
+
runtimeGateDecision(input.decisions.runtimeGate) || 'none',
|
|
1621
|
+
canonicalStringify(input.counters),
|
|
1622
|
+
canonicalStringify(stable)
|
|
1623
|
+
].join(':');
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function directExecutionPlan(task) {
|
|
1627
|
+
return {
|
|
1628
|
+
mode: 'direct',
|
|
1629
|
+
executionMode: 'serial',
|
|
1630
|
+
order: [String(task.id)]
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function serialOrderFor(task, modules) {
|
|
1635
|
+
if (Array.isArray(modules.serialOrder) && modules.serialOrder.length > 0) {
|
|
1636
|
+
return modules.serialOrder.map(String);
|
|
1637
|
+
}
|
|
1638
|
+
if (Array.isArray(modules.plannedSubtasks) && modules.plannedSubtasks.length > 0) {
|
|
1639
|
+
return modules.plannedSubtasks.map(item => String(item.id));
|
|
1640
|
+
}
|
|
1641
|
+
return isValidTask(task) ? [String(task.id)] : [];
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function isValidConsolidation(value) {
|
|
1645
|
+
return isPlainObject(value) &&
|
|
1646
|
+
value.schemaVersion === 1 &&
|
|
1647
|
+
typeof value.taskId === 'string' &&
|
|
1648
|
+
['accepted', 'accepted_with_warnings', 'rejected', 'inconclusive', 'needs_human'].includes(value.status) &&
|
|
1649
|
+
['complete', 'partial', 'invalid'].includes(value.consolidationCompleteness) &&
|
|
1650
|
+
arrayFieldsValid(value, ['perSubtask', 'evidence', 'missingEvidence', 'warnings', 'failures', 'codeFailures', 'infrastructureFailures', 'conflicts', 'inconsistencies', 'invalidReferences', 'remainingIssues', 'inputErrors']);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
function isValidVerification(value) {
|
|
1654
|
+
if (!isPlainObject(value)) return false;
|
|
1655
|
+
if (value.mode === 'not_run') return typeof value.reason === 'string';
|
|
1656
|
+
const outcome = value.verificationOutcome || value.outcome;
|
|
1657
|
+
return value.schemaVersion === 1 &&
|
|
1658
|
+
['verified', 'verified_with_warnings', 'rejected', 'inconclusive', 'blocked', 'needs_human'].includes(outcome);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
function arrayFieldsValid(value, fields) {
|
|
1662
|
+
return fields.every(field => value[field] === undefined || Array.isArray(value[field]));
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function recommendedNextAction(state, gate, consolidation, verification, conflicts) {
|
|
1666
|
+
if (state === 'loop.cancelled_limit') return 'stop_limit';
|
|
1667
|
+
if (state === 'loop.cancelled_cycle') return 'stop_cycle';
|
|
1668
|
+
if (state === 'loop.cancelled_invalid_transition' || state === 'loop.cancelled_invalid_input' || state === 'loop.failed') return 'stop_invalid';
|
|
1669
|
+
if (state === 'loop.needs_human') return 'request_human';
|
|
1670
|
+
if (state === 'loop.blocked') {
|
|
1671
|
+
if (runtimeGateDecision(gate) === 'FALLBACK') return 'integrate_runtime_later';
|
|
1672
|
+
if (conflicts.some(conflict => conflict.severity === 'critical')) return 'review_conflicts';
|
|
1673
|
+
return 'resolve_block';
|
|
1674
|
+
}
|
|
1675
|
+
if (state === 'loop.completed') {
|
|
1676
|
+
const verificationRan = isPlainObject(verification) && verification.mode !== 'not_run' && isValidVerification(verification);
|
|
1677
|
+
const verificationOutcomeValue = verificationRan ? verification.verificationOutcome || verification.outcome : null;
|
|
1678
|
+
if (!isValidConsolidation(consolidation) ||
|
|
1679
|
+
!verificationRan ||
|
|
1680
|
+
!['verified', 'verified_with_warnings'].includes(verificationOutcomeValue)) {
|
|
1681
|
+
return 'provide_simulated_results';
|
|
1682
|
+
}
|
|
1683
|
+
return 'execute_worker';
|
|
1684
|
+
}
|
|
1685
|
+
return 'stop_invalid';
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function reasonToTerminal(nextState, derived) {
|
|
1689
|
+
if (nextState === 'loop.completed') return 'completed';
|
|
1690
|
+
if (nextState === 'loop.blocked') return derived.event === 'GATE_FALLBACK' ? 'fallback_required' : 'blocked';
|
|
1691
|
+
if (nextState === 'loop.needs_human') return 'needs_human';
|
|
1692
|
+
if (nextState === 'loop.cancelled_invalid_input') return 'invalid_input';
|
|
1693
|
+
if (nextState === 'loop.failed') return 'failed';
|
|
1694
|
+
return null;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
function stateSummary(state) {
|
|
1698
|
+
return state.replace(/^loop\./, '');
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function defaultLoopId(task, context) {
|
|
1702
|
+
return `loop:${isValidTask(task) ? String(task.id) : 'null'}:${stringOrNull(context.parentTaskId) || 'root'}`;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function taskIdFrom(task, context) {
|
|
1706
|
+
return isValidTask(task) ? String(task.id) : stringOrNull(context.taskId) || 'null';
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function getParentTaskId(task, context) {
|
|
1710
|
+
return stringOrNull(context.parentTaskId) || (isPlainObject(task) ? stringOrNull(task.parentTaskId) : null);
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function isValidTask(task) {
|
|
1714
|
+
return isPlainObject(task) && task.id !== undefined && task.id !== null && String(task.id).length > 0;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
function gateReason(gate, fallback) {
|
|
1718
|
+
if (!isPlainObject(gate)) return fallback;
|
|
1719
|
+
if (typeof gate.reason === 'string') return gate.reason;
|
|
1720
|
+
if (Array.isArray(gate.reasons) && gate.reasons.length > 0) return gate.reasons.map(String).join('; ');
|
|
1721
|
+
return fallback;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
function runtimeGateDecision(gate) {
|
|
1725
|
+
return isPlainObject(gate) && typeof gate.decision === 'string' ? gate.decision : null;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function managerIdFrom(manager) {
|
|
1729
|
+
if (!isPlainObject(manager)) return null;
|
|
1730
|
+
return stringOrNull(manager.id) || stringOrNull(manager.sector) || stringOrNull(manager.managerId);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function engineIdFrom(engine) {
|
|
1734
|
+
if (engine === null) return null;
|
|
1735
|
+
if (!isPlainObject(engine)) return null;
|
|
1736
|
+
return stringOrNull(engine.id) || stringOrNull(engine.selectedEngine) || stringOrNull(engine.engine);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
function idsFrom(values, key = 'id') {
|
|
1740
|
+
return Array.isArray(values)
|
|
1741
|
+
? values.map(item => isPlainObject(item) ? stringOrNull(item[key]) : null).filter(Boolean).sort(compareStrings)
|
|
1742
|
+
: [];
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function summarizeObject(value, keys) {
|
|
1746
|
+
if (!isPlainObject(value)) return null;
|
|
1747
|
+
return Object.fromEntries(keys.map(key => [key, cloneData(value[key] ?? null)]));
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function hasDependencyCycle(subtasks) {
|
|
1751
|
+
const graph = new Map();
|
|
1752
|
+
for (const subtask of subtasks) {
|
|
1753
|
+
if (!isPlainObject(subtask) || typeof subtask.id !== 'string') continue;
|
|
1754
|
+
graph.set(subtask.id, Array.isArray(subtask.blockedBy) ? subtask.blockedBy.filter(id => typeof id === 'string') : []);
|
|
1755
|
+
}
|
|
1756
|
+
const visiting = new Set();
|
|
1757
|
+
const visited = new Set();
|
|
1758
|
+
const visit = id => {
|
|
1759
|
+
if (visiting.has(id)) return true;
|
|
1760
|
+
if (visited.has(id)) return false;
|
|
1761
|
+
visiting.add(id);
|
|
1762
|
+
for (const next of graph.get(id) || []) {
|
|
1763
|
+
if (visit(next)) return true;
|
|
1764
|
+
}
|
|
1765
|
+
visiting.delete(id);
|
|
1766
|
+
visited.add(id);
|
|
1767
|
+
return false;
|
|
1768
|
+
};
|
|
1769
|
+
return [...graph.keys()].some(visit);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function hasWriteFiles(subtask) {
|
|
1773
|
+
if (!isPlainObject(subtask)) return false;
|
|
1774
|
+
if (subtask.requires && subtask.requires.filesystemWrite === false) return false;
|
|
1775
|
+
return true;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function isSensitivePath(value) {
|
|
1779
|
+
const text = String(value).replace(/\\/g, '/').toLowerCase();
|
|
1780
|
+
return text === '.env' ||
|
|
1781
|
+
text.endsWith('/.env') ||
|
|
1782
|
+
text.includes('/.env/') ||
|
|
1783
|
+
text.includes('secret') ||
|
|
1784
|
+
text.includes('token') ||
|
|
1785
|
+
text.includes('credential') ||
|
|
1786
|
+
text.includes('password') ||
|
|
1787
|
+
text.includes('authorization') ||
|
|
1788
|
+
text.includes('cookie') ||
|
|
1789
|
+
text.includes('apikey') ||
|
|
1790
|
+
text.includes('api-key') ||
|
|
1791
|
+
text.startsWith('../') ||
|
|
1792
|
+
text.includes('/../') ||
|
|
1793
|
+
text.startsWith('/') ||
|
|
1794
|
+
/^[a-z]:\//i.test(text);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function normalizePathForCompare(value) {
|
|
1798
|
+
return String(value || '').replace(/\\/g, '/').replace(/\/+$/g, '').toLowerCase();
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
function extractConflicts(moduleResults, context) {
|
|
1802
|
+
const conflicts = firstDefined(moduleResults && moduleResults.conflicts, context && context.conflicts);
|
|
1803
|
+
return Array.isArray(conflicts) ? sortConflicts(conflicts) : [];
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
function sortConflicts(conflicts) {
|
|
1807
|
+
return (Array.isArray(conflicts) ? conflicts : [])
|
|
1808
|
+
.map(item => cloneData(item))
|
|
1809
|
+
.sort((a, b) => compareStrings(severityRank(a.severity), severityRank(b.severity)) ||
|
|
1810
|
+
compareStrings(a.type || '', b.type || '') ||
|
|
1811
|
+
compareStrings(a.file || a.path || '', b.file || b.path || '') ||
|
|
1812
|
+
compareStrings(canonicalStringify(a.subtaskIds || a.subtaskId || ''), canonicalStringify(b.subtaskIds || b.subtaskId || '')));
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function severityRank(severity) {
|
|
1816
|
+
return { critical: '0', high: '1', medium: '2', low: '3' }[severity] || '9';
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
function loopError(code, message, state, event, field, severity, provenanceValue = null) {
|
|
1820
|
+
return {
|
|
1821
|
+
code,
|
|
1822
|
+
message,
|
|
1823
|
+
state: state || null,
|
|
1824
|
+
event: event || null,
|
|
1825
|
+
field: field || null,
|
|
1826
|
+
severity,
|
|
1827
|
+
provenance: provenanceValue
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
function sortErrors(errors) {
|
|
1832
|
+
return (Array.isArray(errors) ? errors : []).map(item => cloneData(item)).sort((a, b) =>
|
|
1833
|
+
compareStrings(a.severity || '', b.severity || '') ||
|
|
1834
|
+
compareStrings(a.code || '', b.code || '') ||
|
|
1835
|
+
compareStrings(a.message || '', b.message || '')
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
function provenance(source, adapter, field, simulated) {
|
|
1840
|
+
return {
|
|
1841
|
+
source,
|
|
1842
|
+
adapter,
|
|
1843
|
+
field,
|
|
1844
|
+
simulated
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
function firstDefined(...values) {
|
|
1849
|
+
for (const value of values) {
|
|
1850
|
+
if (value !== undefined) return value;
|
|
1851
|
+
}
|
|
1852
|
+
return undefined;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
function uniqueMessages(values) {
|
|
1856
|
+
return [...new Set((Array.isArray(values) ? values : []).filter(value => typeof value === 'string'))].sort(compareStrings);
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function uniqueSortedStrings(values) {
|
|
1860
|
+
return [...new Set((Array.isArray(values) ? values : []).map(value => String(value)).filter(Boolean))].sort(compareStrings);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
function assertPlainApiObject(value, name) {
|
|
1864
|
+
if (!isPlainObject(value)) {
|
|
1865
|
+
throw new TypeError(`${name} must be an object`);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
function isPlainObject(value) {
|
|
1870
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
function isThenable(value) {
|
|
1874
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function';
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
function stringOrNull(value) {
|
|
1878
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
function errorMessage(error) {
|
|
1882
|
+
return error && typeof error.message === 'string' ? error.message : String(error);
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function redactSensitive(value, seen = new WeakMap(), parentKey = '') {
|
|
1886
|
+
if (Array.isArray(value)) {
|
|
1887
|
+
if (seen.has(value)) return '[Circular]';
|
|
1888
|
+
seen.set(value, true);
|
|
1889
|
+
return value.map(item => redactSensitive(item, seen, parentKey));
|
|
1890
|
+
}
|
|
1891
|
+
if (isPlainObject(value)) {
|
|
1892
|
+
if (seen.has(value)) return '[Circular]';
|
|
1893
|
+
seen.set(value, true);
|
|
1894
|
+
return Object.fromEntries(Object.keys(value).sort(compareStrings).map(key => {
|
|
1895
|
+
const sensitive = SENSITIVE_KEYS.some(fragment => key.toLowerCase().includes(fragment));
|
|
1896
|
+
return [key, sensitive ? '[REDACTED]' : redactSensitive(value[key], seen, key)];
|
|
1897
|
+
}));
|
|
1898
|
+
}
|
|
1899
|
+
if (typeof value === 'string' && isSensitiveTraceString(value)) {
|
|
1900
|
+
return '[REDACTED]';
|
|
1901
|
+
}
|
|
1902
|
+
return parentKey && SENSITIVE_KEYS.some(fragment => parentKey.toLowerCase().includes(fragment)) ? '[REDACTED]' : value;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
function isSensitiveTraceString(value) {
|
|
1906
|
+
const text = value.replace(/\\/g, '/').toLowerCase();
|
|
1907
|
+
return text === '.env' ||
|
|
1908
|
+
text.endsWith('/.env') ||
|
|
1909
|
+
text.includes('/.env/') ||
|
|
1910
|
+
text.includes('secret') ||
|
|
1911
|
+
text.includes('token') ||
|
|
1912
|
+
text.startsWith('../') ||
|
|
1913
|
+
text.includes('/../') ||
|
|
1914
|
+
text.startsWith('/') ||
|
|
1915
|
+
/^[a-z]:\//i.test(text);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function redactCycleScalar(value) {
|
|
1919
|
+
if (typeof value === 'string' && isSensitiveTraceString(value)) {
|
|
1920
|
+
return '[REDACTED]';
|
|
1921
|
+
}
|
|
1922
|
+
return value;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function cloneData(value, seen = new WeakMap()) {
|
|
1926
|
+
if (Array.isArray(value)) {
|
|
1927
|
+
if (seen.has(value)) return '[Circular]';
|
|
1928
|
+
seen.set(value, true);
|
|
1929
|
+
return value.map(item => cloneData(item, seen));
|
|
1930
|
+
}
|
|
1931
|
+
if (isPlainObject(value)) {
|
|
1932
|
+
if (seen.has(value)) return '[Circular]';
|
|
1933
|
+
seen.set(value, true);
|
|
1934
|
+
return Object.fromEntries(Object.keys(value).sort(compareStrings).map(key => [key, cloneData(value[key], seen)]));
|
|
1935
|
+
}
|
|
1936
|
+
if (typeof value === 'bigint') {
|
|
1937
|
+
return `${value.toString()}n`;
|
|
1938
|
+
}
|
|
1939
|
+
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
1940
|
+
return String(value);
|
|
1941
|
+
}
|
|
1942
|
+
return value;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
function canonicalStringify(value, seen = new WeakMap()) {
|
|
1946
|
+
if (Array.isArray(value)) {
|
|
1947
|
+
if (seen.has(value)) return '"[Circular]"';
|
|
1948
|
+
seen.set(value, true);
|
|
1949
|
+
return `[${value.map(item => canonicalStringify(item, seen)).join(',')}]`;
|
|
1950
|
+
}
|
|
1951
|
+
if (isPlainObject(value)) {
|
|
1952
|
+
if (seen.has(value)) return '"[Circular]"';
|
|
1953
|
+
seen.set(value, true);
|
|
1954
|
+
return `{${Object.keys(value).sort(compareStrings).map(key => `${JSON.stringify(key)}:${canonicalStringify(value[key], seen)}`).join(',')}}`;
|
|
1955
|
+
}
|
|
1956
|
+
if (typeof value === 'bigint') {
|
|
1957
|
+
return JSON.stringify(`${value.toString()}n`);
|
|
1958
|
+
}
|
|
1959
|
+
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
1960
|
+
return JSON.stringify(String(value));
|
|
1961
|
+
}
|
|
1962
|
+
return JSON.stringify(value);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function compareStrings(a, b) {
|
|
1966
|
+
const left = String(a);
|
|
1967
|
+
const right = String(b);
|
|
1968
|
+
if (left < right) return -1;
|
|
1969
|
+
if (left > right) return 1;
|
|
1970
|
+
return 0;
|
|
1971
|
+
}
|