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.
Files changed (73) hide show
  1. package/CHANGELOG.md +39 -2
  2. package/bundle/agents.js +4 -4
  3. package/bundle/capability-ceiling.js +1 -1
  4. package/bundle/child-tool-plan.js +3 -3
  5. package/bundle/{chunk-HJJAVHFB.js → chunk-235AWJUQ.js} +35 -32
  6. package/bundle/{chunk-P2NCCTZP.js → chunk-2D52JFLF.js} +1 -1
  7. package/bundle/{chunk-WG2OTTAK.js → chunk-2DHZTCOL.js} +2 -2
  8. package/bundle/{chunk-MBDOYZ2V.js → chunk-2S4K53MV.js} +1 -1
  9. package/bundle/{chunk-527E3D2J.js → chunk-4E7BKDTX.js} +5 -5
  10. package/bundle/{chunk-CZTMUBUI.js → chunk-7BWGNDNK.js} +4 -4
  11. package/bundle/{chunk-HXVKGS33.js → chunk-AKOIVLA5.js} +4 -4
  12. package/bundle/{chunk-MHX22USZ.js → chunk-C5P5CHYK.js} +1 -1
  13. package/bundle/{chunk-5ESXK62X.js → chunk-CGN2TVE7.js} +417 -389
  14. package/bundle/{chunk-VTMNBPCQ.js → chunk-D3JAFGMO.js} +1 -1
  15. package/bundle/{chunk-CDI3GMNV.js → chunk-DZDETGRJ.js} +18 -15
  16. package/bundle/{chunk-BKFWQ642.js → chunk-F4ASL6OV.js} +1 -1
  17. package/bundle/{chunk-JHZ6DZ5M.js → chunk-G2Z6ZH26.js} +1 -1
  18. package/bundle/{chunk-XXKG3P2W.js → chunk-J5YL7LYR.js} +4 -4
  19. package/bundle/{chunk-LDMXJIA6.js → chunk-JG4QKI5M.js} +58 -67
  20. package/bundle/{chunk-OOFW75PV.js → chunk-L3WFKBEI.js} +15 -11
  21. package/bundle/{chunk-Y3I6H6DV.js → chunk-LFDBD4PW.js} +2 -2
  22. package/bundle/{chunk-7F5SK2U3.js → chunk-LNRJCP2Y.js} +17 -14
  23. package/bundle/{chunk-4BW55BTL.js → chunk-NENDSQUY.js} +4 -4
  24. package/bundle/{chunk-6B4ITO2W.js → chunk-NKQ6JNAS.js} +1 -1
  25. package/bundle/{chunk-RZJN63GU.js → chunk-PALJGGYC.js} +2 -2
  26. package/bundle/{chunk-TJQJ3MEA.js → chunk-PV63UFUR.js} +10 -10
  27. package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
  28. package/bundle/{chunk-HLE22FSH.js → chunk-SSG7IEP5.js} +2 -2
  29. package/bundle/{chunk-CSSZRTKY.js → chunk-V2BZYLEP.js} +2 -2
  30. package/bundle/{chunk-CNLDYEH4.js → chunk-VQVVHSY6.js} +11 -11
  31. package/bundle/{chunk-CGXWSNPA.js → chunk-XDQORLZ5.js} +4 -4
  32. package/bundle/control-channel.js +3 -3
  33. package/bundle/index.js +1 -1
  34. package/bundle/intercom-bridge.js +3 -3
  35. package/bundle/preflight.js +9 -9
  36. package/bundle/project-panes.js +4 -4
  37. package/docs/configuration.md +12 -1
  38. package/docs/extension-api.md +2 -0
  39. package/docs/missions.md +2 -0
  40. package/docs/tool-reference.md +1 -1
  41. package/package.json +1 -1
  42. package/src/agents/agent-management.d.ts +1 -1
  43. package/src/agents/agent-management.js +2 -2
  44. package/src/agents/agents.js +1 -2
  45. package/src/agents/frontmatter.js +22 -18
  46. package/src/extension/index.js +2 -2
  47. package/src/missions/actions.js +7 -2
  48. package/src/missions/lifecycle.d.ts +11 -0
  49. package/src/missions/lifecycle.js +37 -6
  50. package/src/missions/store.js +1 -0
  51. package/src/missions/types.d.ts +2 -0
  52. package/src/runs/background/async-execution.d.ts +4 -1
  53. package/src/runs/background/async-execution.js +18 -3
  54. package/src/runs/background/async-resume.js +3 -2
  55. package/src/runs/background/async-retention.d.ts +1 -0
  56. package/src/runs/background/async-retention.js +16 -10
  57. package/src/runs/background/retained-children.js +1 -1
  58. package/src/runs/background/run-child-session.d.ts +2 -0
  59. package/src/runs/background/run-child-session.js +9 -1
  60. package/src/runs/background/subagent-runner.js +1 -0
  61. package/src/runs/foreground/subagent-executor.js +29 -51
  62. package/src/runs/shared/capability-ceiling.d.ts +2 -1
  63. package/src/runs/shared/capability-ceiling.js +3 -2
  64. package/src/runs/shared/child-session.d.ts +2 -0
  65. package/src/runs/shared/child-session.js +1 -0
  66. package/src/runs/shared/model-resolution-diagnostic.d.ts +11 -0
  67. package/src/runs/shared/model-resolution-diagnostic.js +25 -0
  68. package/src/runs/shared/parallel-handoff.d.ts +1 -1
  69. package/src/runs/shared/parallel-handoff.js +10 -6
  70. package/src/runs/shared/parallel-utils.d.ts +2 -0
  71. package/src/shared/types.d.ts +1 -1
  72. package/src/workflows/scripted-workflow.js +6 -1
  73. 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 === "@" || new TextEncoder().encode(ref).length > 1024 || ref.startsWith("/") || ref.endsWith("/") || ref.includes("//") || ref.includes("..") || ref.includes("@{")) return false;
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
- const stack = new Error().stack;
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
- parentPort.postMessage({ type: "callObserved", callId: observation.callId, key: observation.key, operation: observation.operation });
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
- parentPort.postMessage({ type: "call", callId, method, args });
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
- parentPort.postMessage({ type: "call", callId, method: "run", args: { key, params, ...(collectFailure ? { collectFailure: true } : {}), ...(batch ? { batch } : {}), ...(generatedLaneKey ? { generatedLaneKey } : {}) } });
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 new TextEncoder().encode(value).byteLength;
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
- const bytes = new TextEncoder().encode(value).subarray(0, MAX_LANE_BOARD_TEXT_BYTES - 3);
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
- parentPort.postMessage({ type: "lanePlan", lanes: lanes.map((lane) => ({
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 (new TextEncoder().encode(stableRunJson(value)).byteLength > 16384) throw new Error(label + " extensionBindings canonical JSON exceeds 16384 bytes.");
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() && new TextEncoder().encode(entry.trim()).byteLength <= maxBytes && !/[\r\n\u0000]/.test(entry);
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") || new TextEncoder().encode(params.command.trim()).byteLength > 16384) throw new Error("runs.host('" + key + "') command must be a non-empty string of at most 16384 bytes without NUL.");
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) || new TextEncoder().encode(params.output.trim()).byteLength > 240)) throw new Error("runs.host('" + key + "') output must be a bounded relative path without traversal or control characters.");
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) || new TextEncoder().encode(params.provider.trim()).byteLength > 64)) throw new Error("runs.host('" + key + "') provider must be a non-empty single-line string of at most 64 bytes.");
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
- let contextObjectPrototype;
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
- parentPort.postMessage({ type: "console", level, text: args.map((value) => typeof value === "string" ? value : inspect(value, { depth: 4, breakLength: 120 })).join(" ") });
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
- walkWorkflowAst(wrapper.body, wrapper);
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
- function assertJsonValue(value, path = "emit", seen = new Set()) {
895
- if (value === null || typeof value === "string" || typeof value === "boolean") return;
896
- if (typeof value === "number") {
897
- if (!Number.isFinite(value)) throw new Error(path + " must contain only finite JSON numbers.");
898
- return;
899
- }
900
- if (typeof value !== "object") throw new Error(path + " must be a JSON value; received " + typeof value + ".");
901
- if (seen.has(value)) throw new Error(path + " must not contain cycles.");
902
- seen.add(value);
903
- if (Array.isArray(value)) {
904
- for (let index = 0; index < value.length; index++) {
905
- if (!Object.prototype.hasOwnProperty.call(value, index)) throw new Error(path + " must not contain sparse array entries.");
906
- assertJsonValue(value[index], path + "[" + index + "]", seen);
907
- }
908
- } else {
909
- const prototype = Object.getPrototypeOf(value);
910
- if (prototype !== null && prototype !== Object.prototype && prototype !== contextObjectPrototype) throw new Error(path + " must contain only plain JSON objects.");
911
- if (Object.getOwnPropertySymbols(value).length > 0) throw new Error(path + " must not contain symbol keys.");
912
- for (const [key, entry] of Object.entries(value)) assertJsonValue(entry, path + "." + key, seen);
913
- }
914
- seen.delete(value);
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
- function isPlainWorkflowObject(value) {
918
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
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
- function unwrapRunsAllResults(value, seen = new Map()) {
924
- if (value === null || typeof value !== "object") return value;
925
- const runsAllTarget = runsAllResultTargets.get(value);
926
- const target = runsAllTarget || value;
927
- if (seen.has(target)) return seen.get(target);
928
- if (Array.isArray(target)) {
929
- const copy = [];
930
- seen.set(target, copy);
931
- let changed = !!runsAllTarget;
932
- for (let index = 0; index < target.length; index++) {
933
- copy[index] = unwrapRunsAllResults(target[index], seen);
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
- return changed ? copy : target;
937
- }
938
- if (!isPlainWorkflowObject(target) || Object.getOwnPropertySymbols(target).length > 0) return target;
939
- let changed = false;
940
- const entries = Object.entries(target).map(([key, entry]) => {
941
- const unwrapped = unwrapRunsAllResults(entry, seen);
942
- changed ||= unwrapped !== entry;
943
- return [key, unwrapped];
944
- });
945
- return changed ? Object.fromEntries(entries) : target;
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 omitUndefinedWorkflowValues(value, seen = new Set()) {
949
- if (value === null || typeof value !== "object") return value;
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
- parentPort.on("message", async (message) => {
968
- if (message.type === "response") {
969
- const entry = pending.get(message.callId);
970
- if (!entry) return;
971
- pending.delete(message.callId);
972
- if (message.ok) entry.resolve(message.value);
973
- else {
974
- const error = new Error(message.error);
975
- if (message.errorKind === "child" || message.errorKind === "detached-child") workflowErrorKinds.set(error, message.errorKind);
976
- entry.reject(error);
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 (message.type !== "start") return;
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
- const sandbox = { runs, Promise: workflowPromise, emit(value) { const unwrapped = unwrapRunsAllResults(value); const emittedValue = unwrapped === undefined ? null : omitUndefinedWorkflowValues(unwrapped); assertJsonValue(emittedValue); parentPort.postMessage({ type: "emit", value: emittedValue }); }, console: capturedConsole };
983
- if (message.stateEnabled) sandbox.state = state;
984
- const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
985
- contextObjectPrototype = vm.runInContext("Object.prototype", context);
986
- // Rebuild args inside the VM realm: a worker-realm object would expose the worker's unrestricted
987
- // Function through args.constructor.constructor, bypassing codeGeneration.strings: false.
988
- sandbox.args = deepFreezeWorkflowArgs(vm.runInContext("JSON.parse", context)(JSON.stringify(message.args ?? {})));
989
- let compiled;
990
- try {
991
- assertPortableWorkflowScript(message.script);
992
- compiled = new vm.Script("(async () => {\n" + message.script + "\n})()", { filename: "workflow-script.js", lineOffset: -1 });
993
- } catch (error) {
994
- parentPort.postMessage({ type: "error", errorKind: "validation", error: isSyntaxError(error) ? formatWorkflowScriptSyntaxError(error) : formatWorkflowScriptError(error) });
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
- const nativePromisePrototype = vm.runInContext("(async () => {})().constructor.prototype", context);
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
- assertJsonValue(persistedValue, "return");
1328
+ encodedValue = JSON.stringify({ value: message.value });
1059
1329
  } catch (error) {
1060
- parentPort.postMessage({ type: "error", errorKind: "return-serialization", errorPhase: "return-serialization", error: formatWorkflowScriptError(error) });
1330
+ runtime.deliver(message.callId, false, undefined, "Workflow host response is not JSON: " + error.message, undefined);
1061
1331
  return;
1062
1332
  }
1063
- parentPort.postMessage({ type: "complete", value: persistedValue });
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
- postWorkflowError(error);
1340
+ postWorkerError(error);
1066
1341
  }
1067
1342
  });