pi-subagents 0.53.0 → 0.55.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 +66 -5
- package/README.md +1 -1
- package/agents/reviewer.md +12 -2
- package/docs/agents.md +4 -4
- package/docs/configuration.md +6 -6
- package/docs/extension-api.md +14 -4
- package/docs/models.md +41 -6
- package/docs/observability.md +14 -3
- package/docs/tool-reference.md +21 -6
- package/docs/workflows.md +7 -1
- package/index.ts +10 -1
- package/package.json +1 -1
- package/prompts/council.md +19 -7
- package/prompts/parallel-review.md +5 -1
- package/prompts/review-loop.md +9 -5
- package/skills/council-mode/SKILL.md +50 -26
- package/skills/pi-subagents/SKILL.md +5 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +16 -1
- package/skills/pi-subagents/references/execution-controls.md +13 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +14 -7
- package/src/agents/agent-management.ts +115 -14
- package/src/agents/agent-serializer.ts +2 -2
- package/src/agents/agents.ts +195 -47
- package/src/api/external-job-provider.ts +10 -1
- package/src/api/preflight.ts +33 -19
- package/src/api/project-panes.ts +2 -0
- package/src/extension/doctor.ts +10 -0
- package/src/extension/fanout-child.ts +3 -2
- package/src/extension/index.ts +24 -4
- package/src/extension/public-execution.ts +12 -9
- package/src/extension/rpc.ts +77 -3
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +9 -6
- package/src/extension/tool-result.ts +19 -0
- package/src/inspectors/herdr/client.ts +3 -3
- package/src/inspectors/herdr/focus.ts +55 -0
- package/src/inspectors/herdr/project-panes.ts +228 -44
- package/src/integrations/herdr-status.ts +26 -4
- package/src/runs/background/async-execution.ts +112 -13
- package/src/runs/background/async-job-tracker.ts +33 -14
- package/src/runs/background/async-resume.ts +20 -2
- package/src/runs/background/async-retention.ts +1 -1
- package/src/runs/background/async-status.ts +10 -0
- package/src/runs/background/chain-root-attachment.ts +5 -0
- package/src/runs/background/control-channel.ts +98 -10
- package/src/runs/background/notify.ts +62 -1
- package/src/runs/background/result-watcher.ts +4 -3
- package/src/runs/background/run-status.ts +15 -1
- package/src/runs/background/stale-run-reconciler.ts +3 -0
- package/src/runs/background/subagent-runner.ts +251 -38
- package/src/runs/background/wait-completions.ts +2 -0
- package/src/runs/background/wait-tool.ts +4 -3
- package/src/runs/foreground/async-stop-action.ts +23 -2
- package/src/runs/foreground/execution.ts +110 -8
- package/src/runs/foreground/subagent-executor.ts +361 -45
- package/src/runs/foreground/workflow-detach-reconcile.ts +3 -0
- package/src/runs/shared/acceptance.ts +1 -0
- package/src/runs/shared/child-identity.ts +36 -0
- package/src/runs/shared/completion-guard.ts +50 -1
- package/src/runs/shared/external-job-bridge.ts +53 -37
- package/src/runs/shared/external-job-runner.ts +126 -23
- package/src/runs/shared/model-fallback.ts +46 -15
- package/src/runs/shared/model-scope.ts +106 -39
- package/src/runs/shared/orca-progress-tabs.ts +71 -9
- package/src/runs/shared/parallel-utils.ts +12 -0
- package/src/runs/shared/pi-args.ts +31 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +39 -10
- package/src/runs/shared/tool-availability.ts +1 -3
- package/src/shared/launch-contract.ts +6 -5
- package/src/shared/thinking-ceiling.ts +52 -0
- package/src/shared/types.ts +72 -5
- package/src/tui/fleet-status.ts +66 -13
- package/src/tui/render.ts +5 -4
- package/src/watchdog/permission-arbiter.ts +59 -51
- package/src/workflows/scripted-workflow.ts +107 -10
|
@@ -45,7 +45,11 @@ export function createWatchdogPermissionArbiter(options: WatchdogPermissionArbit
|
|
|
45
45
|
const createdAt = Date.now();
|
|
46
46
|
const auditBase = { type: "permission.request", createdAt, toolName: request.toolName, preview, matchedRule: "ask", decisionSource: "watchdog" };
|
|
47
47
|
appendPermissionAudit(request.auditPath, auditBase);
|
|
48
|
+
let completed = false;
|
|
48
49
|
const finish = (approved: boolean, reason: string, decision: string): WatchdogPermissionResult => {
|
|
50
|
+
const concise = conciseReason(reason);
|
|
51
|
+
if (completed) return { approved, reason: concise, source: "watchdog" };
|
|
52
|
+
completed = true;
|
|
49
53
|
appendPermissionAudit(request.auditPath, {
|
|
50
54
|
type: "permission.decision",
|
|
51
55
|
createdAt: Date.now(),
|
|
@@ -54,9 +58,9 @@ export function createWatchdogPermissionArbiter(options: WatchdogPermissionArbit
|
|
|
54
58
|
decision,
|
|
55
59
|
approved,
|
|
56
60
|
decisionSource: "watchdog",
|
|
57
|
-
reason:
|
|
61
|
+
reason: concise,
|
|
58
62
|
});
|
|
59
|
-
return { approved, reason:
|
|
63
|
+
return { approved, reason: concise, source: "watchdog" };
|
|
60
64
|
};
|
|
61
65
|
|
|
62
66
|
let childConfig;
|
|
@@ -84,61 +88,65 @@ export function createWatchdogPermissionArbiter(options: WatchdogPermissionArbit
|
|
|
84
88
|
|
|
85
89
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
86
90
|
let agent: Agent | undefined;
|
|
91
|
+
let abort: (() => void) | undefined;
|
|
87
92
|
try {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return finish(approved, decision.reason, decision.decision);
|
|
93
|
+
const run = async (): Promise<WatchdogPermissionResult> => {
|
|
94
|
+
const config = childResolvedConfig(childConfig);
|
|
95
|
+
const selection = await resolveWatchdogReviewModel(request.ctx, config);
|
|
96
|
+
const auth = selection.auth;
|
|
97
|
+
const registeredProvider = (request.ctx.modelRegistry as {
|
|
98
|
+
getRegisteredProviderConfig?: (provider: string) => { api?: string; streamSimple?: StreamFn } | undefined;
|
|
99
|
+
}).getRegisteredProviderConfig?.(selection.model.provider);
|
|
100
|
+
const baseStreamFn = options.streamFn ?? (registeredProvider?.streamSimple && registeredProvider.api === selection.model.api
|
|
101
|
+
? registeredProvider.streamSimple
|
|
102
|
+
: streamSimple);
|
|
103
|
+
const streamFn: StreamFn = (model, context, streamOptions) => baseStreamFn(model, context, {
|
|
104
|
+
...streamOptions,
|
|
105
|
+
...(auth.apiKey ? { apiKey: auth.apiKey } : {}),
|
|
106
|
+
env: auth.env || streamOptions?.env ? { ...(auth.env ?? {}), ...(streamOptions?.env ?? {}) } : undefined,
|
|
107
|
+
headers: { ...(streamOptions?.headers ?? {}), ...(auth.headers ?? {}) },
|
|
108
|
+
});
|
|
109
|
+
agent = new Agent({
|
|
110
|
+
initialState: {
|
|
111
|
+
systemPrompt: [
|
|
112
|
+
"You are the pi-subagents watchdog permission arbiter.",
|
|
113
|
+
"Decide only whether this exact non-bash child tool call should proceed.",
|
|
114
|
+
"Call watchdog_permission_decision exactly once with approve or deny and a concise reason.",
|
|
115
|
+
"Deny when uncertain. Do not produce freeform advice or ask the parent orchestrator.",
|
|
116
|
+
].join("\n"),
|
|
117
|
+
model: selection.model,
|
|
118
|
+
thinkingLevel: selection.thinkingLevel,
|
|
119
|
+
tools: [tool],
|
|
120
|
+
},
|
|
121
|
+
convertToLlm,
|
|
122
|
+
...agentStreamOptions(streamFn),
|
|
123
|
+
getApiKey: (providerName) => providerName === selection.model.provider ? auth.apiKey : undefined,
|
|
124
|
+
beforeToolCall: async ({ toolCall }) => toolCall.name === tool.name ? undefined : { block: true, reason: `Permission arbiter tool '${toolCall.name}' is not allowed.` },
|
|
125
|
+
toolExecution: "sequential",
|
|
126
|
+
});
|
|
127
|
+
await agent.prompt(`Tool: ${request.toolName}\nRedacted arguments: ${preview}`);
|
|
128
|
+
if (!decision) return finish(false, "Watchdog permission arbiter returned no decision.", "malformed");
|
|
129
|
+
const approved = decision.decision === "approve";
|
|
130
|
+
return finish(approved, decision.reason, decision.decision);
|
|
131
|
+
};
|
|
132
|
+
return await Promise.race([
|
|
133
|
+
run(),
|
|
134
|
+
new Promise<WatchdogPermissionResult>((resolve) => { timeout = setTimeout(() => { agent?.abort(); resolve(finish(false, "Watchdog permission decision timed out.", "timeout")); }, childConfig.agentEndTimeoutMs); }),
|
|
135
|
+
new Promise<WatchdogPermissionResult>((resolve) => {
|
|
136
|
+
abort = () => { agent?.abort(); resolve(finish(false, "Watchdog permission decision was cancelled.", "cancelled")); };
|
|
137
|
+
request.signal?.addEventListener("abort", abort, { once: true });
|
|
138
|
+
request.ctx.signal?.addEventListener("abort", abort, { once: true });
|
|
139
|
+
}),
|
|
140
|
+
]);
|
|
137
141
|
} catch (error) {
|
|
138
142
|
const reason = error instanceof Error ? error.message : String(error);
|
|
139
143
|
return finish(false, `Watchdog permission arbiter failed closed: ${reason}`, reason.includes("timed out") ? "timeout" : "error");
|
|
140
144
|
} finally {
|
|
141
145
|
if (timeout) clearTimeout(timeout);
|
|
146
|
+
if (abort) {
|
|
147
|
+
request.signal?.removeEventListener("abort", abort);
|
|
148
|
+
request.ctx.signal?.removeEventListener("abort", abort);
|
|
149
|
+
}
|
|
142
150
|
}
|
|
143
151
|
};
|
|
144
152
|
}
|
|
@@ -242,6 +242,33 @@ function runHostCall(key, params, collectFailure, batch) {
|
|
|
242
242
|
return { key, callId, promise };
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
function isArrayIndexProperty(prop) {
|
|
246
|
+
if (!/^(0|[1-9]\d*)$/.test(prop)) return false;
|
|
247
|
+
const index = Number(prop);
|
|
248
|
+
return Number.isSafeInteger(index) && index >= 0 && index < 4294967295;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const runsAllResultTargets = new WeakMap();
|
|
252
|
+
|
|
253
|
+
function runsAllKeyAccessError(prop) {
|
|
254
|
+
return new Error("Cannot read runs.all result property '" + prop + "'. runs.all resolves to an ordered array, not a key map. Use results[0], array destructuring, or results.map((result) => result.output), not results." + prop + ".");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function wrapRunsAllResults(results, keys) {
|
|
258
|
+
const keySet = new Set(keys);
|
|
259
|
+
const proxy = new Proxy(results, {
|
|
260
|
+
get(target, prop, receiver) {
|
|
261
|
+
if (typeof prop !== "string") return Reflect.get(target, prop, receiver);
|
|
262
|
+
if (prop === "then" || prop === "toJSON") return undefined;
|
|
263
|
+
if (prop in target || isArrayIndexProperty(prop)) return Reflect.get(target, prop, receiver);
|
|
264
|
+
if (keySet.has(prop)) throw runsAllKeyAccessError(prop);
|
|
265
|
+
throw runsAllKeyAccessError(prop);
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
runsAllResultTargets.set(proxy, results);
|
|
269
|
+
return proxy;
|
|
270
|
+
}
|
|
271
|
+
|
|
245
272
|
function formatRef(result) {
|
|
246
273
|
if (!result || typeof result !== "object") throw new Error("runs.ref(result) requires a run result object.");
|
|
247
274
|
const parts = ["run " + (result.key || "unknown")];
|
|
@@ -302,7 +329,7 @@ const runs = Object.freeze({
|
|
|
302
329
|
runFingerprints = fingerprints;
|
|
303
330
|
const batch = { id: "batch-" + (++nextCallId), calls };
|
|
304
331
|
const launched = calls.map(({ key, params }) => runHostCall(key, params, true, batch));
|
|
305
|
-
return trackRunObservation(launched.map(({ key, callId }) => ({ key, operation: "run", callId })), Promise.all(launched.map(({ promise }) => promise)));
|
|
332
|
+
return trackRunObservation(launched.map(({ key, callId }) => ({ key, operation: "run", callId })), Promise.all(launched.map(({ promise }) => promise)).then((results) => wrapRunsAllResults(results, calls.map(({ key }) => key))));
|
|
306
333
|
},
|
|
307
334
|
steer(key, message, options = {}) {
|
|
308
335
|
if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error("runs.steer has an invalid key.");
|
|
@@ -431,6 +458,31 @@ function isPlainWorkflowObject(value) {
|
|
|
431
458
|
return prototype === null || prototype === Object.prototype || prototype === contextObjectPrototype;
|
|
432
459
|
}
|
|
433
460
|
|
|
461
|
+
function unwrapRunsAllResults(value, seen = new Map()) {
|
|
462
|
+
if (value === null || typeof value !== "object") return value;
|
|
463
|
+
const runsAllTarget = runsAllResultTargets.get(value);
|
|
464
|
+
const target = runsAllTarget || value;
|
|
465
|
+
if (seen.has(target)) return seen.get(target);
|
|
466
|
+
if (Array.isArray(target)) {
|
|
467
|
+
const copy = [];
|
|
468
|
+
seen.set(target, copy);
|
|
469
|
+
let changed = !!runsAllTarget;
|
|
470
|
+
for (let index = 0; index < target.length; index++) {
|
|
471
|
+
copy[index] = unwrapRunsAllResults(target[index], seen);
|
|
472
|
+
changed ||= copy[index] !== target[index];
|
|
473
|
+
}
|
|
474
|
+
return changed ? copy : target;
|
|
475
|
+
}
|
|
476
|
+
if (!isPlainWorkflowObject(target) || Object.getOwnPropertySymbols(target).length > 0) return target;
|
|
477
|
+
let changed = false;
|
|
478
|
+
const entries = Object.entries(target).map(([key, entry]) => {
|
|
479
|
+
const unwrapped = unwrapRunsAllResults(entry, seen);
|
|
480
|
+
changed ||= unwrapped !== entry;
|
|
481
|
+
return [key, unwrapped];
|
|
482
|
+
});
|
|
483
|
+
return changed ? Object.fromEntries(entries) : target;
|
|
484
|
+
}
|
|
485
|
+
|
|
434
486
|
function omitUndefinedWorkflowValues(value, seen = new Set()) {
|
|
435
487
|
if (value === null || typeof value !== "object") return value;
|
|
436
488
|
if (seen.has(value)) return value;
|
|
@@ -459,7 +511,7 @@ parentPort.on("message", async (message) => {
|
|
|
459
511
|
}
|
|
460
512
|
if (message.type !== "start") return;
|
|
461
513
|
try {
|
|
462
|
-
const sandbox = { runs, Promise: workflowPromise, emit(value) {
|
|
514
|
+
const sandbox = { runs, Promise: workflowPromise, emit(value) { const emittedValue = unwrapRunsAllResults(value); assertJsonValue(emittedValue); parentPort.postMessage({ type: "emit", value: emittedValue }); }, console: capturedConsole };
|
|
463
515
|
if (message.stateEnabled) sandbox.state = state;
|
|
464
516
|
const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
|
|
465
517
|
contextObjectPrototype = vm.runInContext("Object.prototype", context);
|
|
@@ -540,6 +592,7 @@ parentPort.on("message", async (message) => {
|
|
|
540
592
|
export interface WorkflowScriptChildResult {
|
|
541
593
|
key: string;
|
|
542
594
|
ok: boolean;
|
|
595
|
+
stopped?: boolean;
|
|
543
596
|
/** Canonical child agent name when launch resolution produced one. */
|
|
544
597
|
agent?: string;
|
|
545
598
|
runId?: string;
|
|
@@ -628,10 +681,27 @@ export interface RunWorkflowScriptOptions {
|
|
|
628
681
|
get: (key: string) => unknown | Promise<unknown>;
|
|
629
682
|
set: (key: string, value: unknown) => void | Promise<void>;
|
|
630
683
|
};
|
|
684
|
+
registerStopChild?: (stop: ((key: string, message?: string) => boolean) | undefined) => void;
|
|
631
685
|
onTrace?: (trace: WorkflowScriptTraceEntry[]) => void;
|
|
632
686
|
onEmit?: (emits: unknown[]) => void;
|
|
633
687
|
}
|
|
634
688
|
|
|
689
|
+
function combinedAbortSignal(signals: AbortSignal[]): AbortSignal {
|
|
690
|
+
const controller = new AbortController();
|
|
691
|
+
const abort = (signal: AbortSignal): void => {
|
|
692
|
+
if (controller.signal.aborted) return;
|
|
693
|
+
controller.abort(signal.reason);
|
|
694
|
+
};
|
|
695
|
+
for (const signal of signals) {
|
|
696
|
+
if (signal.aborted) {
|
|
697
|
+
abort(signal);
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
signal.addEventListener("abort", () => abort(signal), { once: true });
|
|
701
|
+
}
|
|
702
|
+
return controller.signal;
|
|
703
|
+
}
|
|
704
|
+
|
|
635
705
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
636
706
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
637
707
|
}
|
|
@@ -795,6 +865,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
795
865
|
const launches = new Map<string, { fingerprint: string; promise: Promise<WorkflowScriptChildResult>; observed: boolean }>();
|
|
796
866
|
const steers = new Map<number, { key: string; promise: Promise<WorkflowSteerResult>; observed: boolean }>();
|
|
797
867
|
const stoppedLaunches = new Set<string>();
|
|
868
|
+
const childStopControllers = new Map<string, AbortController>();
|
|
798
869
|
const batchAdmissions = new Map<string, Promise<void>>();
|
|
799
870
|
const observedRunCalls = new Set<number>();
|
|
800
871
|
const observedSteerCalls = new Set<number>();
|
|
@@ -818,6 +889,26 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
818
889
|
console.error("Workflow onTrace callback failed:", error);
|
|
819
890
|
}
|
|
820
891
|
};
|
|
892
|
+
const stoppedChildResult = (key: string, message: string): WorkflowScriptChildResult => ({ key, ok: false, stopped: true, output: message, error: message, artifactPaths: [] });
|
|
893
|
+
const stopChild = (key: string, message = `Workflow child '${key}' stopped by user.`): boolean => {
|
|
894
|
+
if (!launches.has(key) || children.has(key)) return false;
|
|
895
|
+
stoppedLaunches.add(key);
|
|
896
|
+
children.set(key, stoppedChildResult(key, message));
|
|
897
|
+
childStopControllers.get(key)?.abort(new Error(message));
|
|
898
|
+
const started = trace.findLast((entry) => entry.operation === "run" && entry.key === key && entry.state === "started");
|
|
899
|
+
trace.push({
|
|
900
|
+
operation: "run",
|
|
901
|
+
key,
|
|
902
|
+
state: "stopped",
|
|
903
|
+
...(started?.agent ? { agent: started.agent } : {}),
|
|
904
|
+
...(started?.phase ? { phase: started.phase } : {}),
|
|
905
|
+
...(started?.label ? { label: started.label } : {}),
|
|
906
|
+
error: message,
|
|
907
|
+
});
|
|
908
|
+
traceChanged();
|
|
909
|
+
return true;
|
|
910
|
+
};
|
|
911
|
+
options.registerStopChild?.(stopChild);
|
|
821
912
|
|
|
822
913
|
return await new Promise<WorkflowScriptResult>((resolve, reject) => {
|
|
823
914
|
const finish = (outcome: { value: unknown } | { error: Error & { workflowErrorKind?: unknown } }) => {
|
|
@@ -827,6 +918,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
827
918
|
void Promise.allSettled([...steers.values()].map(({ promise }) => promise)).then(() => {
|
|
828
919
|
if (settled) return;
|
|
829
920
|
settled = true;
|
|
921
|
+
options.registerStopChild?.(undefined);
|
|
830
922
|
if (timer) clearTimeout(timer);
|
|
831
923
|
options.signal?.removeEventListener("abort", onAbort);
|
|
832
924
|
void worker.terminate();
|
|
@@ -1061,7 +1153,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1061
1153
|
const deliver = (promise: Promise<WorkflowScriptChildResult>) => collectFailure
|
|
1062
1154
|
? promise
|
|
1063
1155
|
: promise.then((result) => {
|
|
1064
|
-
if (!result.ok) {
|
|
1156
|
+
if (!result.ok && !result.stopped) {
|
|
1065
1157
|
const childError = new Error(result.detached ? `Run '${key}' detached: ${result.error ?? result.output}` : `Run '${key}' failed: ${result.error ?? result.output}`) as Error & { workflowErrorKind?: "detached-child" };
|
|
1066
1158
|
if (result.detached) childError.workflowErrorKind = "detached-child";
|
|
1067
1159
|
throw childError;
|
|
@@ -1100,13 +1192,16 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1100
1192
|
const promise = admission.then(async () => {
|
|
1101
1193
|
if (settled || finishing || stoppedLaunches.has(key)) {
|
|
1102
1194
|
const reason = childController.signal.reason;
|
|
1103
|
-
const text = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "Workflow script aborted.";
|
|
1104
|
-
return
|
|
1195
|
+
const text = children.get(key)?.error ?? (reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "Workflow script aborted.");
|
|
1196
|
+
return stoppedChildResult(key, text);
|
|
1105
1197
|
}
|
|
1198
|
+
const childStopController = new AbortController();
|
|
1199
|
+
childStopControllers.set(key, childStopController);
|
|
1200
|
+
const childSignal = combinedAbortSignal([childController.signal, childStopController.signal]);
|
|
1106
1201
|
const resolvedResumeValue = resumeReference
|
|
1107
1202
|
? await Promise.resolve().then(() => {
|
|
1108
1203
|
if (!options.resolveResume) throw new Error("Keyed workflow receipt resume is unavailable in this host.");
|
|
1109
|
-
return options.resolveResume(resumeReference,
|
|
1204
|
+
return options.resolveResume(resumeReference, childSignal);
|
|
1110
1205
|
})
|
|
1111
1206
|
: undefined;
|
|
1112
1207
|
const resolvedResume = typeof resolvedResumeValue === "string"
|
|
@@ -1124,22 +1219,24 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1124
1219
|
if (resolvedResumeLineage.at(-1) !== resolvedResumeId) resolvedResumeLineage.push(resolvedResumeId!);
|
|
1125
1220
|
}
|
|
1126
1221
|
const launchParams = resolvedResumeId ? { ...params, resume: resolvedResumeId } : params;
|
|
1127
|
-
return options.launch(key,
|
|
1222
|
+
return options.launch(key, launchParams, childSignal, { admitted: true });
|
|
1128
1223
|
}).then((result) => {
|
|
1129
1224
|
let normalized = !result.ok && !result.error ? { ...result, error: result.output } : result;
|
|
1130
1225
|
if (resolvedResumeLineage?.length && normalized.runId) {
|
|
1131
1226
|
normalized = { ...normalized, continuation: { runIds: [...new Set([...resolvedResumeLineage, normalized.runId])] } };
|
|
1132
1227
|
}
|
|
1133
|
-
|
|
1228
|
+
childStopControllers.delete(key);
|
|
1229
|
+
if (stoppedLaunches.has(key)) return children.get(key) ?? normalized;
|
|
1134
1230
|
children.set(key, normalized);
|
|
1135
|
-
const state = normalized.ok ? "completed" : normalized.detached ? "detached" : "failed";
|
|
1231
|
+
const state = normalized.ok ? "completed" : normalized.stopped ? "stopped" : normalized.detached ? "detached" : "failed";
|
|
1136
1232
|
trace.push({ operation: "run", key, state, durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), ...(normalized.agent ? { agent: normalized.agent } : {}), ...(normalized.runId ? { runId: normalized.runId } : {}), ...(!normalized.ok ? { error: normalized.error ?? normalized.output } : {}) });
|
|
1137
1233
|
traceChanged();
|
|
1138
1234
|
return normalized;
|
|
1139
1235
|
}, (error: unknown) => {
|
|
1140
1236
|
const text = error instanceof Error ? error.message : String(error);
|
|
1141
1237
|
const failure: WorkflowScriptChildResult = { key, ok: false, output: text, error: text, artifactPaths: [] };
|
|
1142
|
-
|
|
1238
|
+
childStopControllers.delete(key);
|
|
1239
|
+
if (stoppedLaunches.has(key)) return children.get(key) ?? { ...failure, stopped: true };
|
|
1143
1240
|
children.set(key, failure);
|
|
1144
1241
|
trace.push({ operation: "run", key, state: "failed", durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), error: text });
|
|
1145
1242
|
traceChanged();
|