pi-subagents 0.83.0 → 0.84.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 +39 -2
- package/bundle/agents.js +4 -4
- package/bundle/capability-ceiling.js +1 -1
- package/bundle/child-tool-plan.js +3 -3
- package/bundle/{chunk-HJJAVHFB.js → chunk-235AWJUQ.js} +35 -32
- package/bundle/{chunk-P2NCCTZP.js → chunk-2D52JFLF.js} +1 -1
- package/bundle/{chunk-WG2OTTAK.js → chunk-2DHZTCOL.js} +2 -2
- package/bundle/{chunk-MBDOYZ2V.js → chunk-2S4K53MV.js} +1 -1
- package/bundle/{chunk-527E3D2J.js → chunk-4E7BKDTX.js} +5 -5
- package/bundle/{chunk-CZTMUBUI.js → chunk-7BWGNDNK.js} +4 -4
- package/bundle/{chunk-HXVKGS33.js → chunk-AKOIVLA5.js} +4 -4
- package/bundle/{chunk-MHX22USZ.js → chunk-C5P5CHYK.js} +1 -1
- package/bundle/{chunk-5ESXK62X.js → chunk-CGN2TVE7.js} +417 -389
- package/bundle/{chunk-VTMNBPCQ.js → chunk-D3JAFGMO.js} +1 -1
- package/bundle/{chunk-CDI3GMNV.js → chunk-DZDETGRJ.js} +18 -15
- package/bundle/{chunk-BKFWQ642.js → chunk-F4ASL6OV.js} +1 -1
- package/bundle/{chunk-JHZ6DZ5M.js → chunk-G2Z6ZH26.js} +1 -1
- package/bundle/{chunk-XXKG3P2W.js → chunk-J5YL7LYR.js} +4 -4
- package/bundle/{chunk-LDMXJIA6.js → chunk-JG4QKI5M.js} +58 -67
- package/bundle/{chunk-OOFW75PV.js → chunk-L3WFKBEI.js} +15 -11
- package/bundle/{chunk-Y3I6H6DV.js → chunk-LFDBD4PW.js} +2 -2
- package/bundle/{chunk-7F5SK2U3.js → chunk-LNRJCP2Y.js} +17 -14
- package/bundle/{chunk-4BW55BTL.js → chunk-NENDSQUY.js} +4 -4
- package/bundle/{chunk-6B4ITO2W.js → chunk-NKQ6JNAS.js} +1 -1
- package/bundle/{chunk-RZJN63GU.js → chunk-PALJGGYC.js} +2 -2
- package/bundle/{chunk-TJQJ3MEA.js → chunk-PV63UFUR.js} +10 -10
- package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
- package/bundle/{chunk-HLE22FSH.js → chunk-SSG7IEP5.js} +2 -2
- package/bundle/{chunk-CSSZRTKY.js → chunk-V2BZYLEP.js} +2 -2
- package/bundle/{chunk-CNLDYEH4.js → chunk-VQVVHSY6.js} +11 -11
- package/bundle/{chunk-CGXWSNPA.js → chunk-XDQORLZ5.js} +4 -4
- package/bundle/control-channel.js +3 -3
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +3 -3
- package/bundle/preflight.js +9 -9
- package/bundle/project-panes.js +4 -4
- package/docs/configuration.md +12 -1
- package/docs/extension-api.md +2 -0
- package/docs/missions.md +2 -0
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agent-management.d.ts +1 -1
- package/src/agents/agent-management.js +2 -2
- package/src/agents/agents.js +1 -2
- package/src/agents/frontmatter.js +22 -18
- package/src/extension/index.js +2 -2
- package/src/missions/actions.js +7 -2
- package/src/missions/lifecycle.d.ts +11 -0
- package/src/missions/lifecycle.js +37 -6
- package/src/missions/store.js +1 -0
- package/src/missions/types.d.ts +2 -0
- package/src/runs/background/async-execution.d.ts +4 -1
- package/src/runs/background/async-execution.js +18 -3
- package/src/runs/background/async-resume.js +3 -2
- package/src/runs/background/async-retention.d.ts +1 -0
- package/src/runs/background/async-retention.js +16 -10
- package/src/runs/background/retained-children.js +1 -1
- package/src/runs/background/run-child-session.d.ts +2 -0
- package/src/runs/background/run-child-session.js +9 -1
- package/src/runs/background/subagent-runner.js +1 -0
- package/src/runs/foreground/subagent-executor.js +29 -51
- package/src/runs/shared/capability-ceiling.d.ts +2 -1
- package/src/runs/shared/capability-ceiling.js +3 -2
- package/src/runs/shared/child-session.d.ts +2 -0
- package/src/runs/shared/child-session.js +1 -0
- package/src/runs/shared/model-resolution-diagnostic.d.ts +11 -0
- package/src/runs/shared/model-resolution-diagnostic.js +25 -0
- package/src/runs/shared/parallel-handoff.d.ts +1 -1
- package/src/runs/shared/parallel-handoff.js +10 -6
- package/src/runs/shared/parallel-utils.d.ts +2 -0
- package/src/shared/types.d.ts +1 -1
- package/src/workflows/scripted-workflow.js +6 -1
- package/workflow-script-worker.cjs +450 -175
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
1
3
|
const { parentPort, workerData } = require("node:worker_threads");
|
|
2
4
|
const vm = require("node:vm");
|
|
3
5
|
const { inspect } = require("node:util");
|
|
@@ -18,6 +20,72 @@ function createWorkflowPromiseHook(callbacks) {
|
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
// Workflow scripts run in their own vm context (the script realm). The API a script can reach (runs,
|
|
24
|
+
// state, emit, console, Promise, results, errors) is built by workflowRuntime inside a second vm
|
|
25
|
+
// context (the runtime realm), compiled from this function's source. Neither realm can compile code
|
|
26
|
+
// from strings, and no worker-realm object may ever become reachable from either: one would hand the
|
|
27
|
+
// script the worker's Function, and with it process. The runtime talks to the worker only through
|
|
28
|
+
// `bridge`, which it keeps in this closure, passes only strings and numbers, and calls in try/catch.
|
|
29
|
+
// Engine paths can still throw a worker-realm error into script code (a stack overflow inside Node's
|
|
30
|
+
// stack formatter, for one), so lockWorkerRealm also makes every worker value useless before a script runs.
|
|
31
|
+
function workflowRuntime(bridge, scriptRealm, stateEnabled) {
|
|
32
|
+
"use strict";
|
|
33
|
+
|
|
34
|
+
// Promise hooks report every promise in the isolate, the worker's own included. Captured before any
|
|
35
|
+
// script runs, so a script that replaces Object.getPrototypeOf cannot receive a worker promise.
|
|
36
|
+
const getPrototypeOf = Object.getPrototypeOf;
|
|
37
|
+
const runtimePromisePrototype = Promise.prototype;
|
|
38
|
+
const nativePromisePrototype = scriptRealm.promisePrototype;
|
|
39
|
+
|
|
40
|
+
function isWorkflowRealmPromise(value) {
|
|
41
|
+
if (!value || typeof value !== "object") return false;
|
|
42
|
+
let prototype = getPrototypeOf(value);
|
|
43
|
+
for (let depth = 0; prototype && depth < 16; depth++) {
|
|
44
|
+
if (prototype === nativePromisePrototype || prototype === runtimePromisePrototype) return true;
|
|
45
|
+
prototype = getPrototypeOf(prototype);
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function post(message) {
|
|
51
|
+
const json = JSON.stringify(message);
|
|
52
|
+
let posted = false;
|
|
53
|
+
try {
|
|
54
|
+
posted = bridge.post(json);
|
|
55
|
+
} catch {}
|
|
56
|
+
if (posted !== true) throw new Error("Workflow runtime could not send a message to the host.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function inspectValue(value) {
|
|
60
|
+
let encoded;
|
|
61
|
+
try {
|
|
62
|
+
encoded = bridge.inspect(value);
|
|
63
|
+
} catch {}
|
|
64
|
+
if (typeof encoded !== "string") throw new Error("console could not format a value.");
|
|
65
|
+
const decoded = JSON.parse(encoded);
|
|
66
|
+
if (decoded[0] !== true) throw new Error(String(decoded[1]));
|
|
67
|
+
return decoded[1];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function utf8ByteLength(value) {
|
|
71
|
+
if (typeof value !== "string") throw new TypeError("Expected a string to measure.");
|
|
72
|
+
let length;
|
|
73
|
+
try {
|
|
74
|
+
length = bridge.utf8Length(value);
|
|
75
|
+
} catch {}
|
|
76
|
+
if (typeof length !== "number" || length < 0) throw new Error("Workflow runtime could not measure a string.");
|
|
77
|
+
return length;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function utf8Prefix(value, maxBytes) {
|
|
81
|
+
let prefix;
|
|
82
|
+
try {
|
|
83
|
+
prefix = bridge.utf8Prefix(value, maxBytes);
|
|
84
|
+
} catch {}
|
|
85
|
+
if (typeof prefix !== "string") throw new Error("Workflow runtime could not truncate a string.");
|
|
86
|
+
return prefix;
|
|
87
|
+
}
|
|
88
|
+
|
|
21
89
|
let nextCallId = 0;
|
|
22
90
|
let topLevelWorkflowPromise;
|
|
23
91
|
let suppressNativePromiseConsumption = 0;
|
|
@@ -26,7 +94,7 @@ const pending = new Map();
|
|
|
26
94
|
const workflowErrorKinds = new WeakMap();
|
|
27
95
|
const runKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
28
96
|
function validGitRef(ref) {
|
|
29
|
-
if (typeof ref !== "string" || !ref || ref === "@" ||
|
|
97
|
+
if (typeof ref !== "string" || !ref || ref === "@" || utf8ByteLength(ref) > 1024 || ref.startsWith("/") || ref.endsWith("/") || ref.includes("//") || ref.includes("..") || ref.includes("@{")) return false;
|
|
30
98
|
if (/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(ref)) return false;
|
|
31
99
|
if (/[[\]\\~^:?*\u0000-\u0020\u007f]/u.test(ref) || ref.endsWith(".") || ref.endsWith(".lock")) return false;
|
|
32
100
|
return ref.split("/").every((component) => component.length > 0 && component !== "." && component !== ".." && !component.startsWith(".") && !component.endsWith(".") && !component.endsWith(".lock"));
|
|
@@ -50,7 +118,13 @@ function canonicalRunParams(params) {
|
|
|
50
118
|
}
|
|
51
119
|
|
|
52
120
|
function isDirectWorkflowScriptPromiseHandlerCall() {
|
|
53
|
-
|
|
121
|
+
let stack;
|
|
122
|
+
try {
|
|
123
|
+
stack = new Error().stack;
|
|
124
|
+
} catch {
|
|
125
|
+
// Near the stack limit the host's formatter overflows and throws its own realm's RangeError.
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
54
128
|
if (typeof stack !== "string") return false;
|
|
55
129
|
return stack.split("\n").some((line) => line.includes("workflow-script.js") && !line.includes("at async "));
|
|
56
130
|
}
|
|
@@ -122,7 +196,7 @@ function addTrackedObservations(tracker, observations) {
|
|
|
122
196
|
for (const observation of tracker.observations) {
|
|
123
197
|
if (observedCallIds.has(observation.callId)) continue;
|
|
124
198
|
observedCallIds.add(observation.callId);
|
|
125
|
-
|
|
199
|
+
post({ type: "callObserved", callId: observation.callId, key: observation.key, operation: observation.operation });
|
|
126
200
|
}
|
|
127
201
|
}
|
|
128
202
|
|
|
@@ -247,7 +321,7 @@ function hostCall(method, args, observation) {
|
|
|
247
321
|
const callId = ++nextCallId;
|
|
248
322
|
const promise = new Promise((resolve, reject) => {
|
|
249
323
|
pending.set(callId, { resolve, reject });
|
|
250
|
-
|
|
324
|
+
post({ type: "call", callId, method, args });
|
|
251
325
|
});
|
|
252
326
|
return observation && typeof observation.key === "string" && typeof observation.operation === "string"
|
|
253
327
|
? trackRunObservation([{ key: observation.key, operation: observation.operation, callId }], promise)
|
|
@@ -258,7 +332,7 @@ function runHostCall(key, params, collectFailure, batch, generatedLaneKey) {
|
|
|
258
332
|
const callId = ++nextCallId;
|
|
259
333
|
const promise = new Promise((resolve, reject) => {
|
|
260
334
|
pending.set(callId, { resolve, reject });
|
|
261
|
-
|
|
335
|
+
post({ type: "call", callId, method: "run", args: { key, params, ...(collectFailure ? { collectFailure: true } : {}), ...(batch ? { batch } : {}), ...(generatedLaneKey ? { generatedLaneKey } : {}) } });
|
|
262
336
|
});
|
|
263
337
|
return { key, callId, promise };
|
|
264
338
|
}
|
|
@@ -300,14 +374,13 @@ function wrapRunsAllResults(results, keys) {
|
|
|
300
374
|
}
|
|
301
375
|
|
|
302
376
|
function laneByteLength(value) {
|
|
303
|
-
return
|
|
377
|
+
return utf8ByteLength(value);
|
|
304
378
|
}
|
|
305
379
|
|
|
306
380
|
function laneBoardText(value) {
|
|
307
381
|
if (typeof value !== "string" || !value) return undefined;
|
|
308
382
|
if (laneByteLength(value) <= MAX_LANE_BOARD_TEXT_BYTES) return value;
|
|
309
|
-
|
|
310
|
-
return new TextDecoder().decode(bytes) + "...";
|
|
383
|
+
return utf8Prefix(value, MAX_LANE_BOARD_TEXT_BYTES - 3) + "...";
|
|
311
384
|
}
|
|
312
385
|
|
|
313
386
|
function laneBoardPath(value) {
|
|
@@ -508,7 +581,7 @@ function workflowPlanStringMetadata(params) {
|
|
|
508
581
|
|
|
509
582
|
function runLanes(laneSpecs) {
|
|
510
583
|
const lanes = validateLaneSpecs(laneSpecs);
|
|
511
|
-
|
|
584
|
+
post({ type: "lanePlan", lanes: lanes.map((lane) => ({
|
|
512
585
|
key: lane.key,
|
|
513
586
|
stages: lane.stages.map((stage) => ({
|
|
514
587
|
key: stage.key,
|
|
@@ -576,7 +649,7 @@ function validateExtensionBindings(value, label) {
|
|
|
576
649
|
}
|
|
577
650
|
}
|
|
578
651
|
visit(value, 0);
|
|
579
|
-
if (
|
|
652
|
+
if (utf8ByteLength(stableRunJson(value)) > 16384) throw new Error(label + " extensionBindings canonical JSON exceeds 16384 bytes.");
|
|
580
653
|
}
|
|
581
654
|
|
|
582
655
|
function validateLaneMetadata(value, label, workflowKey) {
|
|
@@ -590,7 +663,7 @@ function validateLaneMetadata(value, label, workflowKey) {
|
|
|
590
663
|
if (typeof value.key !== "string" || !value.key.trim() || !runKeyPattern.test(value.key.trim())) throw new Error(label + ".key is invalid.");
|
|
591
664
|
if (workflowKey !== undefined && value.key.trim() !== workflowKey) throw new Error(label + ".key must match workflow key '" + workflowKey + "'.");
|
|
592
665
|
if (value.mode !== undefined && !["mutation", "review", "scout", "gate"].includes(value.mode)) throw new Error(label + ".mode is invalid.");
|
|
593
|
-
const bounded = (entry, maxBytes) => typeof entry === "string" && entry.trim() &&
|
|
666
|
+
const bounded = (entry, maxBytes) => typeof entry === "string" && entry.trim() && utf8ByteLength(entry.trim()) <= maxBytes && !/[\r\n\u0000]/.test(entry);
|
|
594
667
|
if (value.sourceRef !== undefined && !bounded(value.sourceRef, 128)) throw new Error(label + ".sourceRef is invalid.");
|
|
595
668
|
for (const [name, maxItems, maxLength] of [["claims", 20, 160], ["outputPaths", 10, 256]]) {
|
|
596
669
|
if (value[name] === undefined) continue;
|
|
@@ -682,11 +755,11 @@ function validateHostCommand(key, params) {
|
|
|
682
755
|
throw new Error("runs.host('" + key + "') params have unsupported fields: " + unknown.join(", ") + "." + cwdHint);
|
|
683
756
|
}
|
|
684
757
|
if (params.kind !== "command") throw new Error("runs.host('" + key + "') kind must be 'command'.");
|
|
685
|
-
if (typeof params.command !== "string" || !params.command.trim() || params.command.includes("\u0000") ||
|
|
758
|
+
if (typeof params.command !== "string" || !params.command.trim() || params.command.includes("\u0000") || utf8ByteLength(params.command.trim()) > 16384) throw new Error("runs.host('" + key + "') command must be a non-empty string of at most 16384 bytes without NUL.");
|
|
686
759
|
if (!Number.isInteger(params.timeoutMs) || params.timeoutMs < 1 || params.timeoutMs > 86400000) throw new Error("runs.host('" + key + "') timeoutMs must be an integer from 1 to 86400000.");
|
|
687
|
-
if (params.output !== undefined && (typeof params.output !== "string" || !params.output.trim() || /^[/\\]|(?:^|[/\\])\.\.(?:[/\\]|$)/.test(params.output) || /[\u0000-\u001f\u007f]/.test(params.output) ||
|
|
760
|
+
if (params.output !== undefined && (typeof params.output !== "string" || !params.output.trim() || /^[/\\]|(?:^|[/\\])\.\.(?:[/\\]|$)/.test(params.output) || /[\u0000-\u001f\u007f]/.test(params.output) || utf8ByteLength(params.output.trim()) > 240)) throw new Error("runs.host('" + key + "') output must be a bounded relative path without traversal or control characters.");
|
|
688
761
|
if (params.role !== undefined && params.role !== "ci" && params.role !== "gate") throw new Error("runs.host('" + key + "') role must be 'ci' or 'gate'.");
|
|
689
|
-
if (params.provider !== undefined && (typeof params.provider !== "string" || !params.provider.trim() || /[\r\n\u0000]/.test(params.provider) ||
|
|
762
|
+
if (params.provider !== undefined && (typeof params.provider !== "string" || !params.provider.trim() || /[\r\n\u0000]/.test(params.provider) || utf8ByteLength(params.provider.trim()) > 64)) throw new Error("runs.host('" + key + "') provider must be a non-empty single-line string of at most 64 bytes.");
|
|
690
763
|
assertJsonValue(params, "runs.host('" + key + "') params");
|
|
691
764
|
if (hostKeys.has(key) || runFingerprints.has(key)) throw new Error("Workflow key '" + key + "' is already in use.");
|
|
692
765
|
if (hostKeys.size >= 32) throw new Error("workflowScript supports at most 32 runs.host calls.");
|
|
@@ -758,6 +831,7 @@ function launchRunsAllPermissive(items) {
|
|
|
758
831
|
|
|
759
832
|
const runs = Object.freeze({
|
|
760
833
|
run(key, params) {
|
|
834
|
+
if (key && typeof key === "object" && !Array.isArray(key)) throw new Error('runs.run expects (key, params); got an object as the first argument. Use runs.run("plan", { agent, task }) or runs.all([{ key, agent, task }]).');
|
|
761
835
|
validateRunCall(key, params, "runs.run", runFingerprints);
|
|
762
836
|
const launched = runHostCall(key, params, false);
|
|
763
837
|
return trackRunObservation([{ key, operation: "run", callId: launched.callId }], launched.promise.then(decorateWorkflowChildResult));
|
|
@@ -806,14 +880,221 @@ const state = Object.freeze({
|
|
|
806
880
|
},
|
|
807
881
|
});
|
|
808
882
|
|
|
809
|
-
|
|
883
|
+
const contextObjectPrototype = scriptRealm.objectPrototype;
|
|
810
884
|
|
|
811
885
|
const capturedConsole = Object.freeze(Object.fromEntries(
|
|
812
886
|
["log", "info", "warn", "error"].map((level) => [level, (...args) => {
|
|
813
|
-
|
|
887
|
+
post({ type: "console", level, text: args.map((value) => typeof value === "string" ? value : inspectValue(value)).join(" ") });
|
|
814
888
|
}]),
|
|
815
889
|
));
|
|
816
890
|
|
|
891
|
+
function postWorkflowError(error) {
|
|
892
|
+
// Pre-run syntax and portability errors are tagged "validation" where the script is compiled;
|
|
893
|
+
// anything reaching here was thrown while the script ran, including a runtime SyntaxError.
|
|
894
|
+
const taggedKind = error && (typeof error === "object" || typeof error === "function") ? workflowErrorKinds.get(error) : undefined;
|
|
895
|
+
post({ type: "error", error: isSyntaxError(error) ? formatWorkflowScriptSyntaxError(error) : formatWorkflowScriptError(error), errorKind: taggedKind ?? "script" });
|
|
896
|
+
}
|
|
897
|
+
function assertJsonValue(value, path = "emit", seen = new Set()) {
|
|
898
|
+
|
|
899
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
900
|
+
if (typeof value === "number") {
|
|
901
|
+
if (!Number.isFinite(value)) throw new Error(path + " must contain only finite JSON numbers.");
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (typeof value !== "object") throw new Error(path + " must be a JSON value; received " + typeof value + ".");
|
|
905
|
+
if (seen.has(value)) throw new Error(path + " must not contain cycles.");
|
|
906
|
+
seen.add(value);
|
|
907
|
+
if (Array.isArray(value)) {
|
|
908
|
+
for (let index = 0; index < value.length; index++) {
|
|
909
|
+
if (!Object.prototype.hasOwnProperty.call(value, index)) throw new Error(path + " must not contain sparse array entries.");
|
|
910
|
+
assertJsonValue(value[index], path + "[" + index + "]", seen);
|
|
911
|
+
}
|
|
912
|
+
} else {
|
|
913
|
+
const prototype = Object.getPrototypeOf(value);
|
|
914
|
+
if (prototype !== null && prototype !== Object.prototype && prototype !== contextObjectPrototype) throw new Error(path + " must contain only plain JSON objects.");
|
|
915
|
+
if (Object.getOwnPropertySymbols(value).length > 0) throw new Error(path + " must not contain symbol keys.");
|
|
916
|
+
for (const [key, entry] of Object.entries(value)) assertJsonValue(entry, path + "." + key, seen);
|
|
917
|
+
}
|
|
918
|
+
seen.delete(value);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function isPlainWorkflowObject(value) {
|
|
922
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
923
|
+
const prototype = Object.getPrototypeOf(value);
|
|
924
|
+
return prototype === null || prototype === Object.prototype || prototype === contextObjectPrototype;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function unwrapRunsAllResults(value, seen = new Map()) {
|
|
928
|
+
if (value === null || typeof value !== "object") return value;
|
|
929
|
+
const runsAllTarget = runsAllResultTargets.get(value);
|
|
930
|
+
const target = runsAllTarget || value;
|
|
931
|
+
if (seen.has(target)) return seen.get(target);
|
|
932
|
+
if (Array.isArray(target)) {
|
|
933
|
+
const copy = [];
|
|
934
|
+
seen.set(target, copy);
|
|
935
|
+
let changed = !!runsAllTarget;
|
|
936
|
+
for (let index = 0; index < target.length; index++) {
|
|
937
|
+
copy[index] = unwrapRunsAllResults(target[index], seen);
|
|
938
|
+
changed ||= copy[index] !== target[index];
|
|
939
|
+
}
|
|
940
|
+
return changed ? copy : target;
|
|
941
|
+
}
|
|
942
|
+
if (!isPlainWorkflowObject(target) || Object.getOwnPropertySymbols(target).length > 0) return target;
|
|
943
|
+
let changed = false;
|
|
944
|
+
const entries = Object.entries(target).map(([key, entry]) => {
|
|
945
|
+
const unwrapped = unwrapRunsAllResults(entry, seen);
|
|
946
|
+
changed ||= unwrapped !== entry;
|
|
947
|
+
return [key, unwrapped];
|
|
948
|
+
});
|
|
949
|
+
return changed ? Object.fromEntries(entries) : target;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function omitUndefinedWorkflowValues(value, seen = new Set()) {
|
|
953
|
+
if (value === null || typeof value !== "object") return value;
|
|
954
|
+
if (seen.has(value)) return value;
|
|
955
|
+
seen.add(value);
|
|
956
|
+
const normalized = Array.isArray(value)
|
|
957
|
+
? value.map((entry) => entry === undefined ? null : omitUndefinedWorkflowValues(entry, seen))
|
|
958
|
+
: isPlainWorkflowObject(value) && Object.getOwnPropertySymbols(value).length === 0
|
|
959
|
+
? Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => entry === undefined ? [] : [[key, omitUndefinedWorkflowValues(entry, seen)]]))
|
|
960
|
+
: value;
|
|
961
|
+
seen.delete(value);
|
|
962
|
+
return normalized;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const scriptGlobals = { runs, Promise: workflowPromise, emit(value) { const unwrapped = unwrapRunsAllResults(value); const emittedValue = unwrapped === undefined ? null : omitUndefinedWorkflowValues(unwrapped); assertJsonValue(emittedValue); post({ type: "emit", value: emittedValue }); }, console: capturedConsole };
|
|
966
|
+
if (stateEnabled) scriptGlobals.state = state;
|
|
967
|
+
|
|
968
|
+
function deliver(callId, ok, encodedValue, errorText, errorKind) {
|
|
969
|
+
const entry = pending.get(callId);
|
|
970
|
+
if (!entry) return;
|
|
971
|
+
pending.delete(callId);
|
|
972
|
+
if (ok) entry.resolve(JSON.parse(encodedValue).value);
|
|
973
|
+
else {
|
|
974
|
+
const error = new Error(errorText);
|
|
975
|
+
if (errorKind === "child" || errorKind === "detached-child") workflowErrorKinds.set(error, errorKind);
|
|
976
|
+
entry.reject(error);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
let nativeThenDescriptor;
|
|
981
|
+
|
|
982
|
+
function beginWorkflowScript() {
|
|
983
|
+
try {
|
|
984
|
+
nativeThenDescriptor = Object.getOwnPropertyDescriptor(nativePromisePrototype, "then");
|
|
985
|
+
if (!nativeThenDescriptor || typeof nativeThenDescriptor.value !== "function") throw new Error("workflowScript could not inspect the VM Promise.prototype.then method.");
|
|
986
|
+
const nativeThen = nativeThenDescriptor.value;
|
|
987
|
+
// Some hosts freeze built-ins in every realm, which makes then read-only. The promise hooks
|
|
988
|
+
// still track observation, so skip the wrapper rather than fail every workflow.
|
|
989
|
+
if (nativeThenDescriptor.configurable) Object.defineProperty(nativePromisePrototype, "then", {
|
|
990
|
+
...nativeThenDescriptor,
|
|
991
|
+
value: function workflowPromiseThen(...args) {
|
|
992
|
+
if (isDirectWorkflowScriptPromiseHandlerCall() || suppressNativePromiseConsumption > 0) {
|
|
993
|
+
return withSuppressedNativePromiseConsumption(() => Reflect.apply(nativeThen, this, args));
|
|
994
|
+
}
|
|
995
|
+
return Reflect.apply(nativeThen, this, args);
|
|
996
|
+
},
|
|
997
|
+
});
|
|
998
|
+
return true;
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
postWorkflowError(error);
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function hookBefore(promise) {
|
|
1006
|
+
if (!isWorkflowRealmPromise(promise)) return;
|
|
1007
|
+
activeNativePromises.push(promise);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function hookAfter(promise) {
|
|
1011
|
+
// Compare by identity rather than lastIndexOf: this may be a worker promise, and a script can replace Array methods.
|
|
1012
|
+
for (let index = activeNativePromises.length - 1; index >= 0; index--) {
|
|
1013
|
+
if (activeNativePromises[index] !== promise) continue;
|
|
1014
|
+
activeNativePromises.splice(index, 1);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function hookInit(promise, parent) {
|
|
1020
|
+
if (!isWorkflowRealmPromise(promise)) return;
|
|
1021
|
+
const childTracker = nativePromiseTracker(promise);
|
|
1022
|
+
if (!parent || !isWorkflowRealmPromise(parent)) return;
|
|
1023
|
+
nativePromiseParents.set(promise, parent);
|
|
1024
|
+
const parentTracker = nativePromiseTracker(parent);
|
|
1025
|
+
addTrackerDependency(childTracker, parentTracker);
|
|
1026
|
+
const activePromise = activeNativePromises.at(-1);
|
|
1027
|
+
if (activePromise && activePromise !== parent) {
|
|
1028
|
+
addTrackerDependency(nativePromiseTracker(activePromise), parentTracker);
|
|
1029
|
+
} else if (!activePromise && suppressNativePromiseConsumption === 0) {
|
|
1030
|
+
markTrackedObservationsConsumed(parentTracker);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function endWorkflowScript() {
|
|
1035
|
+
try {
|
|
1036
|
+
let stopped = false;
|
|
1037
|
+
try {
|
|
1038
|
+
stopped = bridge.stopPromiseHooks();
|
|
1039
|
+
} catch {}
|
|
1040
|
+
if (stopped !== true) throw new Error("Workflow runtime could not stop its promise hooks.");
|
|
1041
|
+
} finally {
|
|
1042
|
+
try {
|
|
1043
|
+
if (nativeThenDescriptor.configurable) Object.defineProperty(nativePromisePrototype, "then", nativeThenDescriptor);
|
|
1044
|
+
} finally {
|
|
1045
|
+
topLevelWorkflowPromise = undefined;
|
|
1046
|
+
activeNativePromises.length = 0;
|
|
1047
|
+
suppressNativePromiseConsumption = 0;
|
|
1048
|
+
nativePromiseTrackers = new WeakMap();
|
|
1049
|
+
nativePromiseParents = new WeakMap();
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function completeWorkflowScript(value) {
|
|
1055
|
+
const persistedValue = value === undefined ? null : omitUndefinedWorkflowValues(value);
|
|
1056
|
+
try {
|
|
1057
|
+
assertJsonValue(persistedValue, "return");
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
post({ type: "error", errorKind: "return-serialization", errorPhase: "return-serialization", error: formatWorkflowScriptError(error) });
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
post({ type: "complete", value: persistedValue });
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// The worker ran the compiled script: `result` is its completion value, or what it threw synchronously.
|
|
1066
|
+
function awaitWorkflowScript(threw, result) {
|
|
1067
|
+
let settled;
|
|
1068
|
+
try {
|
|
1069
|
+
if (threw) throw result;
|
|
1070
|
+
topLevelWorkflowPromise = result;
|
|
1071
|
+
markTrackedObservationsConsumed(nativePromiseTracker(result));
|
|
1072
|
+
settled = Promise.resolve(result);
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
settled = Promise.reject(error);
|
|
1075
|
+
}
|
|
1076
|
+
settled.then((value) => {
|
|
1077
|
+
endWorkflowScript();
|
|
1078
|
+
completeWorkflowScript(value);
|
|
1079
|
+
}, (error) => {
|
|
1080
|
+
endWorkflowScript();
|
|
1081
|
+
postWorkflowError(error);
|
|
1082
|
+
}).catch(postWorkflowError);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return Object.freeze({
|
|
1086
|
+
__proto__: null,
|
|
1087
|
+
scriptGlobals: Object.freeze(scriptGlobals),
|
|
1088
|
+
deliver,
|
|
1089
|
+
beginWorkflowScript,
|
|
1090
|
+
hookBefore,
|
|
1091
|
+
hookAfter,
|
|
1092
|
+
hookInit,
|
|
1093
|
+
awaitWorkflowScript,
|
|
1094
|
+
reportError: postWorkflowError,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
|
|
817
1098
|
function formatWorkflowScriptSyntaxError(error) {
|
|
818
1099
|
const details = formatWorkflowScriptError(error);
|
|
819
1100
|
return [
|
|
@@ -836,17 +1117,11 @@ function isSyntaxError(error) {
|
|
|
836
1117
|
return error instanceof SyntaxError || error?.name === "SyntaxError";
|
|
837
1118
|
}
|
|
838
1119
|
|
|
839
|
-
function postWorkflowError(error) {
|
|
840
|
-
// Pre-run syntax and portability errors are tagged "validation" where the script is compiled;
|
|
841
|
-
// anything reaching here was thrown while the script ran, including a runtime SyntaxError.
|
|
842
|
-
const taggedKind = error && (typeof error === "object" || typeof error === "function") ? workflowErrorKinds.get(error) : undefined;
|
|
843
|
-
parentPort.postMessage({ type: "error", error: isSyntaxError(error) ? formatWorkflowScriptSyntaxError(error) : formatWorkflowScriptError(error), errorKind: taggedKind ?? "script" });
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
process.on("unhandledRejection", postWorkflowError);
|
|
847
|
-
|
|
848
1120
|
const NESTED_ASYNC_WORKFLOW_ERROR = "workflowScript validation failed before child launch; no children launched. 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. Parallel plus sequential rewrite: const a = runs.run(\"a\", { agent: \"worker\", task: \"A\" }); const writer = await runs.run(\"writer\", { agent: \"worker\", task: \"Write\" }); const review = await runs.run(\"review\", { agent: \"reviewer\", task: writer.output }); const [aResult] = await Promise.all([a]); return { a: aResult.output, issue: { writerRunId: writer.runId, reviewRunId: review.runId } };";
|
|
849
1121
|
const AST_SCALAR_KEYS = new Set(["type", "start", "end"]);
|
|
1122
|
+
// A script context has no module loader, and Node and Bun reject import() there with an error built in
|
|
1123
|
+
// the worker realm, so the call is refused before the script runs.
|
|
1124
|
+
const DYNAMIC_IMPORT_WORKFLOW_ERROR = "workflowScript validation failed before child launch; no children launched. workflowScript cannot load modules with import().";
|
|
850
1125
|
|
|
851
1126
|
function assertPortableWorkflowScript(source) {
|
|
852
1127
|
const wrapped = "(async () => {\n" + source + "\n})()";
|
|
@@ -863,7 +1138,8 @@ function assertPortableWorkflowScript(source) {
|
|
|
863
1138
|
throw error;
|
|
864
1139
|
}
|
|
865
1140
|
const wrapper = workflowWrapperFunction(ast);
|
|
866
|
-
|
|
1141
|
+
// Walk the whole program, not just the wrapper body: a script can close the wrapper early.
|
|
1142
|
+
walkWorkflowAst(ast, wrapper);
|
|
867
1143
|
}
|
|
868
1144
|
|
|
869
1145
|
function workflowWrapperFunction(ast) {
|
|
@@ -885,183 +1161,182 @@ function walkWorkflowAst(node, allowedAsyncFunction) {
|
|
|
885
1161
|
if (node !== allowedAsyncFunction && isAsyncFunctionNode(node)) {
|
|
886
1162
|
throw new Error(NESTED_ASYNC_WORKFLOW_ERROR);
|
|
887
1163
|
}
|
|
1164
|
+
if (node.type === "ImportExpression") throw new Error(DYNAMIC_IMPORT_WORKFLOW_ERROR);
|
|
888
1165
|
for (const [key, child] of Object.entries(node)) {
|
|
889
1166
|
if (AST_SCALAR_KEYS.has(key)) continue;
|
|
890
1167
|
walkWorkflowAst(child, allowedAsyncFunction);
|
|
891
1168
|
}
|
|
892
1169
|
}
|
|
893
1170
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1171
|
+
// The error-format helpers run in both realms; the runtime realm compiles its own copies from their source.
|
|
1172
|
+
const WORKFLOW_RUNTIME_SOURCE = [
|
|
1173
|
+
"(function (bridge, scriptRealm, stateEnabled) {",
|
|
1174
|
+
"\"use strict\";",
|
|
1175
|
+
formatWorkflowScriptSyntaxError,
|
|
1176
|
+
formatWorkflowScriptError,
|
|
1177
|
+
isSyntaxError,
|
|
1178
|
+
"return (" + workflowRuntime + ")(bridge, scriptRealm, stateEnabled);",
|
|
1179
|
+
"})",
|
|
1180
|
+
].join("\n");
|
|
1181
|
+
|
|
1182
|
+
const VM_CONTEXT_OPTIONS = { codeGeneration: { strings: false, wasm: false } };
|
|
1183
|
+
const LOCK_PREPARE_STACK_TRACE_SOURCE = "Object.defineProperty(Error, \"prepareStackTrace\", { value: undefined, writable: false, enumerable: false, configurable: false });";
|
|
1184
|
+
|
|
1185
|
+
// A null-prototype sandbox keeps globalThis.constructor out of the worker realm. The sandbox itself is
|
|
1186
|
+
// still a worker-realm object, and Bun forwards a failed global redefinition to it as the worker's
|
|
1187
|
+
// TypeError; lockWorkerRealm makes that error useless. vm.constants.DONT_CONTEXTIFY would avoid the
|
|
1188
|
+
// sandbox, but Bun 1.3 ignores properties defined on such a global, so the script API could not be installed.
|
|
1189
|
+
function createVmRealm() {
|
|
1190
|
+
return vm.createContext(Object.create(null), VM_CONTEXT_OPTIONS);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Runs once the worker has compiled everything it needs, right before the script starts. A worker-realm
|
|
1194
|
+
// value can still reach a script through engine paths (see workflowRuntime), so take away what such a
|
|
1195
|
+
// value would be used for: no worker function's constructor compiles code, and no script can install a
|
|
1196
|
+
// stack formatter that hands it worker frames.
|
|
1197
|
+
function lockWorkerRealm() {
|
|
1198
|
+
const refuse = function Function() {
|
|
1199
|
+
throw new TypeError("Code generation is disabled in the workflow worker.");
|
|
1200
|
+
};
|
|
1201
|
+
const functionPrototypes = [
|
|
1202
|
+
Function.prototype,
|
|
1203
|
+
Object.getPrototypeOf(async function () {}),
|
|
1204
|
+
Object.getPrototypeOf(function* () {}),
|
|
1205
|
+
Object.getPrototypeOf(async function* () {}),
|
|
1206
|
+
];
|
|
1207
|
+
// A host that froze these already chose their values; redefining them would throw and fail every workflow.
|
|
1208
|
+
const lock = (target, key, value) => {
|
|
1209
|
+
if (Object.getOwnPropertyDescriptor(target, key)?.configurable === false) return;
|
|
1210
|
+
Object.defineProperty(target, key, { value, writable: false, enumerable: false, configurable: false });
|
|
1211
|
+
};
|
|
1212
|
+
for (const prototype of functionPrototypes) lock(prototype, "constructor", refuse);
|
|
1213
|
+
lock(Error, "prepareStackTrace", Error.prepareStackTrace);
|
|
915
1214
|
}
|
|
1215
|
+
const INSPECT_OPTIONS = { depth: 4, breakLength: 120, customInspect: false };
|
|
916
1216
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const prototype = Object.getPrototypeOf(value);
|
|
920
|
-
return prototype === null || prototype === Object.prototype || prototype === contextObjectPrototype;
|
|
921
|
-
}
|
|
1217
|
+
let runtime;
|
|
1218
|
+
let stopWorkflowPromiseHook;
|
|
922
1219
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
changed ||= copy[index] !== target[index];
|
|
1220
|
+
// Each method takes and returns only strings, numbers and booleans, and catches its own failures.
|
|
1221
|
+
// inspect is the one exception on input: it formats a script value without calling script-defined
|
|
1222
|
+
// custom inspectors, which would receive worker-realm arguments.
|
|
1223
|
+
const bridge = Object.freeze({
|
|
1224
|
+
__proto__: null,
|
|
1225
|
+
post(json) {
|
|
1226
|
+
try {
|
|
1227
|
+
parentPort.postMessage(JSON.parse(json));
|
|
1228
|
+
return true;
|
|
1229
|
+
} catch {
|
|
1230
|
+
return false;
|
|
935
1231
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
|
|
1232
|
+
},
|
|
1233
|
+
inspect(value) {
|
|
1234
|
+
try {
|
|
1235
|
+
return JSON.stringify([true, inspect(value, INSPECT_OPTIONS)]);
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
const message = error && typeof error.message === "string" ? error.message : "console could not format a value.";
|
|
1238
|
+
return JSON.stringify([false, message]);
|
|
1239
|
+
}
|
|
1240
|
+
},
|
|
1241
|
+
utf8Length(value) {
|
|
1242
|
+
try {
|
|
1243
|
+
return typeof value === "string" ? new TextEncoder().encode(value).byteLength : -1;
|
|
1244
|
+
} catch {
|
|
1245
|
+
return -1;
|
|
1246
|
+
}
|
|
1247
|
+
},
|
|
1248
|
+
utf8Prefix(value, maxBytes) {
|
|
1249
|
+
try {
|
|
1250
|
+
if (typeof value !== "string" || typeof maxBytes !== "number") return undefined;
|
|
1251
|
+
return new TextDecoder().decode(new TextEncoder().encode(value).subarray(0, maxBytes), { stream: true });
|
|
1252
|
+
} catch {
|
|
1253
|
+
return undefined;
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
stopPromiseHooks() {
|
|
1257
|
+
const stop = stopWorkflowPromiseHook;
|
|
1258
|
+
stopWorkflowPromiseHook = undefined;
|
|
1259
|
+
try {
|
|
1260
|
+
stop?.();
|
|
1261
|
+
return true;
|
|
1262
|
+
} catch {
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
1266
|
+
});
|
|
947
1267
|
|
|
948
|
-
function
|
|
949
|
-
|
|
950
|
-
if (seen.has(value)) return value;
|
|
951
|
-
seen.add(value);
|
|
952
|
-
const normalized = Array.isArray(value)
|
|
953
|
-
? value.map((entry) => entry === undefined ? null : omitUndefinedWorkflowValues(entry, seen))
|
|
954
|
-
: isPlainWorkflowObject(value) && Object.getOwnPropertySymbols(value).length === 0
|
|
955
|
-
? Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => entry === undefined ? [] : [[key, omitUndefinedWorkflowValues(entry, seen)]]))
|
|
956
|
-
: value;
|
|
957
|
-
seen.delete(value);
|
|
958
|
-
return normalized;
|
|
1268
|
+
function postWorkerError(error) {
|
|
1269
|
+
parentPort.postMessage({ type: "error", error: isSyntaxError(error) ? formatWorkflowScriptSyntaxError(error) : formatWorkflowScriptError(error), errorKind: "script" });
|
|
959
1270
|
}
|
|
960
1271
|
|
|
1272
|
+
process.on("unhandledRejection", (reason) => {
|
|
1273
|
+
// A worker-realm reason is the worker's own failure; it must not enter the runtime realm.
|
|
1274
|
+
if (runtime && !(reason instanceof Object)) runtime.reportError(reason);
|
|
1275
|
+
else postWorkerError(reason);
|
|
1276
|
+
});
|
|
1277
|
+
|
|
961
1278
|
function deepFreezeWorkflowArgs(value) {
|
|
962
1279
|
if (value === null || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
963
1280
|
for (const entry of Object.values(value)) deepFreezeWorkflowArgs(entry);
|
|
964
1281
|
return Object.freeze(value);
|
|
965
1282
|
}
|
|
966
1283
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1284
|
+
function startWorkflow(message) {
|
|
1285
|
+
const context = createVmRealm();
|
|
1286
|
+
const runtimeContext = createVmRealm();
|
|
1287
|
+
// Bun hands Error.prepareStackTrace call sites (and the array holding them) from the worker's realm,
|
|
1288
|
+
// whatever context the error came from. Lock it in both realms before any code runs there.
|
|
1289
|
+
for (const realm of [context, runtimeContext]) vm.runInContext(LOCK_PREPARE_STACK_TRACE_SOURCE, realm);
|
|
1290
|
+
const scriptRealm = vm.runInContext("({ __proto__: null, objectPrototype: Object.prototype, promisePrototype: (async () => {})().constructor.prototype })", context);
|
|
1291
|
+
const createRuntime = new vm.Script(WORKFLOW_RUNTIME_SOURCE, { filename: "pi-workflow-runtime.js" }).runInContext(runtimeContext);
|
|
1292
|
+
runtime = createRuntime(bridge, scriptRealm, Boolean(message.stateEnabled));
|
|
1293
|
+
Object.assign(context, runtime.scriptGlobals);
|
|
1294
|
+
// Rebuild args inside the VM realm: a worker-realm object would expose the worker's unrestricted
|
|
1295
|
+
// Function through args.constructor.constructor, bypassing codeGeneration.strings: false.
|
|
1296
|
+
context.args = deepFreezeWorkflowArgs(vm.runInContext("JSON.parse", context)(JSON.stringify(message.args ?? {})));
|
|
1297
|
+
let compiled;
|
|
1298
|
+
try {
|
|
1299
|
+
assertPortableWorkflowScript(message.script);
|
|
1300
|
+
compiled = new vm.Script("(async () => {\n" + message.script + "\n})()", { filename: "workflow-script.js", lineOffset: -1 });
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
parentPort.postMessage({ type: "error", errorKind: "validation", error: isSyntaxError(error) ? formatWorkflowScriptSyntaxError(error) : formatWorkflowScriptError(error) });
|
|
978
1303
|
return;
|
|
979
1304
|
}
|
|
980
|
-
if (
|
|
1305
|
+
if (!runtime.beginWorkflowScript()) return;
|
|
1306
|
+
stopWorkflowPromiseHook = createWorkflowPromiseHook({ init: runtime.hookInit, before: runtime.hookBefore, after: runtime.hookAfter });
|
|
1307
|
+
lockWorkerRealm();
|
|
1308
|
+
let result;
|
|
1309
|
+
let threw = false;
|
|
981
1310
|
try {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1311
|
+
result = compiled.runInContext(context);
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
threw = true;
|
|
1314
|
+
result = error;
|
|
1315
|
+
}
|
|
1316
|
+
runtime.awaitWorkflowScript(threw, result);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
parentPort.on("message", (message) => {
|
|
1320
|
+
if (message.type === "response") {
|
|
1321
|
+
if (!runtime || typeof message.callId !== "number") return;
|
|
1322
|
+
if (!message.ok) {
|
|
1323
|
+
runtime.deliver(message.callId, false, undefined, message.error === undefined ? undefined : String(message.error), typeof message.errorKind === "string" ? message.errorKind : undefined);
|
|
995
1324
|
return;
|
|
996
1325
|
}
|
|
997
|
-
|
|
998
|
-
const nativeThenDescriptor = Object.getOwnPropertyDescriptor(nativePromisePrototype, "then");
|
|
999
|
-
if (!nativeThenDescriptor || typeof nativeThenDescriptor.value !== "function") throw new Error("workflowScript could not inspect the VM Promise.prototype.then method.");
|
|
1000
|
-
const nativeThen = nativeThenDescriptor.value;
|
|
1001
|
-
let stopWorkflowPromiseHook;
|
|
1002
|
-
let value;
|
|
1003
|
-
try {
|
|
1004
|
-
// Some hosts freeze built-ins in every realm, which makes then read-only. The promise hooks
|
|
1005
|
-
// below still track observation, so skip the wrapper rather than fail every workflow.
|
|
1006
|
-
if (nativeThenDescriptor.configurable) Object.defineProperty(nativePromisePrototype, "then", {
|
|
1007
|
-
...nativeThenDescriptor,
|
|
1008
|
-
value: function workflowPromiseThen(...args) {
|
|
1009
|
-
if (isDirectWorkflowScriptPromiseHandlerCall() || suppressNativePromiseConsumption > 0) {
|
|
1010
|
-
return withSuppressedNativePromiseConsumption(() => Reflect.apply(nativeThen, this, args));
|
|
1011
|
-
}
|
|
1012
|
-
return Reflect.apply(nativeThen, this, args);
|
|
1013
|
-
},
|
|
1014
|
-
});
|
|
1015
|
-
stopWorkflowPromiseHook = createWorkflowPromiseHook({
|
|
1016
|
-
before(promise) {
|
|
1017
|
-
activeNativePromises.push(promise);
|
|
1018
|
-
},
|
|
1019
|
-
after(promise) {
|
|
1020
|
-
const index = activeNativePromises.lastIndexOf(promise);
|
|
1021
|
-
if (index !== -1) activeNativePromises.splice(index, 1);
|
|
1022
|
-
},
|
|
1023
|
-
init(promise, parent) {
|
|
1024
|
-
const childTracker = nativePromiseTracker(promise);
|
|
1025
|
-
if (!parent) return;
|
|
1026
|
-
nativePromiseParents.set(promise, parent);
|
|
1027
|
-
const parentTracker = nativePromiseTracker(parent);
|
|
1028
|
-
addTrackerDependency(childTracker, parentTracker);
|
|
1029
|
-
const activePromise = activeNativePromises.at(-1);
|
|
1030
|
-
if (activePromise && activePromise !== parent) {
|
|
1031
|
-
addTrackerDependency(nativePromiseTracker(activePromise), parentTracker);
|
|
1032
|
-
} else if (!activePromise && suppressNativePromiseConsumption === 0) {
|
|
1033
|
-
markTrackedObservationsConsumed(parentTracker);
|
|
1034
|
-
}
|
|
1035
|
-
},
|
|
1036
|
-
});
|
|
1037
|
-
const workflowResultPromise = compiled.runInContext(context);
|
|
1038
|
-
topLevelWorkflowPromise = workflowResultPromise;
|
|
1039
|
-
markTrackedObservationsConsumed(nativePromiseTracker(workflowResultPromise));
|
|
1040
|
-
value = await workflowResultPromise;
|
|
1041
|
-
} finally {
|
|
1042
|
-
try {
|
|
1043
|
-
stopWorkflowPromiseHook?.();
|
|
1044
|
-
} finally {
|
|
1045
|
-
try {
|
|
1046
|
-
if (nativeThenDescriptor.configurable) Object.defineProperty(nativePromisePrototype, "then", nativeThenDescriptor);
|
|
1047
|
-
} finally {
|
|
1048
|
-
topLevelWorkflowPromise = undefined;
|
|
1049
|
-
activeNativePromises.length = 0;
|
|
1050
|
-
suppressNativePromiseConsumption = 0;
|
|
1051
|
-
nativePromiseTrackers = new WeakMap();
|
|
1052
|
-
nativePromiseParents = new WeakMap();
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
const persistedValue = value === undefined ? null : omitUndefinedWorkflowValues(value);
|
|
1326
|
+
let encodedValue;
|
|
1057
1327
|
try {
|
|
1058
|
-
|
|
1328
|
+
encodedValue = JSON.stringify({ value: message.value });
|
|
1059
1329
|
} catch (error) {
|
|
1060
|
-
|
|
1330
|
+
runtime.deliver(message.callId, false, undefined, "Workflow host response is not JSON: " + error.message, undefined);
|
|
1061
1331
|
return;
|
|
1062
1332
|
}
|
|
1063
|
-
|
|
1333
|
+
runtime.deliver(message.callId, true, encodedValue, undefined, undefined);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
if (message.type !== "start") return;
|
|
1337
|
+
try {
|
|
1338
|
+
startWorkflow(message);
|
|
1064
1339
|
} catch (error) {
|
|
1065
|
-
|
|
1340
|
+
postWorkerError(error);
|
|
1066
1341
|
}
|
|
1067
1342
|
});
|