pi-subagents 0.49.0 → 0.50.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 (75) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/agents/oracle.md +7 -5
  3. package/agents/researcher.md +1 -1
  4. package/agents/reviewer.md +2 -2
  5. package/agents/scout.md +1 -1
  6. package/agents/worker.md +1 -1
  7. package/docs/agents.md +2 -0
  8. package/docs/configuration.md +47 -3
  9. package/docs/extension-api.md +36 -0
  10. package/docs/missions.md +1 -1
  11. package/docs/observability.md +16 -0
  12. package/docs/tool-reference.md +19 -2
  13. package/docs/workflows.md +2 -2
  14. package/package.json +1 -1
  15. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  16. package/skills/pi-subagents/references/execution-controls.md +5 -4
  17. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  18. package/skills/pi-subagents/references/prompting-and-roles.md +31 -15
  19. package/src/agents/agent-serializer.ts +2 -0
  20. package/src/agents/agents.ts +10 -0
  21. package/src/api/external-runs.ts +174 -84
  22. package/src/api/preflight.ts +12 -6
  23. package/src/extension/config.ts +33 -0
  24. package/src/extension/index.ts +48 -5
  25. package/src/extension/public-execution.ts +3 -1
  26. package/src/extension/rpc.ts +5 -1
  27. package/src/extension/schemas.ts +2 -1
  28. package/src/extension/tool-description.ts +4 -2
  29. package/src/inspectors/herdr/actions.ts +3 -7
  30. package/src/inspectors/herdr/project-panes.ts +2 -6
  31. package/src/inspectors/herdr/shell-command.ts +16 -0
  32. package/src/intercom/intercom-bridge.ts +2 -3
  33. package/src/intercom/native-supervisor-channel.ts +19 -42
  34. package/src/missions/goal-driver.ts +3 -1
  35. package/src/runs/background/active-run-index.ts +71 -1
  36. package/src/runs/background/async-execution.ts +49 -7
  37. package/src/runs/background/async-job-tracker.ts +1 -0
  38. package/src/runs/background/async-resume.ts +3 -5
  39. package/src/runs/background/async-status-snapshot.ts +277 -0
  40. package/src/runs/background/async-status.ts +8 -3
  41. package/src/runs/background/chain-root-attachment.ts +2 -2
  42. package/src/runs/background/completion-replay.ts +11 -1
  43. package/src/runs/background/fleet-view.ts +3 -1
  44. package/src/runs/background/result-files.ts +437 -0
  45. package/src/runs/background/result-watcher.ts +188 -41
  46. package/src/runs/background/retained-children.ts +69 -20
  47. package/src/runs/background/run-id-resolver.ts +30 -24
  48. package/src/runs/background/run-status.ts +7 -2
  49. package/src/runs/background/scheduled-runs.ts +54 -28
  50. package/src/runs/background/stale-run-reconciler.ts +27 -13
  51. package/src/runs/background/subagent-runner.ts +294 -32
  52. package/src/runs/background/subagent-wait.ts +2 -0
  53. package/src/runs/background/wait-completions.ts +5 -2
  54. package/src/runs/foreground/async-dismiss-action.ts +2 -1
  55. package/src/runs/foreground/chain-execution.ts +16 -0
  56. package/src/runs/foreground/execution.ts +219 -15
  57. package/src/runs/foreground/subagent-executor.ts +114 -28
  58. package/src/runs/shared/completion-guard.ts +96 -6
  59. package/src/runs/shared/external-cli-runner.ts +4 -0
  60. package/src/runs/shared/model-fallback.ts +16 -2
  61. package/src/runs/shared/nested-events.ts +66 -62
  62. package/src/runs/shared/orca-progress-tabs.ts +375 -0
  63. package/src/runs/shared/parallel-utils.ts +2 -0
  64. package/src/runs/shared/subagent-control.ts +15 -0
  65. package/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  66. package/src/runs/shared/subagent-startup-retry.ts +12 -0
  67. package/src/runs/shared/tool-timeout.ts +93 -0
  68. package/src/shared/types.ts +29 -2
  69. package/src/slash/slash-commands.ts +34 -25
  70. package/src/slash/slash-live-state.ts +3 -0
  71. package/src/tui/fleet-status.ts +160 -45
  72. package/src/tui/fleet.ts +125 -15
  73. package/src/tui/render.ts +41 -10
  74. package/src/workflows/chat-progress.ts +6 -4
  75. package/src/workflows/scripted-workflow.ts +227 -70
