@agwab/pi-workflow 0.2.1 → 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/dist/compiler.js +6 -8
- package/dist/dynamic-decision.d.ts +0 -1
- package/dist/dynamic-decision.js +0 -7
- 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 +5 -0
- package/dist/engine.js +112 -27
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +27 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/store.js +55 -11
- package/dist/subagent-backend.js +155 -29
- package/dist/types.d.ts +6 -0
- package/dist/workflow-runtime.js +10 -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/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 +14 -9
- package/src/dynamic-decision.ts +0 -11
- package/src/dynamic-profiles.ts +0 -4
- package/src/engine-run-graph.ts +185 -2
- package/src/engine.ts +145 -24
- package/src/extension.ts +33 -4
- package/src/index.ts +3 -1
- package/src/store.ts +74 -11
- package/src/subagent-backend.ts +201 -28
- package/src/types.ts +6 -0
- package/src/workflow-runtime.ts +18 -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/subagent-backend.ts
CHANGED
|
@@ -57,7 +57,12 @@ const LEGACY_FETCH_CACHE_ENV = "PI_WORKFLOW_FETCH_CACHE";
|
|
|
57
57
|
const DEFAULT_TRANSIENT_MODEL_FAILURE_RETRIES = 5;
|
|
58
58
|
const DEFAULT_ARTIFACT_OUTPUT_RETRIES = 2;
|
|
59
59
|
const MAX_CONCURRENT_LAUNCHES_ENV = "PI_WORKFLOW_MAX_CONCURRENT_LAUNCHES";
|
|
60
|
+
const PARENT_SUBAGENT_CWD_ENV = "PI_WORKFLOW_PARENT_SUBAGENT_CWD";
|
|
61
|
+
const PARENT_SUBAGENT_RUNS_DIR_ENV = "PI_WORKFLOW_PARENT_SUBAGENT_RUNS_DIR";
|
|
62
|
+
const PARENT_SUBAGENT_RUN_ID_ENV = "PI_WORKFLOW_PARENT_SUBAGENT_RUN_ID";
|
|
63
|
+
const PARENT_SUBAGENT_ATTEMPT_ID_ENV = "PI_WORKFLOW_PARENT_SUBAGENT_ATTEMPT_ID";
|
|
60
64
|
const DEFAULT_LAUNCH_SLOT_RELEASE_DELAY_MS = 3_000;
|
|
65
|
+
const STALE_LAUNCH_CLAIM_GRACE_MS = 30_000;
|
|
61
66
|
const MIN_TRANSIENT_RETRY_JITTER_MS = 1_000;
|
|
62
67
|
const MAX_TRANSIENT_RETRY_JITTER_MS = 5_000;
|
|
63
68
|
const MODULE_PATH = fileURLToPath(import.meta.url);
|
|
@@ -161,8 +166,31 @@ interface SubagentApi {
|
|
|
161
166
|
): Promise<SubagentRunStatusSnapshot | null>;
|
|
162
167
|
interruptSubagent(options: Record<string, unknown>): Promise<unknown>;
|
|
163
168
|
reconcileSubagentRun(options: Record<string, unknown>): Promise<unknown>;
|
|
169
|
+
recordSubagentChildEvent?(
|
|
170
|
+
options: Record<string, unknown>,
|
|
171
|
+
): Promise<unknown>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
type ParentSubagentChildEvent =
|
|
175
|
+
| "started"
|
|
176
|
+
| "completed"
|
|
177
|
+
| "failed"
|
|
178
|
+
| "cancelled";
|
|
179
|
+
|
|
180
|
+
interface ParentSubagentRef {
|
|
181
|
+
cwd: string;
|
|
182
|
+
runsDir: string;
|
|
183
|
+
runId: string;
|
|
184
|
+
attemptId?: string;
|
|
164
185
|
}
|
|
165
186
|
|
|
187
|
+
const GENERIC_TASK_STATUS_DETAILS = new Set([
|
|
188
|
+
"completed",
|
|
189
|
+
"failed",
|
|
190
|
+
"interrupted",
|
|
191
|
+
"running",
|
|
192
|
+
]);
|
|
193
|
+
|
|
166
194
|
const subagentApiSpecifier = "@agwab/pi-subagent/api";
|
|
167
195
|
let cachedSubagentApi: Promise<SubagentApi> | undefined;
|
|
168
196
|
let injectedSubagentApi: SubagentApi | undefined;
|
|
@@ -180,6 +208,85 @@ async function loadSubagentApi(): Promise<SubagentApi> {
|
|
|
180
208
|
return cachedSubagentApi;
|
|
181
209
|
}
|
|
182
210
|
|
|
211
|
+
function nonEmptyEnv(
|
|
212
|
+
env: Record<string, string | undefined>,
|
|
213
|
+
key: string,
|
|
214
|
+
): string | undefined {
|
|
215
|
+
const value = env[key]?.trim();
|
|
216
|
+
return value ? value : undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parentSubagentRefFromEnv(
|
|
220
|
+
env: Record<string, string | undefined> = process.env,
|
|
221
|
+
): ParentSubagentRef | undefined {
|
|
222
|
+
const cwd = nonEmptyEnv(env, PARENT_SUBAGENT_CWD_ENV);
|
|
223
|
+
const runsDir = nonEmptyEnv(env, PARENT_SUBAGENT_RUNS_DIR_ENV);
|
|
224
|
+
const runId = nonEmptyEnv(env, PARENT_SUBAGENT_RUN_ID_ENV);
|
|
225
|
+
if (!cwd || !runsDir || !runId) return undefined;
|
|
226
|
+
const attemptId = nonEmptyEnv(env, PARENT_SUBAGENT_ATTEMPT_ID_ENV);
|
|
227
|
+
return { cwd, runsDir, runId, ...(attemptId ? { attemptId } : {}) };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function terminalChildEventForTaskStatus(
|
|
231
|
+
status: WorkflowTaskRunRecord["status"],
|
|
232
|
+
): ParentSubagentChildEvent | undefined {
|
|
233
|
+
if (status === "completed") return "completed";
|
|
234
|
+
if (status === "failed") return "failed";
|
|
235
|
+
if (status === "interrupted") return "cancelled";
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function recordParentSubagentChildEvent(options: {
|
|
240
|
+
event: ParentSubagentChildEvent;
|
|
241
|
+
childRunId: string;
|
|
242
|
+
run: WorkflowRunRecord;
|
|
243
|
+
task: WorkflowTaskRunRecord;
|
|
244
|
+
failureKind?: string | null;
|
|
245
|
+
message?: string;
|
|
246
|
+
}): Promise<void> {
|
|
247
|
+
const parent = parentSubagentRefFromEnv();
|
|
248
|
+
if (!parent) return;
|
|
249
|
+
const api = await loadSubagentApi().catch(() => undefined);
|
|
250
|
+
if (!api?.recordSubagentChildEvent) return;
|
|
251
|
+
await api
|
|
252
|
+
.recordSubagentChildEvent({
|
|
253
|
+
...parent,
|
|
254
|
+
event: options.event,
|
|
255
|
+
childRunId: options.childRunId,
|
|
256
|
+
workflowRunId: options.run.runId,
|
|
257
|
+
childTaskId: options.task.taskId,
|
|
258
|
+
...(options.failureKind === undefined
|
|
259
|
+
? {}
|
|
260
|
+
: { failureKind: options.failureKind }),
|
|
261
|
+
...(options.message === undefined ? {} : { message: options.message }),
|
|
262
|
+
})
|
|
263
|
+
.catch(() => undefined);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function recordTerminalParentSubagentChildEvent(
|
|
267
|
+
run: WorkflowRunRecord,
|
|
268
|
+
task: WorkflowTaskRunRecord,
|
|
269
|
+
snapshot: SubagentRunStatusSnapshot,
|
|
270
|
+
): Promise<void> {
|
|
271
|
+
const event = terminalChildEventForTaskStatus(task.status);
|
|
272
|
+
if (!event) return;
|
|
273
|
+
const taskFailureKind =
|
|
274
|
+
task.statusDetail && !GENERIC_TASK_STATUS_DETAILS.has(task.statusDetail)
|
|
275
|
+
? task.statusDetail
|
|
276
|
+
: undefined;
|
|
277
|
+
await recordParentSubagentChildEvent({
|
|
278
|
+
event,
|
|
279
|
+
childRunId: snapshot.runId,
|
|
280
|
+
run,
|
|
281
|
+
task,
|
|
282
|
+
failureKind:
|
|
283
|
+
event === "completed"
|
|
284
|
+
? undefined
|
|
285
|
+
: (snapshot.failureKind ?? taskFailureKind ?? task.statusDetail),
|
|
286
|
+
message: task.lastMessage,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
183
290
|
let launchSlotReleaseDelayMs = DEFAULT_LAUNCH_SLOT_RELEASE_DELAY_MS;
|
|
184
291
|
let transientRetryJitterForTests: (() => number) | undefined;
|
|
185
292
|
const launchWaitQueue: Array<() => void> = [];
|
|
@@ -225,8 +332,7 @@ function releaseLaunchSlotAfterDelay(
|
|
|
225
332
|
release();
|
|
226
333
|
return;
|
|
227
334
|
}
|
|
228
|
-
|
|
229
|
-
timer.unref?.();
|
|
335
|
+
setTimeout(release, delayMs);
|
|
230
336
|
}
|
|
231
337
|
|
|
232
338
|
async function runWithLaunchSlot<T>(action: () => Promise<T>): Promise<T> {
|
|
@@ -282,7 +388,6 @@ export async function cleanupSubagentRun(
|
|
|
282
388
|
run: WorkflowRunRecord,
|
|
283
389
|
): Promise<void> {
|
|
284
390
|
for (const task of run.tasks) {
|
|
285
|
-
if (isTerminalTaskStatus(task.status)) continue;
|
|
286
391
|
const handle = getSubagentHandle(task);
|
|
287
392
|
if (!handle) continue;
|
|
288
393
|
const api = await loadSubagentApi();
|
|
@@ -409,6 +514,13 @@ export async function launchSubagentTask(
|
|
|
409
514
|
task.statusDetail = "running";
|
|
410
515
|
task.lastMessage = "launched via pi-subagent/headless";
|
|
411
516
|
await writeRunRecord(cwd, run).catch(() => undefined);
|
|
517
|
+
await recordParentSubagentChildEvent({
|
|
518
|
+
event: "started",
|
|
519
|
+
childRunId: launched.runId,
|
|
520
|
+
run,
|
|
521
|
+
task,
|
|
522
|
+
message: task.lastMessage,
|
|
523
|
+
});
|
|
412
524
|
return { kind: "launched" };
|
|
413
525
|
}
|
|
414
526
|
|
|
@@ -440,8 +552,13 @@ export async function refreshRunFromSubagentArtifacts(
|
|
|
440
552
|
}
|
|
441
553
|
}
|
|
442
554
|
if (!handle) {
|
|
555
|
+
if (isStaleLaunchClaim(task)) {
|
|
556
|
+
resetStaleLaunchClaim(task);
|
|
557
|
+
changed = true;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
443
560
|
if (isTaskTimedOut(task)) {
|
|
444
|
-
|
|
561
|
+
markSubagentTaskTimedOut(task);
|
|
445
562
|
changed = true;
|
|
446
563
|
}
|
|
447
564
|
continue;
|
|
@@ -466,16 +583,8 @@ export async function refreshRunFromSubagentArtifacts(
|
|
|
466
583
|
|
|
467
584
|
if (snapshot === null) {
|
|
468
585
|
if (isTaskTimedOut(task)) {
|
|
469
|
-
await api
|
|
470
|
-
|
|
471
|
-
cwd: handle.cwd,
|
|
472
|
-
runsDir: handle.runsDir,
|
|
473
|
-
runId: handle.runId,
|
|
474
|
-
attemptId: handle.attemptId,
|
|
475
|
-
reason: "workflow timeout",
|
|
476
|
-
})
|
|
477
|
-
.catch(() => undefined);
|
|
478
|
-
markTaskTimedOut(task);
|
|
586
|
+
await interruptTimedOutSubagent(api, handle);
|
|
587
|
+
markSubagentTaskTimedOut(task);
|
|
479
588
|
changed = true;
|
|
480
589
|
}
|
|
481
590
|
continue;
|
|
@@ -492,16 +601,8 @@ export async function refreshRunFromSubagentArtifacts(
|
|
|
492
601
|
? `pi-subagent heartbeat ${activeAttempt.heartbeatAt}`
|
|
493
602
|
: "pi-subagent running";
|
|
494
603
|
if (isTaskTimedOut(task)) {
|
|
495
|
-
await api
|
|
496
|
-
|
|
497
|
-
cwd: handle.cwd,
|
|
498
|
-
runsDir: handle.runsDir,
|
|
499
|
-
runId: handle.runId,
|
|
500
|
-
attemptId: handle.attemptId,
|
|
501
|
-
reason: "workflow timeout",
|
|
502
|
-
})
|
|
503
|
-
.catch(() => undefined);
|
|
504
|
-
markTaskTimedOut(task);
|
|
604
|
+
await interruptTimedOutSubagent(api, handle);
|
|
605
|
+
markSubagentTaskTimedOut(task);
|
|
505
606
|
changed = true;
|
|
506
607
|
}
|
|
507
608
|
continue;
|
|
@@ -515,6 +616,48 @@ export async function refreshRunFromSubagentArtifacts(
|
|
|
515
616
|
return run;
|
|
516
617
|
}
|
|
517
618
|
|
|
619
|
+
async function interruptTimedOutSubagent(
|
|
620
|
+
api: Awaited<ReturnType<typeof loadSubagentApi>>,
|
|
621
|
+
handle: NonNullable<WorkflowTaskRunRecord["backendHandle"]>,
|
|
622
|
+
): Promise<void> {
|
|
623
|
+
await api
|
|
624
|
+
.interruptSubagent({
|
|
625
|
+
cwd: handle.cwd,
|
|
626
|
+
runsDir: handle.runsDir,
|
|
627
|
+
runId: handle.runId,
|
|
628
|
+
attemptId: handle.attemptId,
|
|
629
|
+
reason: "workflow timeout",
|
|
630
|
+
})
|
|
631
|
+
.catch(() => undefined);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function markSubagentTaskTimedOut(task: WorkflowTaskRunRecord): void {
|
|
635
|
+
markTaskTimedOut(task);
|
|
636
|
+
task.backendHandle = undefined;
|
|
637
|
+
task.backendTaskId = task.taskId;
|
|
638
|
+
task.pid = undefined;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function isStaleLaunchClaim(task: WorkflowTaskRunRecord): boolean {
|
|
642
|
+
if (task.statusDetail !== "launching" || !task.startedAt) return false;
|
|
643
|
+
const startedAtMs = Date.parse(task.startedAt);
|
|
644
|
+
return (
|
|
645
|
+
Number.isFinite(startedAtMs) &&
|
|
646
|
+
Date.now() - startedAtMs > STALE_LAUNCH_CLAIM_GRACE_MS
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function resetStaleLaunchClaim(task: WorkflowTaskRunRecord): void {
|
|
651
|
+
task.status = "pending";
|
|
652
|
+
task.statusDetail = "pending";
|
|
653
|
+
task.startedAt = undefined;
|
|
654
|
+
task.backendHandle = undefined;
|
|
655
|
+
task.backendFiles = undefined;
|
|
656
|
+
task.backendTaskId = task.taskId;
|
|
657
|
+
task.pid = undefined;
|
|
658
|
+
task.lastMessage = "stale pi-subagent launch claim reset";
|
|
659
|
+
}
|
|
660
|
+
|
|
518
661
|
async function materializeTerminalSubagentResult(
|
|
519
662
|
cwd: string,
|
|
520
663
|
run: WorkflowRunRecord,
|
|
@@ -593,7 +736,7 @@ async function materializeTerminalSubagentResult(
|
|
|
593
736
|
snapshot.metadata?.contextLengthExceeded,
|
|
594
737
|
);
|
|
595
738
|
if (task.artifactGraph?.enabled && statusInfo.status === "completed") {
|
|
596
|
-
|
|
739
|
+
const changed = await materializeTerminalArtifactGraphResult(cwd, run, task, {
|
|
597
740
|
outputFile,
|
|
598
741
|
stderrFile,
|
|
599
742
|
resultFile,
|
|
@@ -602,6 +745,8 @@ async function materializeTerminalSubagentResult(
|
|
|
602
745
|
exitCode,
|
|
603
746
|
subagentResult,
|
|
604
747
|
});
|
|
748
|
+
await recordTerminalParentSubagentChildEvent(run, task, snapshot);
|
|
749
|
+
return changed;
|
|
605
750
|
}
|
|
606
751
|
if (
|
|
607
752
|
shouldAttemptArtifactGraphSalvage({
|
|
@@ -615,7 +760,7 @@ async function materializeTerminalSubagentResult(
|
|
|
615
760
|
snapshot,
|
|
616
761
|
})
|
|
617
762
|
) {
|
|
618
|
-
|
|
763
|
+
const changed = await materializeTerminalArtifactGraphResult(cwd, run, task, {
|
|
619
764
|
outputFile,
|
|
620
765
|
stderrFile,
|
|
621
766
|
resultFile,
|
|
@@ -629,6 +774,8 @@ async function materializeTerminalSubagentResult(
|
|
|
629
774
|
subagentFailureKind: snapshot.failureKind,
|
|
630
775
|
},
|
|
631
776
|
});
|
|
777
|
+
await recordTerminalParentSubagentChildEvent(run, task, snapshot);
|
|
778
|
+
return changed;
|
|
632
779
|
}
|
|
633
780
|
const workflowResult = {
|
|
634
781
|
status: statusInfo.status,
|
|
@@ -664,10 +811,12 @@ async function materializeTerminalSubagentResult(
|
|
|
664
811
|
),
|
|
665
812
|
workflowResult,
|
|
666
813
|
);
|
|
667
|
-
|
|
814
|
+
const changed = retryOrFailTransientSubagentFailure(task, {
|
|
668
815
|
reason: statusInfo.failureKind ?? "model",
|
|
669
816
|
message: errorMessage ?? "pi-subagent run failed before producing output",
|
|
670
817
|
});
|
|
818
|
+
await recordTerminalParentSubagentChildEvent(run, task, snapshot);
|
|
819
|
+
return changed;
|
|
671
820
|
}
|
|
672
821
|
await writeJson(resultFile, workflowResult);
|
|
673
822
|
|
|
@@ -682,6 +831,7 @@ async function materializeTerminalSubagentResult(
|
|
|
682
831
|
delete task.backendHandle;
|
|
683
832
|
delete task.backendFiles;
|
|
684
833
|
}
|
|
834
|
+
await recordTerminalParentSubagentChildEvent(run, task, snapshot);
|
|
685
835
|
return changed;
|
|
686
836
|
}
|
|
687
837
|
|
|
@@ -1674,6 +1824,7 @@ async function recoverSubagentHandle(
|
|
|
1674
1824
|
const runsDir = subagentRunsDir(run, task);
|
|
1675
1825
|
const absoluteRunsDir = resolve(task.cwd, runsDir);
|
|
1676
1826
|
const expectedCorrelationId = `${run.runId}:${task.taskId}`;
|
|
1827
|
+
const claimStartedAtMs = timestampMs(task.startedAt);
|
|
1677
1828
|
const entries = await readdir(absoluteRunsDir, { withFileTypes: true }).catch(
|
|
1678
1829
|
() => [],
|
|
1679
1830
|
);
|
|
@@ -1688,6 +1839,7 @@ async function recoverSubagentHandle(
|
|
|
1688
1839
|
join(absoluteRunsDir, entry.name, "run.json"),
|
|
1689
1840
|
);
|
|
1690
1841
|
if (!record || record.correlationId !== expectedCorrelationId) continue;
|
|
1842
|
+
if (isPreClaimSubagentRecord(record, claimStartedAtMs)) continue;
|
|
1691
1843
|
const attemptId =
|
|
1692
1844
|
record.activeAttemptId ??
|
|
1693
1845
|
record.latestAttemptId ??
|
|
@@ -1714,6 +1866,20 @@ async function recoverSubagentHandle(
|
|
|
1714
1866
|
return candidates[0]?.handle;
|
|
1715
1867
|
}
|
|
1716
1868
|
|
|
1869
|
+
function isPreClaimSubagentRecord(
|
|
1870
|
+
record: SubagentRunRecordLike,
|
|
1871
|
+
claimStartedAtMs: number | undefined,
|
|
1872
|
+
): boolean {
|
|
1873
|
+
if (claimStartedAtMs === undefined) return false;
|
|
1874
|
+
const recordStartedAtMs =
|
|
1875
|
+
timestampMs(record.startedAt) ??
|
|
1876
|
+
timestampMs(record.attempts?.[0]?.startedAt) ??
|
|
1877
|
+
timestampMs(record.updatedAt);
|
|
1878
|
+
return (
|
|
1879
|
+
recordStartedAtMs !== undefined && recordStartedAtMs < claimStartedAtMs
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1717
1883
|
function timestampMs(value: string | undefined): number | undefined {
|
|
1718
1884
|
if (value === undefined) return undefined;
|
|
1719
1885
|
const time = Date.parse(value);
|
|
@@ -1774,7 +1940,14 @@ function subagentSessionId(
|
|
|
1774
1940
|
task: WorkflowTaskRunRecord,
|
|
1775
1941
|
): string | undefined {
|
|
1776
1942
|
if (!task.artifactGraph?.enabled) return undefined;
|
|
1777
|
-
|
|
1943
|
+
const baseSessionId = baseSubagentSessionId(run, task);
|
|
1944
|
+
if (task.outputRetry?.sessionId) return task.outputRetry.sessionId;
|
|
1945
|
+
const launchAttempt = task.launchRetry?.attempts ?? 0;
|
|
1946
|
+
if (launchAttempt > 0)
|
|
1947
|
+
return `${baseSessionId}:launch-retry-${launchAttempt}`;
|
|
1948
|
+
const resumeAttempt = task.resumeEvents?.length ?? 0;
|
|
1949
|
+
if (resumeAttempt > 0) return `${baseSessionId}:resume-${resumeAttempt}`;
|
|
1950
|
+
return baseSessionId;
|
|
1778
1951
|
}
|
|
1779
1952
|
|
|
1780
1953
|
function baseSubagentSessionId(
|
package/src/types.ts
CHANGED
|
@@ -542,6 +542,9 @@ export interface CompiledTask {
|
|
|
542
542
|
branchId?: string;
|
|
543
543
|
outputProfile?: string;
|
|
544
544
|
};
|
|
545
|
+
foreachGenerated?: {
|
|
546
|
+
placeholderSpecId: string;
|
|
547
|
+
};
|
|
545
548
|
loopChild?: CompiledLoopChildTaskRef;
|
|
546
549
|
loopPlaceholder?: {
|
|
547
550
|
loopId: string;
|
|
@@ -634,6 +637,9 @@ export interface WorkflowTaskRunRecord {
|
|
|
634
637
|
branchId?: string;
|
|
635
638
|
outputProfile?: string;
|
|
636
639
|
};
|
|
640
|
+
foreachGenerated?: {
|
|
641
|
+
placeholderSpecId: string;
|
|
642
|
+
};
|
|
637
643
|
launchRetry?: {
|
|
638
644
|
attempts: number;
|
|
639
645
|
maxAttempts?: number;
|
package/src/workflow-runtime.ts
CHANGED
|
@@ -345,9 +345,25 @@ export function readSimpleJsonPath(value: unknown, path: string): unknown {
|
|
|
345
345
|
const parts = path.slice(2).split(".").filter(Boolean);
|
|
346
346
|
let current = value as any;
|
|
347
347
|
for (const part of parts) {
|
|
348
|
-
if (current
|
|
349
|
-
return undefined;
|
|
348
|
+
if (!canReadJsonPathPart(current, part)) return undefined;
|
|
350
349
|
current = current[part];
|
|
351
350
|
}
|
|
352
351
|
return current;
|
|
353
352
|
}
|
|
353
|
+
|
|
354
|
+
function canReadJsonPathPart(
|
|
355
|
+
value: unknown,
|
|
356
|
+
part: string,
|
|
357
|
+
): value is Record<string, unknown> {
|
|
358
|
+
return (
|
|
359
|
+
isSafeJsonPathPart(part) && isRecord(value) && Object.hasOwn(value, part)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function isSafeJsonPathPart(part: string): boolean {
|
|
364
|
+
return part !== "__proto__" && part !== "prototype" && part !== "constructor";
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
368
|
+
return typeof value === "object" && value !== null;
|
|
369
|
+
}
|
package/src/workflow-view.ts
CHANGED
|
@@ -1370,10 +1370,11 @@ function statusForSummary(
|
|
|
1370
1370
|
): WorkflowRunStatus | TaskRunStatus {
|
|
1371
1371
|
if (summary.running > 0) return "running";
|
|
1372
1372
|
if (summary.blocked > 0) return "blocked";
|
|
1373
|
-
if (summary.failed > 0
|
|
1373
|
+
if (summary.failed > 0) return "failed";
|
|
1374
1374
|
if (summary.pending > 0) return "pending";
|
|
1375
1375
|
if (summary.total > 0 && summary.completed === summary.total)
|
|
1376
1376
|
return "completed";
|
|
1377
|
+
if (summary.interrupted > 0) return "interrupted";
|
|
1377
1378
|
return "interrupted";
|
|
1378
1379
|
}
|
|
1379
1380
|
|