@agwab/pi-workflow 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/compiler.d.ts +4 -6
- package/dist/compiler.js +70 -39
- package/dist/dynamic-decision.d.ts +0 -1
- package/dist/dynamic-decision.js +0 -7
- package/dist/dynamic-generated-task-runtime.d.ts +2 -0
- package/dist/dynamic-generated-task-runtime.js +21 -8
- package/dist/dynamic-profiles.d.ts +0 -1
- package/dist/dynamic-profiles.js +0 -3
- package/dist/engine-run-graph.d.ts +1 -0
- package/dist/engine-run-graph.js +142 -2
- package/dist/engine.d.ts +10 -6
- package/dist/engine.js +146 -77
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +38 -15
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/store.d.ts +3 -1
- package/dist/store.js +189 -49
- package/dist/subagent-backend.d.ts +4 -0
- package/dist/subagent-backend.js +281 -31
- package/dist/types.d.ts +9 -1
- package/dist/workflow-runtime.d.ts +2 -0
- package/dist/workflow-runtime.js +40 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +167 -48
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +84 -19
- package/docs/usage.md +11 -0
- package/node_modules/@agwab/pi-subagent/README.md +3 -3
- package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
- package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
- package/node_modules/@agwab/pi-subagent/package.json +2 -2
- package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
- package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
- package/node_modules/@agwab/pi-subagent/src/index.ts +995 -573
- package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
- package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
- package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
- package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
- package/node_modules/@agwab/pi-subagent/src/panel.ts +1352 -560
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
- package/package.json +2 -2
- package/src/compiler.ts +127 -66
- package/src/dynamic-decision.ts +0 -11
- package/src/dynamic-generated-task-runtime.ts +47 -12
- package/src/dynamic-profiles.ts +0 -4
- package/src/engine-run-graph.ts +185 -2
- package/src/engine.ts +192 -107
- package/src/extension.ts +50 -17
- package/src/index.ts +3 -1
- package/src/store.ts +253 -55
- package/src/subagent-backend.ts +369 -32
- package/src/types.ts +13 -1
- package/src/workflow-runtime.ts +53 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +621 -228
- package/src/workflow-web-source.ts +118 -28
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +56 -16
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +1 -1
- package/workflows/deep-research/helpers/render-executive.mjs +8 -21
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +0 -1
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +4 -1
- package/workflows/impact-review/spec.json +3 -3
- package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
- package/dist/dynamic-loader.d.ts +0 -25
- package/dist/dynamic-loader.js +0 -13
- package/src/dynamic-loader.ts +0 -49
- package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
package/src/engine.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, extname, join,
|
|
1
|
+
import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, extname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
4
|
import { Worker } from "node:worker_threads";
|
|
5
5
|
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
toProjectPath,
|
|
24
24
|
updateIndex,
|
|
25
25
|
withRunLease,
|
|
26
|
-
|
|
26
|
+
workflowRunPath,
|
|
27
27
|
writeJsonAtomic,
|
|
28
28
|
writeRunRecord,
|
|
29
29
|
writeCompiledRunArtifact,
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
import {
|
|
41
41
|
readSimpleJsonPath,
|
|
42
42
|
type WorkflowModelInfo,
|
|
43
|
+
type WorkflowRuntimeDefaults,
|
|
43
44
|
} from "./workflow-runtime.js";
|
|
44
45
|
import {
|
|
45
46
|
dynamicRunDir,
|
|
@@ -74,7 +75,6 @@ import {
|
|
|
74
75
|
isDynamicCompiledTaskPayload,
|
|
75
76
|
normalizeDynamicAgentRequest,
|
|
76
77
|
readDynamicGeneratedTaskResult,
|
|
77
|
-
type DynamicAgentRequest,
|
|
78
78
|
} from "./dynamic-generated-task-runtime.js";
|
|
79
79
|
import {
|
|
80
80
|
optionalEventString,
|
|
@@ -96,6 +96,7 @@ import {
|
|
|
96
96
|
markDagDependentsSkipped,
|
|
97
97
|
nextTaskRecordIndex,
|
|
98
98
|
reconcileDynamicGeneratedRunRecords,
|
|
99
|
+
reconcileForeachGeneratedRunRecords,
|
|
99
100
|
recoverStaleRunningDynamicControllers,
|
|
100
101
|
replaceDependencyList,
|
|
101
102
|
sourceStageIdsForFrom,
|
|
@@ -116,10 +117,6 @@ import {
|
|
|
116
117
|
readSupportSources,
|
|
117
118
|
writeArtifactGraphDynamicResult,
|
|
118
119
|
} from "./artifact-graph-runtime.js";
|
|
119
|
-
import {
|
|
120
|
-
isDynamicOutputProfile,
|
|
121
|
-
type DynamicOutputProfile,
|
|
122
|
-
} from "./dynamic-profiles.js";
|
|
123
120
|
import {
|
|
124
121
|
DIRECT_DYNAMIC_RUNTIME_VERSION,
|
|
125
122
|
ensureDirectDynamicRuntimeBundle,
|
|
@@ -128,7 +125,6 @@ import {
|
|
|
128
125
|
type CompiledDynamicWorkflowTask,
|
|
129
126
|
type CompiledTask,
|
|
130
127
|
type CompiledWorkflow,
|
|
131
|
-
type ThinkingLevel,
|
|
132
128
|
WORKFLOW_RUN_TYPE,
|
|
133
129
|
type WorkflowIndexRecord,
|
|
134
130
|
type WorkflowRunRecord,
|
|
@@ -151,10 +147,14 @@ const DYNAMIC_CONTROLLER_ENGINE_CAPABILITIES = Object.freeze({
|
|
|
151
147
|
const DYNAMIC_CONTROLLER_ENGINE_INTEGRITY_ERROR_MESSAGE =
|
|
152
148
|
"incompatible or stale pi-workflow engine: dynamic controller context is missing runDecisionLoop (rebuild dist / reload workflow engine)";
|
|
153
149
|
const supervisorTimers = new Map<string, ReturnType<typeof setInterval>>();
|
|
150
|
+
const supervisorRunMtimes = new Map<string, number>();
|
|
151
|
+
const supervisorErrorCounts = new Map<string, number>();
|
|
152
|
+
const MAX_SUPERVISOR_CONSECUTIVE_ERRORS = 3;
|
|
154
153
|
|
|
155
154
|
export interface WorkflowRunOptions {
|
|
156
155
|
task?: string;
|
|
157
|
-
|
|
156
|
+
runtimeOverrides?: WorkflowRuntimeDefaults;
|
|
157
|
+
runtimeDefaults?: WorkflowRuntimeDefaults;
|
|
158
158
|
availableModels?: WorkflowModelInfo[];
|
|
159
159
|
dynamicUi?: DynamicWorkflowUi;
|
|
160
160
|
runId?: string;
|
|
@@ -163,6 +163,7 @@ export interface WorkflowRunOptions {
|
|
|
163
163
|
|
|
164
164
|
interface WorkflowScheduleOptions {
|
|
165
165
|
dynamicUi?: DynamicWorkflowUi;
|
|
166
|
+
availableModels?: WorkflowModelInfo[];
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
export async function runWorkflowSpec(
|
|
@@ -207,6 +208,7 @@ async function runLoadedWorkflowSpec(
|
|
|
207
208
|
cwd,
|
|
208
209
|
specPath,
|
|
209
210
|
task: options.task,
|
|
211
|
+
runtimeOverrides: options.runtimeOverrides,
|
|
210
212
|
runtimeDefaults: options.runtimeDefaults,
|
|
211
213
|
availableModels: options.availableModels,
|
|
212
214
|
});
|
|
@@ -222,12 +224,15 @@ async function runLoadedWorkflowSpec(
|
|
|
222
224
|
await writeRunRecord(cwd, run);
|
|
223
225
|
});
|
|
224
226
|
|
|
227
|
+
const scheduleOptions = {
|
|
228
|
+
dynamicUi: options.dynamicUi,
|
|
229
|
+
availableModels: options.availableModels,
|
|
230
|
+
};
|
|
225
231
|
const scheduled =
|
|
226
|
-
(await scheduleRun(cwd, run.runId, compiled,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
watchRun(cwd, scheduled.runId, { dynamicUi: options.dynamicUi });
|
|
232
|
+
(await scheduleRun(cwd, run.runId, compiled, scheduleOptions)) ??
|
|
233
|
+
(await readRunRecord(cwd, run.runId));
|
|
234
|
+
if (shouldWatchRun(scheduled))
|
|
235
|
+
watchRun(cwd, scheduled.runId, scheduleOptions);
|
|
231
236
|
return scheduled;
|
|
232
237
|
}
|
|
233
238
|
|
|
@@ -243,6 +248,22 @@ export async function refreshRun(
|
|
|
243
248
|
return refreshed ?? current;
|
|
244
249
|
}
|
|
245
250
|
|
|
251
|
+
function hasActiveSchedulerWork(
|
|
252
|
+
run: Pick<WorkflowRunRecord, "status" | "taskSummary">,
|
|
253
|
+
): boolean {
|
|
254
|
+
return (
|
|
255
|
+
run.status === "running" ||
|
|
256
|
+
run.taskSummary.running > 0 ||
|
|
257
|
+
run.taskSummary.pending > 0
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function shouldWatchRun(
|
|
262
|
+
run: Pick<WorkflowRunRecord, "status" | "taskSummary">,
|
|
263
|
+
): boolean {
|
|
264
|
+
return hasActiveSchedulerWork(run);
|
|
265
|
+
}
|
|
266
|
+
|
|
246
267
|
export async function waitForRun(
|
|
247
268
|
cwd: string,
|
|
248
269
|
runIdOrPrefix: string,
|
|
@@ -253,7 +274,7 @@ export async function waitForRun(
|
|
|
253
274
|
const deadline = Date.now() + timeout;
|
|
254
275
|
let run = await refreshRun(cwd, runIdOrPrefix);
|
|
255
276
|
|
|
256
|
-
while (run
|
|
277
|
+
while (hasActiveSchedulerWork(run)) {
|
|
257
278
|
const beforeScheduleRemaining = deadline - Date.now();
|
|
258
279
|
if (beforeScheduleRemaining <= 0)
|
|
259
280
|
throw new Error(
|
|
@@ -263,7 +284,7 @@ export async function waitForRun(
|
|
|
263
284
|
run = await refreshRun(cwd, run.runId);
|
|
264
285
|
const remaining = deadline - Date.now();
|
|
265
286
|
if (remaining <= 0) {
|
|
266
|
-
if (run
|
|
287
|
+
if (!hasActiveSchedulerWork(run)) return run;
|
|
267
288
|
throw new Error(
|
|
268
289
|
`Flow run still running after ${timeout}ms: ${run.runId}`,
|
|
269
290
|
);
|
|
@@ -280,6 +301,48 @@ export interface ResumeRunSummary {
|
|
|
280
301
|
resetTaskIds: string[];
|
|
281
302
|
}
|
|
282
303
|
|
|
304
|
+
export interface StopRunSummary {
|
|
305
|
+
run: WorkflowRunRecord;
|
|
306
|
+
interruptedTaskIds: string[];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function stopRun(
|
|
310
|
+
cwd: string,
|
|
311
|
+
runIdOrPrefix: string,
|
|
312
|
+
): Promise<StopRunSummary> {
|
|
313
|
+
const current = await readRunRecord(cwd, runIdOrPrefix);
|
|
314
|
+
const stopped = await withRunLease(cwd, current.runId, async () => {
|
|
315
|
+
const run = await readRunRecord(cwd, current.runId);
|
|
316
|
+
if (isTerminalWorkflowStatus(run.status)) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`stop requires a non-terminal run; ${run.runId} is ${run.status}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
await resolveWorkflowBackend(run)
|
|
322
|
+
.cleanupRun(cwd, run)
|
|
323
|
+
.catch(() => undefined);
|
|
324
|
+
const interruptedTaskIds: string[] = [];
|
|
325
|
+
for (const task of run.tasks) {
|
|
326
|
+
if (
|
|
327
|
+
setTaskTerminal(task, "interrupted", "workflow_stopped", {
|
|
328
|
+
exitCode: 130,
|
|
329
|
+
lastMessage: "Workflow stopped by user request",
|
|
330
|
+
})
|
|
331
|
+
) {
|
|
332
|
+
interruptedTaskIds.push(task.taskId);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
await writeRunRecord(cwd, run);
|
|
336
|
+
unwatchRun(cwd, run.runId);
|
|
337
|
+
return { run, interruptedTaskIds };
|
|
338
|
+
});
|
|
339
|
+
if (!stopped)
|
|
340
|
+
throw new Error(
|
|
341
|
+
`Could not acquire workflow run lease for ${current.runId}`,
|
|
342
|
+
);
|
|
343
|
+
return stopped;
|
|
344
|
+
}
|
|
345
|
+
|
|
283
346
|
export async function resumeRun(
|
|
284
347
|
cwd: string,
|
|
285
348
|
runIdOrPrefix: string,
|
|
@@ -312,6 +375,9 @@ export async function resumeRun(
|
|
|
312
375
|
const resetTaskIds: string[] = [];
|
|
313
376
|
const updated = await withRunLease(cwd, current.runId, async () => {
|
|
314
377
|
const run = await readRunRecord(cwd, current.runId);
|
|
378
|
+
await resolveWorkflowBackend(run)
|
|
379
|
+
.cleanupRun(cwd, run)
|
|
380
|
+
.catch(() => undefined);
|
|
315
381
|
for (const task of run.tasks) {
|
|
316
382
|
if (resetTaskForResume(task)) resetTaskIds.push(task.taskId);
|
|
317
383
|
}
|
|
@@ -330,7 +396,7 @@ export async function resumeRun(
|
|
|
330
396
|
const scheduled =
|
|
331
397
|
(await scheduleRun(cwd, current.runId, undefined, options)) ??
|
|
332
398
|
(await readRunRecord(cwd, current.runId));
|
|
333
|
-
if (scheduled
|
|
399
|
+
if (shouldWatchRun(scheduled)) watchRun(cwd, scheduled.runId, options);
|
|
334
400
|
return { run: scheduled, resetTaskIds };
|
|
335
401
|
}
|
|
336
402
|
|
|
@@ -341,7 +407,7 @@ export async function resumeSupervisors(
|
|
|
341
407
|
try {
|
|
342
408
|
const runs = await listRunRecords(cwd);
|
|
343
409
|
for (const run of runs) {
|
|
344
|
-
if (run
|
|
410
|
+
if (hasActiveSchedulerWork(run)) {
|
|
345
411
|
await scheduleRun(cwd, run.runId, undefined, options).catch((error) =>
|
|
346
412
|
recordSupervisorError(cwd, run.runId, error),
|
|
347
413
|
);
|
|
@@ -356,6 +422,15 @@ export async function resumeSupervisors(
|
|
|
356
422
|
}
|
|
357
423
|
}
|
|
358
424
|
|
|
425
|
+
function unwatchRun(cwd: string, runId: string): void {
|
|
426
|
+
const key = `${cwd}\0${runId}`;
|
|
427
|
+
const existing = supervisorTimers.get(key);
|
|
428
|
+
if (existing) clearInterval(existing);
|
|
429
|
+
supervisorTimers.delete(key);
|
|
430
|
+
supervisorRunMtimes.delete(key);
|
|
431
|
+
supervisorErrorCounts.delete(key);
|
|
432
|
+
}
|
|
433
|
+
|
|
359
434
|
export function watchRun(
|
|
360
435
|
cwd: string,
|
|
361
436
|
runId: string,
|
|
@@ -366,17 +441,36 @@ export function watchRun(
|
|
|
366
441
|
|
|
367
442
|
const timer = setInterval(() => {
|
|
368
443
|
void (async () => {
|
|
444
|
+
const previousMtime = supervisorRunMtimes.get(key);
|
|
445
|
+
const beforeMtime = await readRunMtimeMs(cwd, runId);
|
|
369
446
|
const refreshed = await refreshRun(cwd, runId);
|
|
370
|
-
|
|
371
|
-
|
|
447
|
+
const afterMtime = await readRunMtimeMs(cwd, runId);
|
|
448
|
+
const currentMtime = afterMtime ?? beforeMtime;
|
|
449
|
+
if (currentMtime !== undefined)
|
|
450
|
+
supervisorRunMtimes.set(key, currentMtime);
|
|
451
|
+
supervisorErrorCounts.delete(key);
|
|
452
|
+
|
|
453
|
+
if (hasActiveSchedulerWork(refreshed)) {
|
|
454
|
+
const unchanged =
|
|
455
|
+
previousMtime !== undefined &&
|
|
456
|
+
currentMtime !== undefined &&
|
|
457
|
+
currentMtime <= previousMtime;
|
|
458
|
+
if (!unchanged) await scheduleRun(cwd, runId, undefined, options);
|
|
372
459
|
return;
|
|
373
460
|
}
|
|
374
461
|
|
|
375
|
-
|
|
376
|
-
if (existing) clearInterval(existing);
|
|
377
|
-
supervisorTimers.delete(key);
|
|
462
|
+
unwatchRun(cwd, runId);
|
|
378
463
|
})().catch((error) => {
|
|
379
|
-
|
|
464
|
+
if (isMissingRunError(error)) {
|
|
465
|
+
unwatchRun(cwd, runId);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const failures = (supervisorErrorCounts.get(key) ?? 0) + 1;
|
|
469
|
+
supervisorErrorCounts.set(key, failures);
|
|
470
|
+
void recordSupervisorError(cwd, runId, error).finally(() => {
|
|
471
|
+
if (failures >= MAX_SUPERVISOR_CONSECUTIVE_ERRORS)
|
|
472
|
+
unwatchRun(cwd, runId);
|
|
473
|
+
});
|
|
380
474
|
});
|
|
381
475
|
}, POLL_INTERVAL_MS);
|
|
382
476
|
|
|
@@ -384,6 +478,29 @@ export function watchRun(
|
|
|
384
478
|
supervisorTimers.set(key, timer);
|
|
385
479
|
}
|
|
386
480
|
|
|
481
|
+
async function readRunMtimeMs(
|
|
482
|
+
cwd: string,
|
|
483
|
+
runId: string,
|
|
484
|
+
): Promise<number | undefined> {
|
|
485
|
+
try {
|
|
486
|
+
return (await stat(workflowRunPath(cwd, runId))).mtimeMs;
|
|
487
|
+
} catch (error) {
|
|
488
|
+
if (isEnoentError(error)) return undefined;
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function isEnoentError(error: unknown): boolean {
|
|
494
|
+
return (error as NodeJS.ErrnoException | undefined)?.code === "ENOENT";
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function isMissingRunError(error: unknown): boolean {
|
|
498
|
+
return (
|
|
499
|
+
isEnoentError(error) ||
|
|
500
|
+
(error instanceof Error && /^Flow run not found: /.test(error.message))
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
387
504
|
export async function scheduleRun(
|
|
388
505
|
cwd: string,
|
|
389
506
|
runId: string,
|
|
@@ -393,7 +510,12 @@ export async function scheduleRun(
|
|
|
393
510
|
return withRunLease(cwd, runId, async () => {
|
|
394
511
|
let run = await readRunRecord(cwd, runId);
|
|
395
512
|
run = await resolveWorkflowBackend(run).refreshRun(cwd, run);
|
|
396
|
-
if (
|
|
513
|
+
if (isTerminalWorkflowStatus(run.status)) return run;
|
|
514
|
+
if (
|
|
515
|
+
run.taskSummary.blocked > 0 &&
|
|
516
|
+
run.taskSummary.pending === 0 &&
|
|
517
|
+
run.taskSummary.running === 0
|
|
518
|
+
)
|
|
397
519
|
return run;
|
|
398
520
|
|
|
399
521
|
const compiledFlow =
|
|
@@ -492,7 +614,7 @@ export function formatRun(
|
|
|
492
614
|
async function reconcileActiveRuns(cwd: string): Promise<void> {
|
|
493
615
|
const runs = await listRunRecords(cwd);
|
|
494
616
|
for (const run of runs) {
|
|
495
|
-
if (run
|
|
617
|
+
if (hasActiveSchedulerWork(run))
|
|
496
618
|
await refreshRun(cwd, run.runId).catch((error) =>
|
|
497
619
|
recordSupervisorError(cwd, run.runId, error),
|
|
498
620
|
);
|
|
@@ -504,7 +626,7 @@ async function reconcileIndexedActiveRuns(
|
|
|
504
626
|
index: WorkflowIndexRecord,
|
|
505
627
|
): Promise<void> {
|
|
506
628
|
for (const run of index.runs) {
|
|
507
|
-
if (run
|
|
629
|
+
if (hasActiveSchedulerWork(run))
|
|
508
630
|
await refreshRun(cwd, run.runId).catch((error) =>
|
|
509
631
|
recordSupervisorError(cwd, run.runId, error),
|
|
510
632
|
);
|
|
@@ -543,6 +665,16 @@ async function scheduleDag(
|
|
|
543
665
|
compiledFlow,
|
|
544
666
|
);
|
|
545
667
|
if (loopReconciled) return;
|
|
668
|
+
const foreachReconciled = reconcileForeachGeneratedRunRecords(
|
|
669
|
+
cwd,
|
|
670
|
+
run,
|
|
671
|
+
compiledFlow,
|
|
672
|
+
);
|
|
673
|
+
if (foreachReconciled) {
|
|
674
|
+
await writeJsonAtomic(compiledWorkflowPath(cwd, run.runId), compiledFlow);
|
|
675
|
+
await writeRunRecord(cwd, run);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
546
678
|
const dynamicReconciled = reconcileDynamicGeneratedRunRecords(
|
|
547
679
|
cwd,
|
|
548
680
|
run,
|
|
@@ -628,7 +760,7 @@ async function scheduleDag(
|
|
|
628
760
|
index,
|
|
629
761
|
options,
|
|
630
762
|
);
|
|
631
|
-
if (launched) running += 1;
|
|
763
|
+
if (launched && run.tasks[index]?.status === "running") running += 1;
|
|
632
764
|
}
|
|
633
765
|
}
|
|
634
766
|
|
|
@@ -788,6 +920,17 @@ async function materializeForeachTask(
|
|
|
788
920
|
|
|
789
921
|
const placeholderSpecId = template.id;
|
|
790
922
|
const generatedSpecIds = generated.tasks.map((task) => task.id);
|
|
923
|
+
const hasDownstreamDependents = compiledFlow.tasks.some(
|
|
924
|
+
(task, taskIndex) =>
|
|
925
|
+
taskIndex !== index && (task.dependsOn ?? []).includes(placeholderSpecId),
|
|
926
|
+
);
|
|
927
|
+
if (generatedSpecIds.length === 0 && !hasDownstreamDependents) {
|
|
928
|
+
setTaskTerminal(templateRunTask, "completed", "foreach_empty", {
|
|
929
|
+
lastMessage: "foreach produced 0 item(s)",
|
|
930
|
+
});
|
|
931
|
+
await writeRunRecord(cwd, run);
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
791
934
|
compiledFlow.tasks.splice(index, 1, ...generated.tasks);
|
|
792
935
|
updateDownstreamDependencies(
|
|
793
936
|
compiledFlow,
|
|
@@ -880,12 +1023,15 @@ async function launchPendingTaskAt(
|
|
|
880
1023
|
return false;
|
|
881
1024
|
}
|
|
882
1025
|
|
|
883
|
-
let launchTask
|
|
884
|
-
|
|
885
|
-
launchTask = await prepareArtifactGraphRetryTask(cwd, task, launchTask);
|
|
886
|
-
}
|
|
887
|
-
|
|
1026
|
+
let launchTask: CompiledWorkflow["tasks"][number] | undefined;
|
|
1027
|
+
let prepareComplete = false;
|
|
888
1028
|
try {
|
|
1029
|
+
launchTask = await prepareDagTask(cwd, run, compiledFlow, index);
|
|
1030
|
+
if (task.outputRetry) {
|
|
1031
|
+
launchTask = await prepareArtifactGraphRetryTask(cwd, task, launchTask);
|
|
1032
|
+
}
|
|
1033
|
+
prepareComplete = true;
|
|
1034
|
+
|
|
889
1035
|
if (launchTask.kind === "support") {
|
|
890
1036
|
return await executeSupportTask(cwd, run, task, launchTask);
|
|
891
1037
|
}
|
|
@@ -913,10 +1059,11 @@ async function launchPendingTaskAt(
|
|
|
913
1059
|
if (launch.kind === "fatal") throw new Error(launch.message);
|
|
914
1060
|
return launch.kind === "launched";
|
|
915
1061
|
} catch (error) {
|
|
916
|
-
const statusDetail =
|
|
917
|
-
|
|
1062
|
+
const statusDetail = !prepareComplete
|
|
1063
|
+
? "prepare_failed"
|
|
1064
|
+
: launchTask?.kind === "support"
|
|
918
1065
|
? "support_failed"
|
|
919
|
-
: launchTask
|
|
1066
|
+
: launchTask?.safety.requiresWorktree
|
|
920
1067
|
? "worktree_failed"
|
|
921
1068
|
: "launch_failed";
|
|
922
1069
|
setTaskTerminal(task, "failed", statusDetail, {
|
|
@@ -1047,6 +1194,7 @@ async function executeDynamicControllerTask(
|
|
|
1047
1194
|
sources,
|
|
1048
1195
|
dynamic: compiledTask.dynamic,
|
|
1049
1196
|
dynamicUi: options.dynamicUi,
|
|
1197
|
+
availableModels: options.availableModels,
|
|
1050
1198
|
});
|
|
1051
1199
|
await assertDynamicGeneratedTasksSettled({
|
|
1052
1200
|
cwd,
|
|
@@ -1056,6 +1204,7 @@ async function executeDynamicControllerTask(
|
|
|
1056
1204
|
controllerTask: task,
|
|
1057
1205
|
controllerCompiledTask: compiledTask,
|
|
1058
1206
|
dynamic: compiledTask.dynamic,
|
|
1207
|
+
availableModels: options.availableModels,
|
|
1059
1208
|
});
|
|
1060
1209
|
await recordActiveRuntime();
|
|
1061
1210
|
const unrunBranchBlockers = await dynamicUnrunBranchBlockers(
|
|
@@ -1188,6 +1337,7 @@ async function runDynamicControllerWorker(input: {
|
|
|
1188
1337
|
sources: Record<string, unknown>;
|
|
1189
1338
|
dynamic: CompiledDynamicWorkflowTask;
|
|
1190
1339
|
dynamicUi?: DynamicWorkflowUi;
|
|
1340
|
+
availableModels?: WorkflowModelInfo[];
|
|
1191
1341
|
}): Promise<unknown> {
|
|
1192
1342
|
const resolved = await resolveWorkflowHelperRef(
|
|
1193
1343
|
input.dynamic.uses,
|
|
@@ -1810,65 +1960,6 @@ function requiredDynamicString(
|
|
|
1810
1960
|
return value.trim();
|
|
1811
1961
|
}
|
|
1812
1962
|
|
|
1813
|
-
function optionalDynamicString(
|
|
1814
|
-
value: unknown,
|
|
1815
|
-
field: string,
|
|
1816
|
-
): string | undefined {
|
|
1817
|
-
if (value === undefined) return undefined;
|
|
1818
|
-
return requiredDynamicString(value, field);
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
function optionalDynamicStringArray(
|
|
1822
|
-
value: unknown,
|
|
1823
|
-
field: string,
|
|
1824
|
-
): string[] | undefined {
|
|
1825
|
-
if (value === undefined) return undefined;
|
|
1826
|
-
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
1827
|
-
throw new Error(`ctx.agent() ${field} must be an array of strings`);
|
|
1828
|
-
}
|
|
1829
|
-
return value.map((item) => item.trim()).filter(Boolean);
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
function isPlainDynamicRecord(
|
|
1833
|
-
value: unknown,
|
|
1834
|
-
): value is Record<string, unknown> {
|
|
1835
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
function optionalDynamicPositiveInteger(
|
|
1839
|
-
value: unknown,
|
|
1840
|
-
field: string,
|
|
1841
|
-
): number | undefined {
|
|
1842
|
-
if (value === undefined) return undefined;
|
|
1843
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
1844
|
-
throw new Error(`ctx.agent() ${field} must be a positive integer`);
|
|
1845
|
-
}
|
|
1846
|
-
return value;
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
function requiredDynamicOutputProfile(
|
|
1850
|
-
value: unknown,
|
|
1851
|
-
field: string,
|
|
1852
|
-
api: string,
|
|
1853
|
-
): DynamicOutputProfile {
|
|
1854
|
-
const profile = requiredDynamicString(value, field, api);
|
|
1855
|
-
if (!isDynamicOutputProfile(profile)) {
|
|
1856
|
-
throw new Error(`${api} ${field} has an unsupported output profile`);
|
|
1857
|
-
}
|
|
1858
|
-
return profile;
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
function requiredDynamicNonNegativeInteger(
|
|
1862
|
-
value: unknown,
|
|
1863
|
-
field: string,
|
|
1864
|
-
api: string,
|
|
1865
|
-
): number {
|
|
1866
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
1867
|
-
throw new Error(`${api} ${field} must be a non-negative integer`);
|
|
1868
|
-
}
|
|
1869
|
-
return value;
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
1963
|
function requiredDynamicPositiveInteger(
|
|
1873
1964
|
value: unknown,
|
|
1874
1965
|
field: string,
|
|
@@ -1880,17 +1971,6 @@ function requiredDynamicPositiveInteger(
|
|
|
1880
1971
|
return value;
|
|
1881
1972
|
}
|
|
1882
1973
|
|
|
1883
|
-
function optionalDynamicStringField(value: unknown): string | undefined {
|
|
1884
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
function optionalDynamicOutputProfile(
|
|
1888
|
-
value: unknown,
|
|
1889
|
-
): DynamicOutputProfile | undefined {
|
|
1890
|
-
if (value === undefined) return undefined;
|
|
1891
|
-
return requiredDynamicOutputProfile(value, "outputProfile", "ctx.agent()");
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
1974
|
async function currentDynamicBudgetRemaining(input: {
|
|
1895
1975
|
cwd: string;
|
|
1896
1976
|
run: WorkflowRunRecord;
|
|
@@ -2263,6 +2343,7 @@ async function assertDynamicGeneratedTasksSettled(input: {
|
|
|
2263
2343
|
controllerTask: WorkflowTaskRunRecord;
|
|
2264
2344
|
controllerCompiledTask: CompiledTask;
|
|
2265
2345
|
dynamic: CompiledDynamicWorkflowTask;
|
|
2346
|
+
availableModels?: WorkflowModelInfo[];
|
|
2266
2347
|
}): Promise<void> {
|
|
2267
2348
|
const state = await readOrRebuildDynamicState(input.cwd, input.run.runId);
|
|
2268
2349
|
const generatedTaskIds =
|
|
@@ -2289,6 +2370,7 @@ async function repairMissingDynamicGeneratedTask(
|
|
|
2289
2370
|
controllerTask: WorkflowTaskRunRecord;
|
|
2290
2371
|
controllerCompiledTask: CompiledTask;
|
|
2291
2372
|
dynamic: CompiledDynamicWorkflowTask;
|
|
2373
|
+
availableModels?: WorkflowModelInfo[];
|
|
2292
2374
|
},
|
|
2293
2375
|
specId: string,
|
|
2294
2376
|
): Promise<WorkflowTaskRunRecord> {
|
|
@@ -2325,6 +2407,7 @@ async function repairMissingDynamicGeneratedTask(
|
|
|
2325
2407
|
branchId: optionalEventString(event.payload.branchId),
|
|
2326
2408
|
request,
|
|
2327
2409
|
dynamic: input.dynamic,
|
|
2410
|
+
availableModels: input.availableModels,
|
|
2328
2411
|
});
|
|
2329
2412
|
assertDynamicGeneratedMetadataMatches(compiledTask, {
|
|
2330
2413
|
controllerSpecId: input.controllerTask.specId,
|
|
@@ -2374,6 +2457,7 @@ async function runDynamicAgentRequest(input: {
|
|
|
2374
2457
|
request: unknown;
|
|
2375
2458
|
generatedTaskIds: string[];
|
|
2376
2459
|
isSettled?: () => boolean;
|
|
2460
|
+
availableModels?: WorkflowModelInfo[];
|
|
2377
2461
|
}): Promise<unknown> {
|
|
2378
2462
|
await assertDynamicRuntimeBudgetAvailable({
|
|
2379
2463
|
cwd: input.cwd,
|
|
@@ -2459,6 +2543,7 @@ async function runDynamicAgentRequest(input: {
|
|
|
2459
2543
|
branchId: generationBranchId,
|
|
2460
2544
|
request: generationRequest,
|
|
2461
2545
|
dynamic: input.dynamic,
|
|
2546
|
+
availableModels: input.availableModels,
|
|
2462
2547
|
});
|
|
2463
2548
|
assertDynamicGeneratedMetadataMatches(compiledTask, {
|
|
2464
2549
|
controllerSpecId: input.controllerTask.specId,
|