@@ -4,15 +4,23 @@ const KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
4
4
 
5
5
  const WORKER_SOURCE = String.raw`
6
6
  const { parentPort } = require("node:worker_threads");
7
+ const { promiseHooks } = require("node:v8");
7
8
  const vm = require("node:vm");
8
9
  const { inspect } = require("node:util");
9
10
 
11
+ if (!promiseHooks || typeof promiseHooks.createHook !== "function") throw new Error("workflowScript requires node:v8 promiseHooks.createHook support.");
12
+
10
13
  let nextCallId = 0;
14
+ let topLevelWorkflowPromise;
15
+ let suppressNativePromiseConsumption = 0;
16
+ const activeNativePromises = [];
11
17
  const pending = new Map();
12
18
  const runKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
13
- const trackedPromisePatch = Symbol("trackedPromisePatch");
14
- const trackedPromiseObservations = new WeakMap();
15
- let suppressRunObservation = 0;
19
+ const trackedPromiseTrackers = new WeakMap();
20
+ const trackedPromiseTargets = new WeakMap();
21
+ let nativePromiseTrackers = new WeakMap();
22
+ let nativePromiseParents = new WeakMap();
23
+ const observedRunCallIds = new Set();
16
24
 
17
25
  function stableRunJson(value) {
18
26
  if (Array.isArray(value)) return "[" + value.map(stableRunJson).join(",") + "]";
@@ -23,15 +31,47 @@ function stableRunJson(value) {
23
31
  function isDirectWorkflowScriptPromiseHandlerCall() {
24
32
  const stack = new Error().stack;
25
33
  if (typeof stack !== "string") return false;
26
- const frame = stack.split("\n").slice(1).map((line) => line.trim()).find((line) =>
27
- line &&
28
- !line.includes("isDirectWorkflowScriptPromiseHandlerCall") &&
29
- !line.includes("markObserved") &&
30
- !line.includes("promise.then") &&
31
- !line.includes("promise.catch") &&
32
- !line.includes("promise.finally")
33
- );
34
- return frame ? frame.includes("workflow-script.js") && !frame.includes("at async ") : false;
34
+ return stack.split("\n").some((line) => line.includes("workflow-script.js") && !line.includes("at async "));
35
+ }
36
+
37
+ function nativePromiseTracker(promise) {
38
+ if (!promise || (typeof promise !== "object" && typeof promise !== "function")) return undefined;
39
+ let tracker = nativePromiseTrackers.get(promise);
40
+ if (!tracker) {
41
+ tracker = { observations: [], consumed: false, dependencies: [] };
42
+ nativePromiseTrackers.set(promise, tracker);
43
+ }
44
+ return tracker;
45
+ }
46
+
47
+ function promiseObservationTracker(value) {
48
+ if (!value || (typeof value !== "object" && typeof value !== "function")) return undefined;
49
+ return trackedPromiseTrackers.get(value) ?? nativePromiseTrackers.get(value);
50
+ }
51
+
52
+ function addTrackerDependency(tracker, dependency) {
53
+ if (!dependency) return;
54
+ tracker.dependencies ??= [];
55
+ if (!tracker.dependencies.includes(dependency)) tracker.dependencies.push(dependency);
56
+ if (tracker.consumed) markTrackedObservationsConsumed(dependency);
57
+ }
58
+
59
+ function descendsFromTopLevelWorkflow(promise) {
60
+ const seen = new Set();
61
+ for (let current = promise; current && !seen.has(current); current = nativePromiseParents.get(current)) {
62
+ if (current === topLevelWorkflowPromise) return true;
63
+ seen.add(current);
64
+ }
65
+ return false;
66
+ }
67
+
68
+ function withSuppressedNativePromiseConsumption(callback) {
69
+ suppressNativePromiseConsumption++;
70
+ try {
71
+ return callback();
72
+ } finally {
73
+ suppressNativePromiseConsumption--;
74
+ }
35
75
  }
36
76
 
37
77
  function mergeObservations(...groups) {
@@ -47,63 +87,107 @@ function mergeObservations(...groups) {
47
87
  return merged;
48
88
  }
49
89
 
50
- function trackedObservations(value) {
51
- return value && (typeof value === "object" || typeof value === "function") ? trackedPromiseObservations.get(value) ?? [] : [];
90
+ function trackedObservationTracker(value) {
91
+ return value && (typeof value === "object" || typeof value === "function") ? trackedPromiseTrackers.get(value) : undefined;
52
92
  }
53
93
 
54
- function withSuppressedRunObservation(callback) {
55
- suppressRunObservation += 1;
56
- try {
57
- return callback();
58
- } finally {
59
- suppressRunObservation -= 1;
94
+ function trackedPromiseTarget(value) {
95
+ return value && (typeof value === "object" || typeof value === "function") ? trackedPromiseTargets.get(value) ?? value : value;
96
+ }
97
+
98
+ function addTrackedObservations(tracker, observations) {
99
+ tracker.observations = mergeObservations(tracker.observations, observations);
100
+ if (!tracker.consumed) return;
101
+ for (const observation of tracker.observations) {
102
+ if (observedRunCallIds.has(observation.callId)) continue;
103
+ observedRunCallIds.add(observation.callId);
104
+ parentPort.postMessage({ type: "runObserved", callId: observation.callId, key: observation.key });
105
+ }
106
+ }
107
+
108
+ function markTrackedObservationsConsumed(tracker, seen = new Set()) {
109
+ if (seen.has(tracker)) return;
110
+ seen.add(tracker);
111
+ tracker.consumed = true;
112
+ addTrackedObservations(tracker, []);
113
+ for (const dependency of tracker.dependencies ?? []) markTrackedObservationsConsumed(dependency, seen);
114
+ }
115
+
116
+ function consumeTrackedObservations(tracker) {
117
+ if (isDirectWorkflowScriptPromiseHandlerCall()) return;
118
+ const activePromise = activeNativePromises.at(-1);
119
+ if (activePromise === topLevelWorkflowPromise || descendsFromTopLevelWorkflow(activePromise)) {
120
+ markTrackedObservationsConsumed(tracker);
121
+ } else if (activePromise) {
122
+ addTrackerDependency(nativePromiseTracker(activePromise), tracker);
123
+ } else {
124
+ markTrackedObservationsConsumed(tracker);
60
125
  }
61
126
  }
62
127
 
128
+ function trackObservationTracker(tracker, promise, allowFutureObservations = false) {
129
+ const target = trackedPromiseTarget(promise);
130
+ if ((!allowFutureObservations && tracker.observations.length === 0) || !target || typeof target.then !== "function") return promise;
131
+
132
+ const tracked = new Proxy(target, {
133
+ get(promiseTarget, prop) {
134
+ if (prop === "then") return function promiseThen(onFulfilled, onRejected) {
135
+ consumeTrackedObservations(tracker);
136
+ return trackObservationTracker({ observations: tracker.observations, consumed: false, dependencies: [tracker] }, promiseTarget.then(onFulfilled, onRejected), true);
137
+ };
138
+ if (prop === "catch") return function promiseCatch(onRejected) {
139
+ consumeTrackedObservations(tracker);
140
+ return trackObservationTracker({ observations: tracker.observations, consumed: false, dependencies: [tracker] }, promiseTarget.catch(onRejected), true);
141
+ };
142
+ if (prop === "finally") return function promiseFinally(onFinally) {
143
+ consumeTrackedObservations(tracker);
144
+ return trackObservationTracker({ observations: tracker.observations, consumed: false, dependencies: [tracker] }, promiseTarget.finally(onFinally), true);
145
+ };
146
+ return Reflect.get(promiseTarget, prop, promiseTarget);
147
+ },
148
+ });
149
+ trackedPromiseTrackers.set(tracked, tracker);
150
+ trackedPromiseTargets.set(tracked, target);
151
+ return tracked;
152
+ }
153
+
63
154
  function trackRunObservation(observations, promise) {
64
- const merged = mergeObservations(trackedObservations(promise), observations);
65
- if (merged.length === 0 || !promise || typeof promise.then !== "function") return promise;
66
- trackedPromiseObservations.set(promise, merged);
67
- if (promise[trackedPromisePatch]) return promise;
68
-
69
- const observedCallIds = new Set();
70
- const markObserved = () => {
71
- if (suppressRunObservation > 0 || isDirectWorkflowScriptPromiseHandlerCall()) return;
72
- for (const observation of trackedObservations(promise)) {
73
- if (observedCallIds.has(observation.callId)) continue;
74
- observedCallIds.add(observation.callId);
75
- parentPort.postMessage({ type: "runObserved", callId: observation.callId, key: observation.key });
76
- }
77
- };
78
- const originalThen = promise.then.bind(promise);
79
- const originalCatch = promise.catch.bind(promise);
80
- const originalFinally = promise.finally.bind(promise);
81
- Object.defineProperty(promise, trackedPromisePatch, { value: true });
82
- promise.then = (onFulfilled, onRejected) => {
83
- markObserved();
84
- return trackRunObservation(trackedObservations(promise), originalThen(onFulfilled, onRejected));
85
- };
86
- promise.catch = (onRejected) => {
87
- markObserved();
88
- return trackRunObservation(trackedObservations(promise), originalCatch(onRejected));
89
- };
90
- promise.finally = (onFinally) => {
91
- markObserved();
92
- return trackRunObservation(trackedObservations(promise), originalFinally(onFinally));
93
- };
94
- return promise;
155
+ const tracker = trackedObservationTracker(promise) ?? { observations: [], consumed: false };
156
+ addTrackedObservations(tracker, observations);
157
+ return trackObservationTracker(tracker, promise);
95
158
  }
96
159
 
97
160
  function trackPromiseCombinator(items, createPromise) {
98
161
  const values = Array.from(items);
99
- const observations = mergeObservations(...values.map(trackedObservations));
100
- const promise = withSuppressedRunObservation(() => createPromise(values));
101
- return observations.length > 0 ? trackRunObservation(observations, promise) : promise;
162
+ const dependencies = [...new Set(values.map(promiseObservationTracker).filter(Boolean))];
163
+ const promise = withSuppressedNativePromiseConsumption(() => createPromise(values.map(trackedPromiseTarget)));
164
+ if (dependencies.length === 0) return promise;
165
+ return trackObservationTracker({ observations: [], consumed: false, dependencies }, promise, true);
102
166
  }
103
167
 
104
168
  const workflowPromise = new Proxy(Promise, {
105
- construct(target, args) {
106
- return new target(...args);
169
+ construct(target, [executor]) {
170
+ if (typeof executor !== "function") return new target(executor);
171
+ const tracker = { observations: [], consumed: false };
172
+ const promise = new target((resolve, reject) => {
173
+ let settled = false;
174
+ try {
175
+ executor((value) => {
176
+ if (settled) return;
177
+ settled = true;
178
+ addTrackerDependency(tracker, promiseObservationTracker(value));
179
+ resolve(trackedPromiseTarget(value));
180
+ }, (reason) => {
181
+ if (settled) return;
182
+ settled = true;
183
+ reject(reason);
184
+ });
185
+ } catch (error) {
186
+ settled = true;
187
+ throw error;
188
+ }
189
+ });
190
+ return trackObservationTracker(tracker, promise, true);
107
191
  },
108
192
  get(target, prop) {
109
193
  if (prop === "all") return (items) => trackPromiseCombinator(items, (values) => target.all(values));
@@ -111,9 +195,10 @@ const workflowPromise = new Proxy(Promise, {
111
195
  if (prop === "race") return (items) => trackPromiseCombinator(items, (values) => target.race(values));
112
196
  if (prop === "any") return (items) => trackPromiseCombinator(items, (values) => target.any(values));
113
197
  if (prop === "resolve") return (value) => {
114
- const observations = trackedObservations(value);
115
- const promise = withSuppressedRunObservation(() => target.resolve(value));
116
- return observations.length > 0 ? trackRunObservation(observations, promise) : promise;
198
+ const dependency = promiseObservationTracker(value);
199
+ const promise = withSuppressedNativePromiseConsumption(() => target.resolve(trackedPromiseTarget(value)));
200
+ if (!dependency) return promise;
201
+ return trackObservationTracker({ observations: [], consumed: false, dependencies: [dependency] }, promise, true);
117
202
  };
118
203
  const value = target[prop];
119
204
  return typeof value === "function" ? value.bind(target) : value;
@@ -289,7 +374,11 @@ parentPort.on("message", async (message) => {
289
374
  if (!entry) return;
290
375
  pending.delete(message.callId);
291
376
  if (message.ok) entry.resolve(message.value);
292
- else entry.reject(new Error(message.error));
377
+ else {
378
+ const error = new Error(message.error);
379
+ if (message.errorKind === "detached-child") error.workflowErrorKind = "detached-child";
380
+ entry.reject(error);
381
+ }
293
382
  return;
294
383
  }
295
384
  if (message.type !== "start") return;
@@ -306,12 +395,68 @@ parentPort.on("message", async (message) => {
306
395
  parentPort.postMessage({ type: "error", error: formatWorkflowScriptSyntaxError(error) });
307
396
  return;
308
397
  }
309
- const value = await compiled.runInContext(context);
398
+ const nativePromisePrototype = vm.runInContext("(async () => {})().constructor.prototype", context);
399
+ const nativeThenDescriptor = Object.getOwnPropertyDescriptor(nativePromisePrototype, "then");
400
+ if (!nativeThenDescriptor || typeof nativeThenDescriptor.value !== "function") throw new Error("workflowScript could not inspect the VM Promise.prototype.then method.");
401
+ const nativeThen = nativeThenDescriptor.value;
402
+ let stopWorkflowPromiseHook;
403
+ let value;
404
+ try {
405
+ Object.defineProperty(nativePromisePrototype, "then", {
406
+ ...nativeThenDescriptor,
407
+ value: function workflowPromiseThen(...args) {
408
+ if (isDirectWorkflowScriptPromiseHandlerCall() || suppressNativePromiseConsumption > 0) {
409
+ return withSuppressedNativePromiseConsumption(() => Reflect.apply(nativeThen, this, args));
410
+ }
411
+ return Reflect.apply(nativeThen, this, args);
412
+ },
413
+ });
414
+ stopWorkflowPromiseHook = promiseHooks.createHook({
415
+ before(promise) {
416
+ activeNativePromises.push(promise);
417
+ },
418
+ after(promise) {
419
+ const index = activeNativePromises.lastIndexOf(promise);
420
+ if (index !== -1) activeNativePromises.splice(index, 1);
421
+ },
422
+ init(promise, parent) {
423
+ const childTracker = nativePromiseTracker(promise);
424
+ if (!parent) return;
425
+ nativePromiseParents.set(promise, parent);
426
+ const parentTracker = nativePromiseTracker(parent);
427
+ addTrackerDependency(childTracker, parentTracker);
428
+ const activePromise = activeNativePromises.at(-1);
429
+ if (activePromise && activePromise !== parent) {
430
+ addTrackerDependency(nativePromiseTracker(activePromise), parentTracker);
431
+ } else if (!activePromise && suppressNativePromiseConsumption === 0) {
432
+ markTrackedObservationsConsumed(parentTracker);
433
+ }
434
+ },
435
+ });
436
+ const workflowResultPromise = compiled.runInContext(context);
437
+ topLevelWorkflowPromise = workflowResultPromise;
438
+ markTrackedObservationsConsumed(nativePromiseTracker(workflowResultPromise));
439
+ value = await workflowResultPromise;
440
+ } finally {
441
+ try {
442
+ stopWorkflowPromiseHook?.();
443
+ } finally {
444
+ try {
445
+ Object.defineProperty(nativePromisePrototype, "then", nativeThenDescriptor);
446
+ } finally {
447
+ topLevelWorkflowPromise = undefined;
448
+ activeNativePromises.length = 0;
449
+ suppressNativePromiseConsumption = 0;
450
+ nativePromiseTrackers = new WeakMap();
451
+ nativePromiseParents = new WeakMap();
452
+ }
453
+ }
454
+ }
310
455
  const persistedValue = value === undefined ? null : omitUndefinedWorkflowValues(value);
311
456
  assertJsonValue(persistedValue, "return");
312
457
  parentPort.postMessage({ type: "complete", value: persistedValue });
313
458
  } catch (error) {
314
- parentPort.postMessage({ type: "error", error: error && error.stack ? error.stack : String(error) });
459
+ parentPort.postMessage({ type: "error", error: error && error.stack ? error.stack : String(error), ...(error && error.workflowErrorKind === "detached-child" ? { errorKind: "detached-child" } : {}) });
315
460
  }
316
461
  });
317
462
  `;
