pi-subagents 0.49.0 → 0.51.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 +97 -0
- package/agents/gpt-pro.md +17 -0
- package/agents/oracle.md +7 -5
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +2 -2
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +37 -2
- package/docs/configuration.md +76 -14
- package/docs/extension-api.md +78 -1
- package/docs/missions.md +1 -1
- package/docs/observability.md +20 -4
- package/docs/tool-reference.md +55 -39
- package/docs/workflows.md +171 -5
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +5 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
- package/skills/pi-subagents/references/execution-controls.md +22 -18
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
- package/src/agents/agent-management.ts +100 -345
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +135 -25
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +42 -11
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +36 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +210 -90
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +14 -36
- package/src/extension/tool-description.ts +37 -24
- package/src/inspectors/herdr/actions.ts +5 -9
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +4 -8
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/intercom/native-supervisor-channel.ts +49 -51
- package/src/missions/goal-driver.ts +3 -1
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +94 -1
- package/src/runs/background/async-execution.ts +98 -38
- package/src/runs/background/async-job-tracker.ts +21 -4
- package/src/runs/background/async-resume.ts +30 -17
- package/src/runs/background/async-retention.ts +888 -0
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +47 -56
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +3 -1
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/notify.ts +3 -1
- package/src/runs/background/result-files.ts +505 -0
- package/src/runs/background/result-watcher.ts +250 -51
- package/src/runs/background/retained-children.ts +79 -20
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +37 -29
- package/src/runs/background/run-status.ts +34 -20
- package/src/runs/background/scheduled-runs.ts +71 -27
- package/src/runs/background/stale-run-reconciler.ts +33 -16
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +535 -161
- package/src/runs/background/subagent-wait.ts +9 -7
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +22 -2
- package/src/runs/background/wait-subscriptions.ts +80 -1
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +221 -15
- package/src/runs/foreground/subagent-executor.ts +529 -1519
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/completion-guard.ts +96 -6
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/external-job-bridge.ts +450 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +34 -3
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +437 -0
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +6 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/subagent-control.ts +41 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +12 -1
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +88 -27
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-commands.ts +45 -28
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet.ts +186 -28
- package/src/tui/render.ts +41 -10
- package/src/workflows/chat-progress.ts +7 -5
- package/src/workflows/scripted-workflow.ts +424 -125
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1565
|
@@ -1,18 +1,43 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
1
4
|
import { Worker } from "node:worker_threads";
|
|
2
5
|
|
|
3
6
|
const KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
7
|
+
const requireFromPackage = createRequire(import.meta.url);
|
|
4
8
|
|
|
5
9
|
const WORKER_SOURCE = String.raw`
|
|
6
|
-
const { parentPort } = require("node:worker_threads");
|
|
10
|
+
const { parentPort, workerData } = require("node:worker_threads");
|
|
7
11
|
const vm = require("node:vm");
|
|
8
12
|
const { inspect } = require("node:util");
|
|
13
|
+
const { parse } = require(workerData.acornPath);
|
|
14
|
+
|
|
15
|
+
let promiseHooks;
|
|
16
|
+
try {
|
|
17
|
+
({ promiseHooks } = require("node:v8"));
|
|
18
|
+
} catch {}
|
|
19
|
+
|
|
20
|
+
function createWorkflowPromiseHook(callbacks) {
|
|
21
|
+
if (!promiseHooks || typeof promiseHooks.createHook !== "function") return () => {};
|
|
22
|
+
try {
|
|
23
|
+
return promiseHooks.createHook(callbacks);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error?.name !== "NotImplementedError") throw error;
|
|
26
|
+
return () => {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
9
29
|
|
|
10
30
|
let nextCallId = 0;
|
|
31
|
+
let topLevelWorkflowPromise;
|
|
32
|
+
let suppressNativePromiseConsumption = 0;
|
|
33
|
+
const activeNativePromises = [];
|
|
11
34
|
const pending = new Map();
|
|
12
35
|
const runKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
let
|
|
36
|
+
const trackedPromiseTrackers = new WeakMap();
|
|
37
|
+
const trackedPromiseTargets = new WeakMap();
|
|
38
|
+
let nativePromiseTrackers = new WeakMap();
|
|
39
|
+
let nativePromiseParents = new WeakMap();
|
|
40
|
+
const observedCallIds = new Set();
|
|
16
41
|
|
|
17
42
|
function stableRunJson(value) {
|
|
18
43
|
if (Array.isArray(value)) return "[" + value.map(stableRunJson).join(",") + "]";
|
|
@@ -23,15 +48,47 @@ function stableRunJson(value) {
|
|
|
23
48
|
function isDirectWorkflowScriptPromiseHandlerCall() {
|
|
24
49
|
const stack = new Error().stack;
|
|
25
50
|
if (typeof stack !== "string") return false;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
return stack.split("\n").some((line) => line.includes("workflow-script.js") && !line.includes("at async "));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function nativePromiseTracker(promise) {
|
|
55
|
+
if (!promise || (typeof promise !== "object" && typeof promise !== "function")) return undefined;
|
|
56
|
+
let tracker = nativePromiseTrackers.get(promise);
|
|
57
|
+
if (!tracker) {
|
|
58
|
+
tracker = { observations: [], consumed: false, dependencies: [] };
|
|
59
|
+
nativePromiseTrackers.set(promise, tracker);
|
|
60
|
+
}
|
|
61
|
+
return tracker;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function promiseObservationTracker(value) {
|
|
65
|
+
if (!value || (typeof value !== "object" && typeof value !== "function")) return undefined;
|
|
66
|
+
return trackedPromiseTrackers.get(value) ?? nativePromiseTrackers.get(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function addTrackerDependency(tracker, dependency) {
|
|
70
|
+
if (!dependency) return;
|
|
71
|
+
tracker.dependencies ??= [];
|
|
72
|
+
if (!tracker.dependencies.includes(dependency)) tracker.dependencies.push(dependency);
|
|
73
|
+
if (tracker.consumed) markTrackedObservationsConsumed(dependency);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function descendsFromTopLevelWorkflow(promise) {
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
for (let current = promise; current && !seen.has(current); current = nativePromiseParents.get(current)) {
|
|
79
|
+
if (current === topLevelWorkflowPromise) return true;
|
|
80
|
+
seen.add(current);
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function withSuppressedNativePromiseConsumption(callback) {
|
|
86
|
+
suppressNativePromiseConsumption++;
|
|
87
|
+
try {
|
|
88
|
+
return callback();
|
|
89
|
+
} finally {
|
|
90
|
+
suppressNativePromiseConsumption--;
|
|
91
|
+
}
|
|
35
92
|
}
|
|
36
93
|
|
|
37
94
|
function mergeObservations(...groups) {
|
|
@@ -39,7 +96,7 @@ function mergeObservations(...groups) {
|
|
|
39
96
|
const merged = [];
|
|
40
97
|
for (const group of groups) {
|
|
41
98
|
for (const observation of group) {
|
|
42
|
-
if (!observation || typeof observation.callId !== "number" || typeof observation.key !== "string" || seen.has(observation.callId)) continue;
|
|
99
|
+
if (!observation || typeof observation.callId !== "number" || typeof observation.key !== "string" || typeof observation.operation !== "string" || seen.has(observation.callId)) continue;
|
|
43
100
|
seen.add(observation.callId);
|
|
44
101
|
merged.push(observation);
|
|
45
102
|
}
|
|
@@ -47,63 +104,107 @@ function mergeObservations(...groups) {
|
|
|
47
104
|
return merged;
|
|
48
105
|
}
|
|
49
106
|
|
|
50
|
-
function
|
|
51
|
-
return value && (typeof value === "object" || typeof value === "function") ?
|
|
107
|
+
function trackedObservationTracker(value) {
|
|
108
|
+
return value && (typeof value === "object" || typeof value === "function") ? trackedPromiseTrackers.get(value) : undefined;
|
|
52
109
|
}
|
|
53
110
|
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
111
|
+
function trackedPromiseTarget(value) {
|
|
112
|
+
return value && (typeof value === "object" || typeof value === "function") ? trackedPromiseTargets.get(value) ?? value : value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function addTrackedObservations(tracker, observations) {
|
|
116
|
+
tracker.observations = mergeObservations(tracker.observations, observations);
|
|
117
|
+
if (!tracker.consumed) return;
|
|
118
|
+
for (const observation of tracker.observations) {
|
|
119
|
+
if (observedCallIds.has(observation.callId)) continue;
|
|
120
|
+
observedCallIds.add(observation.callId);
|
|
121
|
+
parentPort.postMessage({ type: "callObserved", callId: observation.callId, key: observation.key, operation: observation.operation });
|
|
60
122
|
}
|
|
61
123
|
}
|
|
62
124
|
|
|
125
|
+
function markTrackedObservationsConsumed(tracker, seen = new Set()) {
|
|
126
|
+
if (seen.has(tracker)) return;
|
|
127
|
+
seen.add(tracker);
|
|
128
|
+
tracker.consumed = true;
|
|
129
|
+
addTrackedObservations(tracker, []);
|
|
130
|
+
for (const dependency of tracker.dependencies ?? []) markTrackedObservationsConsumed(dependency, seen);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function consumeTrackedObservations(tracker) {
|
|
134
|
+
if (isDirectWorkflowScriptPromiseHandlerCall()) return;
|
|
135
|
+
const activePromise = activeNativePromises.at(-1);
|
|
136
|
+
if (activePromise === topLevelWorkflowPromise || descendsFromTopLevelWorkflow(activePromise)) {
|
|
137
|
+
markTrackedObservationsConsumed(tracker);
|
|
138
|
+
} else if (activePromise) {
|
|
139
|
+
addTrackerDependency(nativePromiseTracker(activePromise), tracker);
|
|
140
|
+
} else {
|
|
141
|
+
markTrackedObservationsConsumed(tracker);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function trackObservationTracker(tracker, promise, allowFutureObservations = false) {
|
|
146
|
+
const target = trackedPromiseTarget(promise);
|
|
147
|
+
if ((!allowFutureObservations && tracker.observations.length === 0) || !target || typeof target.then !== "function") return promise;
|
|
148
|
+
|
|
149
|
+
const tracked = new Proxy(target, {
|
|
150
|
+
get(promiseTarget, prop) {
|
|
151
|
+
if (prop === "then") return function promiseThen(onFulfilled, onRejected) {
|
|
152
|
+
consumeTrackedObservations(tracker);
|
|
153
|
+
return trackObservationTracker({ observations: tracker.observations, consumed: false, dependencies: [tracker] }, promiseTarget.then(onFulfilled, onRejected), true);
|
|
154
|
+
};
|
|
155
|
+
if (prop === "catch") return function promiseCatch(onRejected) {
|
|
156
|
+
consumeTrackedObservations(tracker);
|
|
157
|
+
return trackObservationTracker({ observations: tracker.observations, consumed: false, dependencies: [tracker] }, promiseTarget.catch(onRejected), true);
|
|
158
|
+
};
|
|
159
|
+
if (prop === "finally") return function promiseFinally(onFinally) {
|
|
160
|
+
consumeTrackedObservations(tracker);
|
|
161
|
+
return trackObservationTracker({ observations: tracker.observations, consumed: false, dependencies: [tracker] }, promiseTarget.finally(onFinally), true);
|
|
162
|
+
};
|
|
163
|
+
return Reflect.get(promiseTarget, prop, promiseTarget);
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
trackedPromiseTrackers.set(tracked, tracker);
|
|
167
|
+
trackedPromiseTargets.set(tracked, target);
|
|
168
|
+
return tracked;
|
|
169
|
+
}
|
|
170
|
+
|
|
63
171
|
function trackRunObservation(observations, promise) {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (promise[trackedPromisePatch]) return promise;
|
|
68
|
-
|
|
69
|
-
const observedCallIds = new Set();
|
|
70
|
-
const markObserved = () => {
|
|
71
|
-
if (suppressRunObservation > 0 || isDirectWorkflowScriptPromiseHandlerCall()) return;
|
|
72
|
-
for (const observation of trackedObservations(promise)) {
|
|
73
|
-
if (observedCallIds.has(observation.callId)) continue;
|
|
74
|
-
observedCallIds.add(observation.callId);
|
|
75
|
-
parentPort.postMessage({ type: "runObserved", callId: observation.callId, key: observation.key });
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
const originalThen = promise.then.bind(promise);
|
|
79
|
-
const originalCatch = promise.catch.bind(promise);
|
|
80
|
-
const originalFinally = promise.finally.bind(promise);
|
|
81
|
-
Object.defineProperty(promise, trackedPromisePatch, { value: true });
|
|
82
|
-
promise.then = (onFulfilled, onRejected) => {
|
|
83
|
-
markObserved();
|
|
84
|
-
return trackRunObservation(trackedObservations(promise), originalThen(onFulfilled, onRejected));
|
|
85
|
-
};
|
|
86
|
-
promise.catch = (onRejected) => {
|
|
87
|
-
markObserved();
|
|
88
|
-
return trackRunObservation(trackedObservations(promise), originalCatch(onRejected));
|
|
89
|
-
};
|
|
90
|
-
promise.finally = (onFinally) => {
|
|
91
|
-
markObserved();
|
|
92
|
-
return trackRunObservation(trackedObservations(promise), originalFinally(onFinally));
|
|
93
|
-
};
|
|
94
|
-
return promise;
|
|
172
|
+
const tracker = trackedObservationTracker(promise) ?? { observations: [], consumed: false };
|
|
173
|
+
addTrackedObservations(tracker, observations);
|
|
174
|
+
return trackObservationTracker(tracker, promise);
|
|
95
175
|
}
|
|
96
176
|
|
|
97
177
|
function trackPromiseCombinator(items, createPromise) {
|
|
98
178
|
const values = Array.from(items);
|
|
99
|
-
const
|
|
100
|
-
const promise =
|
|
101
|
-
|
|
179
|
+
const dependencies = [...new Set(values.map(promiseObservationTracker).filter(Boolean))];
|
|
180
|
+
const promise = withSuppressedNativePromiseConsumption(() => createPromise(values.map(trackedPromiseTarget)));
|
|
181
|
+
if (dependencies.length === 0) return promise;
|
|
182
|
+
return trackObservationTracker({ observations: [], consumed: false, dependencies }, promise, true);
|
|
102
183
|
}
|
|
103
184
|
|
|
104
185
|
const workflowPromise = new Proxy(Promise, {
|
|
105
|
-
construct(target,
|
|
106
|
-
return new target(
|
|
186
|
+
construct(target, [executor]) {
|
|
187
|
+
if (typeof executor !== "function") return new target(executor);
|
|
188
|
+
const tracker = { observations: [], consumed: false };
|
|
189
|
+
const promise = new target((resolve, reject) => {
|
|
190
|
+
let settled = false;
|
|
191
|
+
try {
|
|
192
|
+
executor((value) => {
|
|
193
|
+
if (settled) return;
|
|
194
|
+
settled = true;
|
|
195
|
+
addTrackerDependency(tracker, promiseObservationTracker(value));
|
|
196
|
+
resolve(trackedPromiseTarget(value));
|
|
197
|
+
}, (reason) => {
|
|
198
|
+
if (settled) return;
|
|
199
|
+
settled = true;
|
|
200
|
+
reject(reason);
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
settled = true;
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
return trackObservationTracker(tracker, promise, true);
|
|
107
208
|
},
|
|
108
209
|
get(target, prop) {
|
|
109
210
|
if (prop === "all") return (items) => trackPromiseCombinator(items, (values) => target.all(values));
|
|
@@ -111,9 +212,10 @@ const workflowPromise = new Proxy(Promise, {
|
|
|
111
212
|
if (prop === "race") return (items) => trackPromiseCombinator(items, (values) => target.race(values));
|
|
112
213
|
if (prop === "any") return (items) => trackPromiseCombinator(items, (values) => target.any(values));
|
|
113
214
|
if (prop === "resolve") return (value) => {
|
|
114
|
-
const
|
|
115
|
-
const promise =
|
|
116
|
-
|
|
215
|
+
const dependency = promiseObservationTracker(value);
|
|
216
|
+
const promise = withSuppressedNativePromiseConsumption(() => target.resolve(trackedPromiseTarget(value)));
|
|
217
|
+
if (!dependency) return promise;
|
|
218
|
+
return trackObservationTracker({ observations: [], consumed: false, dependencies: [dependency] }, promise, true);
|
|
117
219
|
};
|
|
118
220
|
const value = target[prop];
|
|
119
221
|
return typeof value === "function" ? value.bind(target) : value;
|
|
@@ -126,8 +228,8 @@ function hostCall(method, args, observation) {
|
|
|
126
228
|
pending.set(callId, { resolve, reject });
|
|
127
229
|
parentPort.postMessage({ type: "call", callId, method, args });
|
|
128
230
|
});
|
|
129
|
-
return observation && typeof observation.key === "string"
|
|
130
|
-
? trackRunObservation([{ key: observation.key, callId }], promise)
|
|
231
|
+
return observation && typeof observation.key === "string" && typeof observation.operation === "string"
|
|
232
|
+
? trackRunObservation([{ key: observation.key, operation: observation.operation, callId }], promise)
|
|
131
233
|
: promise;
|
|
132
234
|
}
|
|
133
235
|
|
|
@@ -156,6 +258,7 @@ function validateRunCall(key, params, label, fingerprints) {
|
|
|
156
258
|
const hint = label === "runs.run" ? "; use runs.all(...) and JavaScript control flow for orchestration." : ".";
|
|
157
259
|
throw new Error(label + " accepts one child via { agent, task } and execution controls only" + hint);
|
|
158
260
|
}
|
|
261
|
+
if (Object.prototype.hasOwnProperty.call(params, "clarify")) throw new Error(label + " does not support clarify UI.");
|
|
159
262
|
if (params.worktree !== undefined && typeof params.worktree !== "boolean") throw new Error(label + " worktree must be true or false.");
|
|
160
263
|
if (params.gate !== undefined && (typeof params.gate !== "string" || !params.gate.trim())) throw new Error(label + " gate must be a non-empty command string.");
|
|
161
264
|
if (params.gate !== undefined && params.acceptance !== undefined) throw new Error(label + " gate cannot be combined with acceptance; use one gate command or acceptance.verify.");
|
|
@@ -173,7 +276,7 @@ function validateRunCall(key, params, label, fingerprints) {
|
|
|
173
276
|
const runs = Object.freeze({
|
|
174
277
|
run(key, params) {
|
|
175
278
|
validateRunCall(key, params, "runs.run", runFingerprints);
|
|
176
|
-
return hostCall("run", { key, params }, { key });
|
|
279
|
+
return hostCall("run", { key, params }, { key, operation: "run" });
|
|
177
280
|
},
|
|
178
281
|
all(items) {
|
|
179
282
|
if (!Array.isArray(items)) throw new Error("runs.all(items) requires an array.");
|
|
@@ -190,7 +293,18 @@ const runs = Object.freeze({
|
|
|
190
293
|
for (const { key, params } of calls) runFingerprints.set(key, stableRunJson(params));
|
|
191
294
|
const batch = { id: "batch-" + (++nextCallId), calls };
|
|
192
295
|
const launched = calls.map(({ key, params }) => runHostCall(key, params, true, batch));
|
|
193
|
-
return trackRunObservation(launched.map(({ key, callId }) => ({ key, callId })), Promise.all(launched.map(({ promise }) => promise)));
|
|
296
|
+
return trackRunObservation(launched.map(({ key, callId }) => ({ key, operation: "run", callId })), Promise.all(launched.map(({ promise }) => promise)));
|
|
297
|
+
},
|
|
298
|
+
steer(key, message, options = {}) {
|
|
299
|
+
if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error("runs.steer has an invalid key.");
|
|
300
|
+
if (typeof message !== "string" || !message.trim()) throw new Error("runs.steer message must be a non-empty string.");
|
|
301
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) throw new Error("runs.steer options must be an object.");
|
|
302
|
+
const allowed = new Set(["mode", "index", "ackTimeoutMs"]);
|
|
303
|
+
for (const option of Object.keys(options)) if (!allowed.has(option)) throw new Error("runs.steer options contain unsupported field '" + option + "'.");
|
|
304
|
+
if (options.mode !== undefined && options.mode !== "steer" && options.mode !== "follow_up" && options.mode !== "auto") throw new Error("runs.steer mode must be 'steer', 'follow_up', or 'auto'.");
|
|
305
|
+
if (options.index !== undefined && (!Number.isInteger(options.index) || options.index < 0 || options.index > 1000000)) throw new Error("runs.steer index must be an integer between 0 and 1000000.");
|
|
306
|
+
if (options.ackTimeoutMs !== undefined && (!Number.isInteger(options.ackTimeoutMs) || options.ackTimeoutMs < 1)) throw new Error("runs.steer ackTimeoutMs must be a positive integer.");
|
|
307
|
+
return hostCall("steer", { key, message: message.trim(), options }, { key, operation: "steer" });
|
|
194
308
|
},
|
|
195
309
|
status(keyOrRunId) { return hostCall("status", { keyOrRunId }); },
|
|
196
310
|
ref: formatRef,
|
|
@@ -214,14 +328,6 @@ const state = Object.freeze({
|
|
|
214
328
|
},
|
|
215
329
|
});
|
|
216
330
|
|
|
217
|
-
const prompts = Object.freeze({
|
|
218
|
-
render(ref, vars) {
|
|
219
|
-
if (typeof ref !== "string" || !ref.trim()) throw new Error("prompts.render(ref, vars) requires a non-empty ref string.");
|
|
220
|
-
if (vars !== undefined) assertJsonValue(vars, "prompts.render vars");
|
|
221
|
-
return hostCall("prompts.render", { ref, vars });
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
331
|
let contextObjectPrototype;
|
|
226
332
|
|
|
227
333
|
const capturedConsole = Object.freeze(Object.fromEntries(
|
|
@@ -231,7 +337,7 @@ const capturedConsole = Object.freeze(Object.fromEntries(
|
|
|
231
337
|
));
|
|
232
338
|
|
|
233
339
|
function formatWorkflowScriptSyntaxError(error) {
|
|
234
|
-
const details =
|
|
340
|
+
const details = formatWorkflowScriptError(error);
|
|
235
341
|
return [
|
|
236
342
|
"workflowScript must be valid JavaScript.",
|
|
237
343
|
"If task text contains Markdown fences or backticks, use an array joined with \"\\n\" or escaped strings instead of a raw backtick template literal.",
|
|
@@ -241,6 +347,52 @@ function formatWorkflowScriptSyntaxError(error) {
|
|
|
241
347
|
].join("\n");
|
|
242
348
|
}
|
|
243
349
|
|
|
350
|
+
function formatWorkflowScriptError(error) {
|
|
351
|
+
const message = error && typeof error.message === "string" ? error.message : String(error);
|
|
352
|
+
const stack = error && typeof error.stack === "string" ? error.stack : "";
|
|
353
|
+
if (!stack) return message;
|
|
354
|
+
return stack.includes(message) ? stack : message + "\n" + stack;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function isSyntaxError(error) {
|
|
358
|
+
return error instanceof SyntaxError || error?.name === "SyntaxError";
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const NESTED_ASYNC_WORKFLOW_ERROR = "workflowScript does not support nested async functions. Use top-level await, plain helper functions that return runs.run(...), or explicit Promise chains so workflows stay portable across Node and Bun.";
|
|
362
|
+
const AST_SCALAR_KEYS = new Set(["type", "start", "end"]);
|
|
363
|
+
|
|
364
|
+
function assertPortableWorkflowScript(source) {
|
|
365
|
+
const wrapped = "(async () => {\n" + source + "\n})()";
|
|
366
|
+
const ast = parse(wrapped, { ecmaVersion: "latest", sourceType: "script" });
|
|
367
|
+
const wrapper = workflowWrapperFunction(ast);
|
|
368
|
+
walkWorkflowAst(wrapper.body, wrapper);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function workflowWrapperFunction(ast) {
|
|
372
|
+
const wrapper = ast.body?.[0]?.expression?.callee;
|
|
373
|
+
if (!wrapper || wrapper.type !== "ArrowFunctionExpression") throw new Error("workflowScript wrapper parse invariant failed.");
|
|
374
|
+
return wrapper;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function isAsyncFunctionNode(node) {
|
|
378
|
+
return node.async === true && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function walkWorkflowAst(node, allowedAsyncFunction) {
|
|
382
|
+
if (!node || typeof node !== "object") return;
|
|
383
|
+
if (Array.isArray(node)) {
|
|
384
|
+
for (const item of node) walkWorkflowAst(item, allowedAsyncFunction);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (node !== allowedAsyncFunction && isAsyncFunctionNode(node)) {
|
|
388
|
+
throw new Error(NESTED_ASYNC_WORKFLOW_ERROR);
|
|
389
|
+
}
|
|
390
|
+
for (const [key, child] of Object.entries(node)) {
|
|
391
|
+
if (AST_SCALAR_KEYS.has(key)) continue;
|
|
392
|
+
walkWorkflowAst(child, allowedAsyncFunction);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
244
396
|
function assertJsonValue(value, path = "emit", seen = new Set()) {
|
|
245
397
|
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
246
398
|
if (typeof value === "number") {
|
|
@@ -289,29 +441,89 @@ parentPort.on("message", async (message) => {
|
|
|
289
441
|
if (!entry) return;
|
|
290
442
|
pending.delete(message.callId);
|
|
291
443
|
if (message.ok) entry.resolve(message.value);
|
|
292
|
-
else
|
|
444
|
+
else {
|
|
445
|
+
const error = new Error(message.error);
|
|
446
|
+
if (message.errorKind === "detached-child") error.workflowErrorKind = "detached-child";
|
|
447
|
+
entry.reject(error);
|
|
448
|
+
}
|
|
293
449
|
return;
|
|
294
450
|
}
|
|
295
451
|
if (message.type !== "start") return;
|
|
296
452
|
try {
|
|
297
|
-
const sandbox = { runs,
|
|
453
|
+
const sandbox = { runs, Promise: workflowPromise, emit(value) { assertJsonValue(value); parentPort.postMessage({ type: "emit", value }); }, console: capturedConsole };
|
|
298
454
|
if (message.stateEnabled) sandbox.state = state;
|
|
299
455
|
const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
|
|
300
456
|
contextObjectPrototype = vm.runInContext("Object.prototype", context);
|
|
301
457
|
let compiled;
|
|
302
458
|
try {
|
|
459
|
+
assertPortableWorkflowScript(message.script);
|
|
303
460
|
compiled = new vm.Script("(async () => {\n" + message.script + "\n})()", { filename: "workflow-script.js" });
|
|
304
461
|
} catch (error) {
|
|
305
|
-
|
|
306
|
-
parentPort.postMessage({ type: "error", error: formatWorkflowScriptSyntaxError(error) });
|
|
462
|
+
parentPort.postMessage({ type: "error", error: isSyntaxError(error) ? formatWorkflowScriptSyntaxError(error) : formatWorkflowScriptError(error) });
|
|
307
463
|
return;
|
|
308
464
|
}
|
|
309
|
-
const
|
|
465
|
+
const nativePromisePrototype = vm.runInContext("(async () => {})().constructor.prototype", context);
|
|
466
|
+
const nativeThenDescriptor = Object.getOwnPropertyDescriptor(nativePromisePrototype, "then");
|
|
467
|
+
if (!nativeThenDescriptor || typeof nativeThenDescriptor.value !== "function") throw new Error("workflowScript could not inspect the VM Promise.prototype.then method.");
|
|
468
|
+
const nativeThen = nativeThenDescriptor.value;
|
|
469
|
+
let stopWorkflowPromiseHook;
|
|
470
|
+
let value;
|
|
471
|
+
try {
|
|
472
|
+
Object.defineProperty(nativePromisePrototype, "then", {
|
|
473
|
+
...nativeThenDescriptor,
|
|
474
|
+
value: function workflowPromiseThen(...args) {
|
|
475
|
+
if (isDirectWorkflowScriptPromiseHandlerCall() || suppressNativePromiseConsumption > 0) {
|
|
476
|
+
return withSuppressedNativePromiseConsumption(() => Reflect.apply(nativeThen, this, args));
|
|
477
|
+
}
|
|
478
|
+
return Reflect.apply(nativeThen, this, args);
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
stopWorkflowPromiseHook = createWorkflowPromiseHook({
|
|
482
|
+
before(promise) {
|
|
483
|
+
activeNativePromises.push(promise);
|
|
484
|
+
},
|
|
485
|
+
after(promise) {
|
|
486
|
+
const index = activeNativePromises.lastIndexOf(promise);
|
|
487
|
+
if (index !== -1) activeNativePromises.splice(index, 1);
|
|
488
|
+
},
|
|
489
|
+
init(promise, parent) {
|
|
490
|
+
const childTracker = nativePromiseTracker(promise);
|
|
491
|
+
if (!parent) return;
|
|
492
|
+
nativePromiseParents.set(promise, parent);
|
|
493
|
+
const parentTracker = nativePromiseTracker(parent);
|
|
494
|
+
addTrackerDependency(childTracker, parentTracker);
|
|
495
|
+
const activePromise = activeNativePromises.at(-1);
|
|
496
|
+
if (activePromise && activePromise !== parent) {
|
|
497
|
+
addTrackerDependency(nativePromiseTracker(activePromise), parentTracker);
|
|
498
|
+
} else if (!activePromise && suppressNativePromiseConsumption === 0) {
|
|
499
|
+
markTrackedObservationsConsumed(parentTracker);
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
const workflowResultPromise = compiled.runInContext(context);
|
|
504
|
+
topLevelWorkflowPromise = workflowResultPromise;
|
|
505
|
+
markTrackedObservationsConsumed(nativePromiseTracker(workflowResultPromise));
|
|
506
|
+
value = await workflowResultPromise;
|
|
507
|
+
} finally {
|
|
508
|
+
try {
|
|
509
|
+
stopWorkflowPromiseHook?.();
|
|
510
|
+
} finally {
|
|
511
|
+
try {
|
|
512
|
+
Object.defineProperty(nativePromisePrototype, "then", nativeThenDescriptor);
|
|
513
|
+
} finally {
|
|
514
|
+
topLevelWorkflowPromise = undefined;
|
|
515
|
+
activeNativePromises.length = 0;
|
|
516
|
+
suppressNativePromiseConsumption = 0;
|
|
517
|
+
nativePromiseTrackers = new WeakMap();
|
|
518
|
+
nativePromiseParents = new WeakMap();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
310
522
|
const persistedValue = value === undefined ? null : omitUndefinedWorkflowValues(value);
|
|
311
523
|
assertJsonValue(persistedValue, "return");
|
|
312
524
|
parentPort.postMessage({ type: "complete", value: persistedValue });
|
|
313
525
|
} catch (error) {
|
|
314
|
-
parentPort.postMessage({ type: "error", error: error && error.
|
|
526
|
+
parentPort.postMessage({ type: "error", error: isSyntaxError(error) ? formatWorkflowScriptSyntaxError(error) : formatWorkflowScriptError(error), ...(error && error.workflowErrorKind === "detached-child" ? { errorKind: "detached-child" } : {}) });
|
|
315
527
|
}
|
|
316
528
|
});
|
|
317
529
|
`;
|
|
@@ -324,15 +536,16 @@ export interface WorkflowScriptChildResult {
|
|
|
324
536
|
runId?: string;
|
|
325
537
|
output: string;
|
|
326
538
|
error?: string;
|
|
539
|
+
detached?: boolean;
|
|
327
540
|
structuredOutput?: unknown;
|
|
328
541
|
artifactPaths: string[];
|
|
329
542
|
results?: unknown[];
|
|
330
543
|
}
|
|
331
544
|
|
|
332
545
|
export interface WorkflowScriptTraceEntry {
|
|
333
|
-
operation: "run" | "status";
|
|
546
|
+
operation: "run" | "status" | "steer";
|
|
334
547
|
key: string;
|
|
335
|
-
state: "started" | "completed" | "failed" | "stopped" | "reused";
|
|
548
|
+
state: "started" | "completed" | "failed" | "detached" | "stopped" | "reused" | "queued" | "delivered" | "missed";
|
|
336
549
|
/** Canonical child agent name when resolved launch or result data is available. */
|
|
337
550
|
agent?: string;
|
|
338
551
|
runId?: string;
|
|
@@ -342,6 +555,21 @@ export interface WorkflowScriptTraceEntry {
|
|
|
342
555
|
error?: string;
|
|
343
556
|
}
|
|
344
557
|
|
|
558
|
+
export interface WorkflowSteerOptions {
|
|
559
|
+
mode?: "steer" | "follow_up" | "auto";
|
|
560
|
+
index?: number;
|
|
561
|
+
ackTimeoutMs?: number;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export interface WorkflowSteerResult {
|
|
565
|
+
key: string;
|
|
566
|
+
state: "queued" | "delivered" | "missed" | "failed";
|
|
567
|
+
requestId?: string;
|
|
568
|
+
deliveryStatus?: "queued" | "delivered";
|
|
569
|
+
targets?: Array<{ index: number; state: string; reason?: string }>;
|
|
570
|
+
error?: string;
|
|
571
|
+
}
|
|
572
|
+
|
|
345
573
|
export interface WorkflowScriptResult {
|
|
346
574
|
value: unknown;
|
|
347
575
|
emits: unknown[];
|
|
@@ -352,11 +580,13 @@ export interface WorkflowScriptResult {
|
|
|
352
580
|
|
|
353
581
|
export class WorkflowScriptError extends Error {
|
|
354
582
|
readonly partial: Omit<WorkflowScriptResult, "value">;
|
|
583
|
+
readonly errorKind?: "detached-child";
|
|
355
584
|
|
|
356
|
-
constructor(message: string, partial: Omit<WorkflowScriptResult, "value"
|
|
585
|
+
constructor(message: string, partial: Omit<WorkflowScriptResult, "value">, errorKind?: "detached-child") {
|
|
357
586
|
super(message);
|
|
358
587
|
this.name = "WorkflowScriptError";
|
|
359
588
|
this.partial = partial;
|
|
589
|
+
this.errorKind = errorKind;
|
|
360
590
|
}
|
|
361
591
|
}
|
|
362
592
|
|
|
@@ -367,13 +597,11 @@ export interface RunWorkflowScriptOptions {
|
|
|
367
597
|
admit?: (calls: Array<{ key: string; params: Record<string, unknown> }>) => void | Promise<void>;
|
|
368
598
|
launch: (key: string, params: Record<string, unknown>, signal: AbortSignal, admission: { admitted: boolean }) => Promise<WorkflowScriptChildResult>;
|
|
369
599
|
status: (keyOrRunId: string, signal: AbortSignal) => Promise<WorkflowScriptChildResult>;
|
|
600
|
+
steer?: (key: string, message: string, options: WorkflowSteerOptions, signal: AbortSignal) => Promise<WorkflowSteerResult>;
|
|
370
601
|
state?: {
|
|
371
602
|
get: (key: string) => unknown | Promise<unknown>;
|
|
372
603
|
set: (key: string, value: unknown) => void | Promise<void>;
|
|
373
604
|
};
|
|
374
|
-
prompts?: {
|
|
375
|
-
render: (ref: string, vars?: unknown) => string | Promise<string>;
|
|
376
|
-
};
|
|
377
605
|
onTrace?: (trace: WorkflowScriptTraceEntry[]) => void;
|
|
378
606
|
onEmit?: (emits: unknown[]) => void;
|
|
379
607
|
}
|
|
@@ -481,22 +709,50 @@ function workflowStringMetadata(params: Record<string, unknown>): Pick<WorkflowS
|
|
|
481
709
|
};
|
|
482
710
|
}
|
|
483
711
|
|
|
712
|
+
function resolveWorkflowParserEntry(): string {
|
|
713
|
+
try {
|
|
714
|
+
return requireFromPackage.resolve("acorn");
|
|
715
|
+
} catch (primaryError) {
|
|
716
|
+
// Some runtimes (e.g. Bun-compiled single-file binaries) fail bare
|
|
717
|
+
// package-specifier resolution through createRequire while subpath
|
|
718
|
+
// resolution still works. Resolve the manifest and derive the
|
|
719
|
+
// CommonJS entry from its "main" field instead.
|
|
720
|
+
try {
|
|
721
|
+
const manifestPath = requireFromPackage.resolve("acorn/package.json");
|
|
722
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as { main?: unknown };
|
|
723
|
+
const entry = typeof manifest.main === "string" && manifest.main ? manifest.main : "./dist/acorn.js";
|
|
724
|
+
return resolvePath(dirname(manifestPath), entry);
|
|
725
|
+
} catch {
|
|
726
|
+
throw primaryError;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
484
731
|
export async function runWorkflowScript(options: RunWorkflowScriptOptions): Promise<WorkflowScriptResult> {
|
|
485
732
|
if (!options.script.trim()) throw new Error("workflowScript must not be empty.");
|
|
486
733
|
if (options.timeoutMs !== undefined && (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1)) throw new Error("workflow script timeout must be a positive integer.");
|
|
487
734
|
|
|
488
|
-
|
|
735
|
+
let acornPath: string;
|
|
736
|
+
try {
|
|
737
|
+
acornPath = resolveWorkflowParserEntry();
|
|
738
|
+
} catch (error) {
|
|
739
|
+
throw new Error("Workflow parser dependency 'acorn' is unavailable from pi-subagents. Reinstall pi-subagents dependencies before launching workflowScript.", { cause: error });
|
|
740
|
+
}
|
|
741
|
+
const worker = new Worker(WORKER_SOURCE, { eval: true, workerData: { acornPath } });
|
|
489
742
|
const emits: unknown[] = [];
|
|
490
743
|
const consoleEntries: WorkflowScriptResult["console"] = [];
|
|
491
744
|
const trace: WorkflowScriptTraceEntry[] = [];
|
|
492
745
|
const children = new Map<string, WorkflowScriptChildResult>();
|
|
493
746
|
const childOrder: string[] = [];
|
|
494
747
|
const launches = new Map<string, { fingerprint: string; promise: Promise<WorkflowScriptChildResult>; observed: boolean }>();
|
|
748
|
+
const steers = new Map<number, { key: string; promise: Promise<WorkflowSteerResult>; observed: boolean }>();
|
|
495
749
|
const stoppedLaunches = new Set<string>();
|
|
496
750
|
const batchAdmissions = new Map<string, Promise<void>>();
|
|
497
751
|
const observedRunCalls = new Set<number>();
|
|
752
|
+
const observedSteerCalls = new Set<number>();
|
|
498
753
|
const childController = new AbortController();
|
|
499
754
|
let settled = false;
|
|
755
|
+
let finishing = false;
|
|
500
756
|
|
|
501
757
|
const partial = (): Omit<WorkflowScriptResult, "value"> => ({ emits, console: consoleEntries, trace, children: childOrder.flatMap((key) => {
|
|
502
758
|
const child = children.get(key);
|
|
@@ -505,20 +761,29 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
505
761
|
const traceChanged = () => options.onTrace?.([...trace]);
|
|
506
762
|
|
|
507
763
|
return await new Promise<WorkflowScriptResult>((resolve, reject) => {
|
|
508
|
-
const finish = (outcome: { value: unknown } | { error: Error }) => {
|
|
509
|
-
if (settled) return;
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
764
|
+
const finish = (outcome: { value: unknown } | { error: Error & { workflowErrorKind?: unknown } }) => {
|
|
765
|
+
if (settled || finishing) return;
|
|
766
|
+
finishing = true;
|
|
767
|
+
childController.abort("error" in outcome ? outcome.error : new Error("Workflow script completed."));
|
|
768
|
+
void Promise.allSettled([...steers.values()].map(({ promise }) => promise)).then(() => {
|
|
769
|
+
if (settled) return;
|
|
770
|
+
settled = true;
|
|
771
|
+
if (timer) clearTimeout(timer);
|
|
772
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
773
|
+
void worker.terminate();
|
|
774
|
+
const unobservedKeys = "value" in outcome ? [...launches].filter(([, launch]) => !launch.observed).map(([key]) => key) : [];
|
|
775
|
+
const completionError = unobservedKeys.length > 0
|
|
776
|
+
? new Error(`workflowScript completed with unawaited runs.run launch(es): ${unobservedKeys.map((key) => `'${key}'`).join(", ")}. For ordinary parallel fanout use await runs.all([{key, agent, task}, ...]); do not read .output from unawaited launches.`)
|
|
777
|
+
: "value" in outcome
|
|
778
|
+
? (() => {
|
|
779
|
+
const unobservedSteers = [...steers.values()].filter((steer) => !steer.observed).map((steer) => steer.key);
|
|
780
|
+
return unobservedSteers.length > 0 ? new Error(`workflowScript completed with unawaited runs.steer call(s): ${unobservedSteers.map((key) => `'${key}'`).join(", ")}. Await or return each call.`) : undefined;
|
|
781
|
+
})()
|
|
782
|
+
: undefined;
|
|
783
|
+
if ("error" in outcome) reject(new WorkflowScriptError(outcome.error.message, partial(), outcome.error.workflowErrorKind === "detached-child" ? "detached-child" : undefined));
|
|
784
|
+
else if (completionError) reject(new WorkflowScriptError(completionError.message, partial()));
|
|
785
|
+
else resolve({ value: outcome.value, ...partial() });
|
|
786
|
+
});
|
|
522
787
|
};
|
|
523
788
|
const onAbort = () => {
|
|
524
789
|
const signalReason = options.signal?.reason;
|
|
@@ -585,12 +850,22 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
585
850
|
}
|
|
586
851
|
return finish({ value: message.value });
|
|
587
852
|
}
|
|
588
|
-
if (message.type === "error")
|
|
589
|
-
|
|
853
|
+
if (message.type === "error") {
|
|
854
|
+
const workflowError = new Error(typeof message.error === "string" ? message.error : "Workflow script failed.") as Error & { workflowErrorKind?: "detached-child" };
|
|
855
|
+
if (message.errorKind === "detached-child") workflowError.workflowErrorKind = "detached-child";
|
|
856
|
+
return finish({ error: workflowError });
|
|
857
|
+
}
|
|
858
|
+
if (message.type === "callObserved" && typeof message.callId === "number") {
|
|
590
859
|
const key = typeof message.key === "string" ? message.key : undefined;
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
860
|
+
if (message.operation === "run") {
|
|
861
|
+
const launch = key ? launches.get(key) : undefined;
|
|
862
|
+
if (launch) launch.observed = true;
|
|
863
|
+
else observedRunCalls.add(message.callId);
|
|
864
|
+
} else if (message.operation === "steer") {
|
|
865
|
+
const steer = steers.get(message.callId);
|
|
866
|
+
if (steer) steer.observed = true;
|
|
867
|
+
else observedSteerCalls.add(message.callId);
|
|
868
|
+
}
|
|
594
869
|
return;
|
|
595
870
|
}
|
|
596
871
|
if (message.type !== "call" || typeof message.callId !== "number" || typeof message.method !== "string" || !isRecord(message.args)) return;
|
|
@@ -601,22 +876,10 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
601
876
|
if (!settled) worker.postMessage({ type: "response", callId: message.callId, ok: true, value: omitUndefinedWorkflowValues(value) });
|
|
602
877
|
},
|
|
603
878
|
(error: unknown) => {
|
|
604
|
-
if (!settled) worker.postMessage({ type: "response", callId: message.callId, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
879
|
+
if (!settled) worker.postMessage({ type: "response", callId: message.callId, ok: false, error: error instanceof Error ? error.message : String(error), ...(error instanceof Error && (error as { workflowErrorKind?: unknown }).workflowErrorKind === "detached-child" ? { errorKind: "detached-child" } : {}) });
|
|
605
880
|
},
|
|
606
881
|
);
|
|
607
882
|
};
|
|
608
|
-
|
|
609
|
-
if (message.method === "prompts.render") {
|
|
610
|
-
if (!options.prompts) return respond(Promise.reject(new Error("Workflow prompt rendering is unavailable.")));
|
|
611
|
-
const ref = message.args.ref;
|
|
612
|
-
const vars = message.args.vars;
|
|
613
|
-
if (typeof ref !== "string" || !ref.trim()) return respond(Promise.reject(new Error("prompts.render(ref, vars) requires a non-empty ref string.")));
|
|
614
|
-
return respond(Promise.resolve().then(() => options.prompts!.render(ref, vars)).then((rendered) => {
|
|
615
|
-
if (typeof rendered !== "string") throw new Error("prompts.render must return task text.");
|
|
616
|
-
return rendered;
|
|
617
|
-
}));
|
|
618
|
-
}
|
|
619
|
-
|
|
620
883
|
if (message.method === "state.get" || message.method === "state.set") {
|
|
621
884
|
if (!options.state) return respond(Promise.reject(new Error("Workflow state is unavailable without a mission.")));
|
|
622
885
|
let key: string;
|
|
@@ -642,9 +905,9 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
642
905
|
const target = known?.runId ?? keyOrRunId;
|
|
643
906
|
trace.push({ operation: "status", key: keyOrRunId, state: "started", ...(known?.runId ? { runId: known.runId } : {}) });
|
|
644
907
|
traceChanged();
|
|
645
|
-
if (settled) return;
|
|
908
|
+
if (settled || finishing) return;
|
|
646
909
|
respond(options.status(target, childController.signal).then((result) => {
|
|
647
|
-
if (settled) return result;
|
|
910
|
+
if (settled || finishing) return result;
|
|
648
911
|
trace.push({ operation: "status", key: keyOrRunId, state: result.ok ? "completed" : "failed", ...(result.runId ? { runId: result.runId } : {}), ...(!result.ok ? { error: result.output } : {}) });
|
|
649
912
|
traceChanged();
|
|
650
913
|
if (!result.ok) throw new Error(`Status '${keyOrRunId}' failed: ${result.output}`);
|
|
@@ -652,6 +915,37 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
652
915
|
}));
|
|
653
916
|
return;
|
|
654
917
|
}
|
|
918
|
+
if (message.method === "steer") {
|
|
919
|
+
let key: string;
|
|
920
|
+
try {
|
|
921
|
+
key = validateKey(message.args.key, "runs.steer");
|
|
922
|
+
} catch (error) {
|
|
923
|
+
return respond(Promise.reject(error));
|
|
924
|
+
}
|
|
925
|
+
const steerMessage = message.args.message;
|
|
926
|
+
if (typeof steerMessage !== "string" || !steerMessage.trim()) return respond(Promise.reject(new Error(`runs.steer('${key}') requires a non-empty message.`)));
|
|
927
|
+
const steerOptions = isRecord(message.args.options) ? message.args.options as WorkflowSteerOptions : {};
|
|
928
|
+
const startedAt = Date.now();
|
|
929
|
+
trace.push({ operation: "steer", key, state: "started" });
|
|
930
|
+
traceChanged();
|
|
931
|
+
const promise = Promise.resolve().then(() => {
|
|
932
|
+
if (!launches.has(key)) throw new Error(`runs.steer('${key}') requires a prior runs.run/runs.all launch with that key.`);
|
|
933
|
+
if (!options.steer) throw new Error("Workflow steering is unavailable in this host.");
|
|
934
|
+
return options.steer(key, steerMessage.trim(), steerOptions, childController.signal);
|
|
935
|
+
}).then((receipt) => {
|
|
936
|
+
trace.push({ operation: "steer", key, state: receipt.state, durationMs: Date.now() - startedAt, ...(receipt.error ? { error: receipt.error } : {}) });
|
|
937
|
+
traceChanged();
|
|
938
|
+
return receipt;
|
|
939
|
+
}, (error: unknown) => {
|
|
940
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
941
|
+
trace.push({ operation: "steer", key, state: "failed", durationMs: Date.now() - startedAt, error: text });
|
|
942
|
+
traceChanged();
|
|
943
|
+
throw error;
|
|
944
|
+
});
|
|
945
|
+
steers.set(message.callId, { key, promise, observed: observedSteerCalls.delete(message.callId) });
|
|
946
|
+
respond(promise);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
655
949
|
if (message.method !== "run") return respond(Promise.reject(new Error(`Unknown runs API method '${message.method}'.`)));
|
|
656
950
|
|
|
657
951
|
let key: string;
|
|
@@ -693,7 +987,11 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
693
987
|
const deliver = (promise: Promise<WorkflowScriptChildResult>) => collectFailure
|
|
694
988
|
? promise
|
|
695
989
|
: promise.then((result) => {
|
|
696
|
-
if (!result.ok)
|
|
990
|
+
if (!result.ok) {
|
|
991
|
+
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" };
|
|
992
|
+
if (result.detached) childError.workflowErrorKind = "detached-child";
|
|
993
|
+
throw childError;
|
|
994
|
+
}
|
|
697
995
|
return result;
|
|
698
996
|
});
|
|
699
997
|
const fingerprint = stableJson(params);
|
|
@@ -719,13 +1017,13 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
719
1017
|
return true;
|
|
720
1018
|
});
|
|
721
1019
|
admission = Promise.resolve().then(() => {
|
|
722
|
-
if (settled) return;
|
|
1020
|
+
if (settled || finishing) return;
|
|
723
1021
|
return options.admit?.(calls);
|
|
724
1022
|
});
|
|
725
1023
|
if (batch) batchAdmissions.set(batch.id, admission);
|
|
726
1024
|
}
|
|
727
1025
|
const promise = admission.then(() => {
|
|
728
|
-
if (settled || stoppedLaunches.has(key)) {
|
|
1026
|
+
if (settled || finishing || stoppedLaunches.has(key)) {
|
|
729
1027
|
const reason = childController.signal.reason;
|
|
730
1028
|
const text = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "Workflow script aborted.";
|
|
731
1029
|
return { key, ok: false, output: text, error: text, artifactPaths: [] };
|
|
@@ -735,7 +1033,8 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
735
1033
|
const normalized = !result.ok && !result.error ? { ...result, error: result.output } : result;
|
|
736
1034
|
if (stoppedLaunches.has(key)) return normalized;
|
|
737
1035
|
children.set(key, normalized);
|
|
738
|
-
|
|
1036
|
+
const state = normalized.ok ? "completed" : normalized.detached ? "detached" : "failed";
|
|
1037
|
+
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 } : {}) });
|
|
739
1038
|
traceChanged();
|
|
740
1039
|
return normalized;
|
|
741
1040
|
}, (error: unknown) => {
|