@@ -324,6 +469,7 @@ export interface WorkflowScriptChildResult {
324
469
  runId?: string;
325
470
  output: string;
326
471
  error?: string;
472
+ detached?: boolean;
327
473
  structuredOutput?: unknown;
328
474
  artifactPaths: string[];
329
475
  results?: unknown[];
@@ -332,7 +478,7 @@ export interface WorkflowScriptChildResult {
332
478
  export interface WorkflowScriptTraceEntry {
333
479
  operation: "run" | "status";
334
480
  key: string;
335
- state: "started" | "completed" | "failed" | "stopped" | "reused";
481
+ state: "started" | "completed" | "failed" | "detached" | "stopped" | "reused";
336
482
  /** Canonical child agent name when resolved launch or result data is available. */
337
483
  agent?: string;
338
484
  runId?: string;
@@ -352,11 +498,13 @@ export interface WorkflowScriptResult {
352
498
 
353
499
  export class WorkflowScriptError extends Error {
354
500
  readonly partial: Omit<WorkflowScriptResult, "value">;
501
+ readonly errorKind?: "detached-child";
355
502
 
356
- constructor(message: string, partial: Omit<WorkflowScriptResult, "value">) {
503
+ constructor(message: string, partial: Omit<WorkflowScriptResult, "value">, errorKind?: "detached-child") {
357
504
  super(message);
358
505
  this.name = "WorkflowScriptError";
359
506
  this.partial = partial;
507
+ this.errorKind = errorKind;
360
508
  }
361
509
  }
362
510
 
@@ -505,7 +653,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
505
653
  const traceChanged = () => options.onTrace?.([...trace]);
506
654
 
507
655
  return await new Promise<WorkflowScriptResult>((resolve, reject) => {
508
- const finish = (outcome: { value: unknown } | { error: Error }) => {
656
+ const finish = (outcome: { value: unknown } | { error: Error & { workflowErrorKind?: unknown } }) => {
509
657
  if (settled) return;
510
658
  settled = true;
511
659
  if (timer) clearTimeout(timer);
@@ -516,7 +664,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
516
664
  ? new Error(`workflowScript completed with unawaited runs.run launch(es): ${unobservedKeys.map((key) => `'${key}'`).join(", ")}. Await or return each launch.`)
517
665
  : undefined;
518
666
  childController.abort("error" in outcome ? outcome.error : completionError ?? new Error("Workflow script completed."));
519
- if ("error" in outcome) reject(new WorkflowScriptError(outcome.error.message, partial()));
667
+ if ("error" in outcome) reject(new WorkflowScriptError(outcome.error.message, partial(), outcome.error.workflowErrorKind === "detached-child" ? "detached-child" : undefined));
520
668
  else if (completionError) reject(new WorkflowScriptError(completionError.message, partial()));
521
669
  else resolve({ value: outcome.value, ...partial() });
522
670
  };
@@ -585,7 +733,11 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
585
733
  }
586
734
  return finish({ value: message.value });
587
735
  }
588
- if (message.type === "error") return finish({ error: new Error(typeof message.error === "string" ? message.error : "Workflow script failed.") });
736
+ if (message.type === "error") {
737
+ const workflowError = new Error(typeof message.error === "string" ? message.error : "Workflow script failed.") as Error & { workflowErrorKind?: "detached-child" };
738
+ if (message.errorKind === "detached-child") workflowError.workflowErrorKind = "detached-child";
739
+ return finish({ error: workflowError });
740
+ }
589
741
  if (message.type === "runObserved" && typeof message.callId === "number") {
590
742
  const key = typeof message.key === "string" ? message.key : undefined;
591
743
  const launch = key ? launches.get(key) : undefined;
@@ -601,7 +753,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
601
753
  if (!settled) worker.postMessage({ type: "response", callId: message.callId, ok: true, value: omitUndefinedWorkflowValues(value) });
602
754
  },
603
755
  (error: unknown) => {
604
- if (!settled) worker.postMessage({ type: "response", callId: message.callId, ok: false, error: error instanceof Error ? error.message : String(error) });
756
+ if (!settled) worker.postMessage({ type: "response", callId: message.callId, ok: false, error: error instanceof Error ? error.message : String(error), ...(error instanceof Error && (error as { workflowErrorKind?: unknown }).workflowErrorKind === "detached-child" ? { errorKind: "detached-child" } : {}) });
605
757
  },
606
758
  );
607
759
  };
@@ -693,7 +845,11 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
693
845
  const deliver = (promise: Promise<WorkflowScriptChildResult>) => collectFailure
694
846
  ? promise
695
847
  : promise.then((result) => {
696
- if (!result.ok) throw new Error(`Run '${key}' failed: ${result.error ?? result.output}`);
848
+ if (!result.ok) {
849
+ const childError = new Error(result.detached ? `Run '${key}' detached: ${result.error ?? result.output}` : `Run '${key}' failed: ${result.error ?? result.output}`) as Error & { workflowErrorKind?: "detached-child" };
850
+ if (result.detached) childError.workflowErrorKind = "detached-child";
851
+ throw childError;
852
+ }
697
853
  return result;
698
854
  });
699
855
  const fingerprint = stableJson(params);
@@ -735,7 +891,8 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
735
891
  const normalized = !result.ok && !result.error ? { ...result, error: result.output } : result;
736
892
  if (stoppedLaunches.has(key)) return normalized;
737
893
  children.set(key, normalized);
738
- trace.push({ operation: "run", key, state: normalized.ok ? "completed" : "failed", durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), ...(normalized.agent ? { agent: normalized.agent } : {}), ...(normalized.runId ? { runId: normalized.runId } : {}), ...(!normalized.ok ? { error: normalized.error ?? normalized.output } : {}) });
894
+ const state = normalized.ok ? "completed" : normalized.detached ? "detached" : "failed";
895
+ trace.push({ operation: "run", key, state, durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), ...(normalized.agent ? { agent: normalized.agent } : {}), ...(normalized.runId ? { runId: normalized.runId } : {}), ...(!normalized.ok ? { error: normalized.error ?? normalized.output } : {}) });
739
896
  traceChanged();
740
897
  return normalized;
741
898
  }, (error: unknown) => {