@bastani/atomic 0.9.45-alpha.1 → 0.9.46-alpha.1

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 (166) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +11 -0
  3. package/dist/builtin/intercom/broker/broker.ts +124 -10
  4. package/dist/builtin/intercom/broker/client.ts +177 -25
  5. package/dist/builtin/intercom/broker/multinode-router.ts +228 -27
  6. package/dist/builtin/intercom/broker/multinode-state-store.ts +10 -0
  7. package/dist/builtin/intercom/broker/recipient-receipts.ts +38 -4
  8. package/dist/builtin/intercom/broker/send-handler.ts +9 -2
  9. package/dist/builtin/intercom/broker/sql-outbox-store.ts +29 -4
  10. package/dist/builtin/intercom/group.ts +25 -0
  11. package/dist/builtin/intercom/index.bundle.mjs +177 -35
  12. package/dist/builtin/intercom/package.json +1 -1
  13. package/dist/builtin/intercom/retry-policy.ts +7 -0
  14. package/dist/builtin/intercom/types.ts +21 -0
  15. package/dist/builtin/mcp/package.json +1 -1
  16. package/dist/builtin/subagents/CHANGELOG.md +10 -0
  17. package/dist/builtin/subagents/package.json +1 -1
  18. package/dist/builtin/subagents/src/extension/index.bundle.mjs +150 -54
  19. package/dist/builtin/web-access/package.json +1 -1
  20. package/dist/builtin/workflows/CHANGELOG.md +45 -0
  21. package/dist/builtin/workflows/builtin/{chunk-6901nsd6.js → chunk-6jpwc09k.js} +1 -1
  22. package/dist/builtin/workflows/builtin/{chunk-vwx023ge.js → chunk-qhm35ntx.js} +1 -1
  23. package/dist/builtin/workflows/builtin/{chunk-jvna28mq.js → chunk-zhyqnf5g.js} +331 -68
  24. package/dist/builtin/workflows/builtin/goal.js +2 -2
  25. package/dist/builtin/workflows/builtin/index.js +3 -3
  26. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  27. package/dist/builtin/workflows/package.json +1 -1
  28. package/dist/builtin/workflows/skills/prompt-engineer/SKILL.md +1 -0
  29. package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_6.md +2 -2
  30. package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_6_1_sol.md +2 -2
  31. package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_6_1_terra.md +77 -0
  32. package/dist/builtin/workflows/src/authoring.d.ts +1 -1
  33. package/dist/builtin/workflows/src/durable/journal/internal.d.ts +2 -0
  34. package/dist/builtin/workflows/src/extension/index.bundle.mjs +7076 -3386
  35. package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +11075 -7261
  36. package/dist/builtin/workflows/src/index.js +6768 -3605
  37. package/dist/builtin/workflows/src/local-worker-entry.mjs +11075 -7261
  38. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +19 -0
  39. package/dist/core/agent-session-boundaries.d.ts.map +1 -1
  40. package/dist/core/agent-session-boundaries.js +1 -0
  41. package/dist/core/agent-session-boundaries.js.map +1 -1
  42. package/dist/core/agent-session-events.d.ts.map +1 -1
  43. package/dist/core/agent-session-events.js +2 -1
  44. package/dist/core/agent-session-events.js.map +1 -1
  45. package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
  46. package/dist/core/agent-session-extension-bindings.js +15 -2
  47. package/dist/core/agent-session-extension-bindings.js.map +1 -1
  48. package/dist/core/agent-session-methods.d.ts +3 -2
  49. package/dist/core/agent-session-methods.d.ts.map +1 -1
  50. package/dist/core/agent-session-methods.js.map +1 -1
  51. package/dist/core/agent-session-prompt.d.ts +4 -2
  52. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  53. package/dist/core/agent-session-prompt.js +13 -8
  54. package/dist/core/agent-session-prompt.js.map +1 -1
  55. package/dist/core/agent-session-recovery.d.ts.map +1 -1
  56. package/dist/core/agent-session-recovery.js +92 -1
  57. package/dist/core/agent-session-recovery.js.map +1 -1
  58. package/dist/core/agent-session-transfer.d.ts.map +1 -1
  59. package/dist/core/agent-session-transfer.js +5 -0
  60. package/dist/core/agent-session-transfer.js.map +1 -1
  61. package/dist/core/agent-session.d.ts +2 -0
  62. package/dist/core/agent-session.d.ts.map +1 -1
  63. package/dist/core/agent-session.js +2 -0
  64. package/dist/core/agent-session.js.map +1 -1
  65. package/dist/core/copilot-errors.d.ts +6 -0
  66. package/dist/core/copilot-errors.d.ts.map +1 -0
  67. package/dist/core/copilot-errors.js +17 -0
  68. package/dist/core/copilot-errors.js.map +1 -0
  69. package/dist/core/execution-model-router.d.ts.map +1 -1
  70. package/dist/core/execution-model-router.js +12 -2
  71. package/dist/core/execution-model-router.js.map +1 -1
  72. package/dist/core/extensions/context-types.d.ts +7 -0
  73. package/dist/core/extensions/context-types.d.ts.map +1 -1
  74. package/dist/core/extensions/context-types.js.map +1 -1
  75. package/dist/core/extensions/runner-context.d.ts.map +1 -1
  76. package/dist/core/extensions/runner-context.js +6 -0
  77. package/dist/core/extensions/runner-context.js.map +1 -1
  78. package/dist/core/extensions/workflow-activity-hub.d.ts +4 -0
  79. package/dist/core/extensions/workflow-activity-hub.d.ts.map +1 -1
  80. package/dist/core/extensions/workflow-activity-hub.js +26 -5
  81. package/dist/core/extensions/workflow-activity-hub.js.map +1 -1
  82. package/dist/core/extensions/workflow-events.d.ts +5 -0
  83. package/dist/core/extensions/workflow-events.d.ts.map +1 -1
  84. package/dist/core/extensions/workflow-events.js.map +1 -1
  85. package/dist/core/messages.d.ts +5 -0
  86. package/dist/core/messages.d.ts.map +1 -1
  87. package/dist/core/messages.js +1 -1
  88. package/dist/core/messages.js.map +1 -1
  89. package/dist/core/model-fallback-failures.d.ts +1 -1
  90. package/dist/core/model-fallback-failures.d.ts.map +1 -1
  91. package/dist/core/model-fallback-failures.js +37 -3
  92. package/dist/core/model-fallback-failures.js.map +1 -1
  93. package/dist/core/model-runtime-streaming.js +2 -2
  94. package/dist/core/model-runtime-streaming.js.map +1 -1
  95. package/dist/core/provider-composer.d.ts.map +1 -1
  96. package/dist/core/provider-composer.js +19 -11
  97. package/dist/core/provider-composer.js.map +1 -1
  98. package/dist/core/provider-rate-limits.d.ts +55 -6
  99. package/dist/core/provider-rate-limits.d.ts.map +1 -1
  100. package/dist/core/provider-rate-limits.js +182 -29
  101. package/dist/core/provider-rate-limits.js.map +1 -1
  102. package/dist/core/provider-request-admission.d.ts.map +1 -1
  103. package/dist/core/provider-request-admission.js +42 -18
  104. package/dist/core/provider-request-admission.js.map +1 -1
  105. package/dist/core/storage/node-sqlite.d.ts.map +1 -1
  106. package/dist/core/storage/node-sqlite.js +176 -19
  107. package/dist/core/storage/node-sqlite.js.map +1 -1
  108. package/dist/core/storage/provider.d.ts +4 -2
  109. package/dist/core/storage/provider.d.ts.map +1 -1
  110. package/dist/core/storage/provider.js +94 -5
  111. package/dist/core/storage/provider.js.map +1 -1
  112. package/dist/core/structured-output/index.d.ts.map +1 -1
  113. package/dist/core/structured-output/index.js +15 -8
  114. package/dist/core/structured-output/index.js.map +1 -1
  115. package/dist/core/structured-output/provider-failure.d.ts +17 -0
  116. package/dist/core/structured-output/provider-failure.d.ts.map +1 -0
  117. package/dist/core/structured-output/provider-failure.js +28 -0
  118. package/dist/core/structured-output/provider-failure.js.map +1 -0
  119. package/dist/core/tools/bash.d.ts.map +1 -1
  120. package/dist/core/tools/bash.js +13 -7
  121. package/dist/core/tools/bash.js.map +1 -1
  122. package/dist/core/tools/shell-task-render.d.ts +1 -0
  123. package/dist/core/tools/shell-task-render.d.ts.map +1 -1
  124. package/dist/core/tools/shell-task-render.js +7 -2
  125. package/dist/core/tools/shell-task-render.js.map +1 -1
  126. package/dist/extensions/tool-search/tool.d.ts +5 -0
  127. package/dist/extensions/tool-search/tool.d.ts.map +1 -1
  128. package/dist/extensions/tool-search/tool.js +38 -5
  129. package/dist/extensions/tool-search/tool.js.map +1 -1
  130. package/dist/index.d.ts +1 -1
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +1 -1
  133. package/dist/index.js.map +1 -1
  134. package/dist/modes/interactive/components/chat-session-host-actions.js +3 -2
  135. package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
  136. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  137. package/dist/modes/interactive/components/login-dialog.js +42 -14
  138. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  139. package/dist/modes/interactive/interactive-extension-context.js +2 -1
  140. package/dist/modes/interactive/interactive-extension-context.js.map +1 -1
  141. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  142. package/dist/modes/interactive/interactive-startup.js +5 -6
  143. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  144. package/dist/modes/rpc/rpc-client-process.d.ts.map +1 -1
  145. package/dist/modes/rpc/rpc-client-process.js +8 -1
  146. package/dist/modes/rpc/rpc-client-process.js.map +1 -1
  147. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  148. package/dist/modes/rpc/rpc-client.js +7 -1
  149. package/dist/modes/rpc/rpc-client.js.map +1 -1
  150. package/dist/utils/ansi.d.ts +2 -0
  151. package/dist/utils/ansi.d.ts.map +1 -1
  152. package/dist/utils/ansi.js +5 -0
  153. package/dist/utils/ansi.js.map +1 -1
  154. package/docs/codemode.md +1 -1
  155. package/docs/intercom/reference.md +3 -3
  156. package/docs/models/evals.md +31 -12
  157. package/docs/models/reference.md +23 -51
  158. package/docs/models.md +3 -4
  159. package/docs/providers.md +5 -15
  160. package/docs/settings.md +1 -1
  161. package/docs/storage.md +2 -2
  162. package/docs/tools.md +1 -1
  163. package/docs/workflows/api-reference.md +20 -1
  164. package/docs/workflows/operations.md +17 -4
  165. package/npm-shrinkwrap.json +64 -44
  166. package/package.json +4 -4
@@ -7785,6 +7785,9 @@ function splitKnownThinkingSuffix(model) {
7785
7785
  thinkingSuffix: `:${suffix}`
7786
7786
  };
7787
7787
  }
7788
+ function taskRecordModel(model) {
7789
+ return model === undefined ? undefined : splitKnownThinkingSuffix(model).baseModel;
7790
+ }
7788
7791
 
7789
7792
  // dist/builtin/subagents/src/shared/formatters.ts
7790
7793
  function formatTokens(n) {
@@ -9818,6 +9821,14 @@ function resumeRunWorktree(child, setupHook) {
9818
9821
  }
9819
9822
 
9820
9823
  // dist/builtin/subagents/src/runs/foreground/subagent-child-records.ts
9824
+ function heldTaskExpands(held) {
9825
+ return held.taskExpands ?? true;
9826
+ }
9827
+ function pendingMessagesOfResult(result) {
9828
+ if (!result.pendingMessages?.length)
9829
+ return;
9830
+ return { messages: result.pendingMessages, taskExpands: result.pendingTaskExpands === true };
9831
+ }
9821
9832
  function subagentChildInheritance(options) {
9822
9833
  return {
9823
9834
  ...options.currentModel ? { model: options.currentModel } : {},
@@ -10741,8 +10752,8 @@ async function runAgentTask(input) {
10741
10752
  ...input.options.orchestratorIntercomTarget ? { orchestratorTarget: input.options.orchestratorIntercomTarget } : {},
10742
10753
  ...worktree ? { worktree } : {},
10743
10754
  ...terminalStatus ? { terminalStatus } : {},
10744
- ...heldMessages?.length ? { heldMessages: { taskId, messages: heldMessages } } : {},
10745
- ...launch ? { launch } : {},
10755
+ ...heldMessages?.messages.length ? { heldMessages: { taskId, ...heldMessages } } : {},
10756
+ ...launch ? { launch } : heldMessages?.taskExpands && input.options.resumeSession !== true && childSession.childSessionId === undefined ? { launch: subagentChildLaunch(input.task, input.intentTask, input.options) } : {},
10746
10757
  inherited: subagentChildInheritance(input.options)
10747
10758
  });
10748
10759
  };
@@ -10888,7 +10899,7 @@ ${launch.message}` : input.task,
10888
10899
  if (child.model !== undefined || child.thinking !== undefined)
10889
10900
  context.reportActivity({
10890
10901
  reportId: "terminal-model",
10891
- change: { kind: "model", model: child.model, thinking: child.thinking }
10902
+ change: { kind: "model", model: taskRecordModel(child.model), thinking: child.thinking }
10892
10903
  });
10893
10904
  const stopped = takeSubagentChildStop(context.ref.taskId);
10894
10905
  const stoppedByControl = stopped !== undefined && child.status === "interrupted";
@@ -10901,11 +10912,11 @@ ${launch.message}` : input.task,
10901
10912
  if (endedForGood || child.status === "killed")
10902
10913
  forgetRunMembership(runId, index);
10903
10914
  if (!ownerClosed())
10904
- recordChild("terminal", stoppedByControl ? stopped : stoppedByStagePause ? SUBAGENT_CHILD_STAGE_PAUSED_STATUS : child.status, stoppedByControl || stoppedByStagePause ? child.pendingMessages : undefined);
10915
+ recordChild("terminal", stoppedByControl ? stopped : stoppedByStagePause ? SUBAGENT_CHILD_STAGE_PAUSED_STATUS : child.status, stoppedByControl || stoppedByStagePause ? pendingMessagesOfResult(child) : undefined);
10905
10916
  else if (endedForGood)
10906
10917
  recordChild("terminal", SUBAGENT_CHILD_STAGE_CLOSED_STATUS);
10907
10918
  else if (child.pendingMessages?.length)
10908
- recordChild("running", undefined, child.pendingMessages);
10919
+ recordChild("running", undefined, pendingMessagesOfResult(child));
10909
10920
  input.onTerminal?.(child);
10910
10921
  const text = input.outputText?.(child) ?? getSingleResultOutput(child);
10911
10922
  retainTaskOutput(child, text, context.ref);
@@ -11070,7 +11081,7 @@ async function runForegroundParallelTasks(input) {
11070
11081
  const sharedProgress = index === input.behaviors.findIndex((candidate) => candidate.progress) ? join12(input.paramsCwd, "progress.md") : undefined;
11071
11082
  const runOptions = {
11072
11083
  runningCapacity: input.runningCapacity,
11073
- getChildSessionOptions: input.ctx.getChildSessionOptions,
11084
+ getChildSessionOptions: input.ctx.getChildSessionOptionsResolver?.() ?? input.ctx.getChildSessionOptions,
11074
11085
  resourceLoaderInheritanceSnapshot: input.resourceLoaderInheritanceSnapshot,
11075
11086
  cwd: taskCwd,
11076
11087
  signal: input.signal,
@@ -11186,6 +11197,7 @@ async function runForegroundParallelTasks(input) {
11186
11197
  taskResponse: response
11187
11198
  };
11188
11199
  }
11200
+ const launchCwd = input.ctx.cwd;
11189
11201
  const launched = runOwnerlessChild({
11190
11202
  agent: task.agent,
11191
11203
  ...agentConfig ? { agentSource: agentConfig.source } : {},
@@ -11194,7 +11206,7 @@ async function runForegroundParallelTasks(input) {
11194
11206
  options: runOptions,
11195
11207
  recordChild: input.recordChild ?? { record() {} },
11196
11208
  schedule,
11197
- runSync: (childTask, options) => input.runtime.runSync(input.ctx.cwd, taskAgents, task.agent, childTask, options)
11209
+ runSync: (childTask, options) => input.runtime.runSync(launchCwd, taskAgents, task.agent, childTask, options)
11198
11210
  }).result;
11199
11211
  return launched.finally(() => {
11200
11212
  if (input.foregroundControl?.currentIndex === index) {
@@ -11255,8 +11267,8 @@ function runOwnerlessChild(input) {
11255
11267
  ...input.options.orchestratorIntercomTarget ? { orchestratorTarget: input.options.orchestratorIntercomTarget } : {},
11256
11268
  ...worktree ? { worktree } : {},
11257
11269
  ...terminalStatus ? { terminalStatus } : {},
11258
- ...held?.length ? { heldMessages: { taskId: launchId, messages: held } } : {},
11259
- ...queuedLaunch ? { launch: queuedLaunch } : {},
11270
+ ...held?.messages.length ? { heldMessages: { taskId: launchId, ...held } } : {},
11271
+ ...queuedLaunch ? { launch: queuedLaunch } : held?.taskExpands && input.options.resumeSession !== true && childSession.childSessionId === undefined ? { launch: subagentChildLaunch(input.task, undefined, input.options) } : {},
11260
11272
  inherited: subagentChildInheritance(input.options)
11261
11273
  });
11262
11274
  };
@@ -11333,10 +11345,10 @@ ${resumed.message}` : input.task,
11333
11345
  forgetRunMembership(runId, index);
11334
11346
  if (ownerClosed) {
11335
11347
  parkRunWorktree(runId, index);
11336
- record("running", undefined, child.pendingMessages);
11348
+ record("running", undefined, pendingMessagesOfResult(child));
11337
11349
  return child;
11338
11350
  }
11339
- record("terminal", stoppedByControl ? stopped : child.status, stoppedByControl ? child.pendingMessages : undefined);
11351
+ record("terminal", stoppedByControl ? stopped : child.status, stoppedByControl ? pendingMessagesOfResult(child) : undefined);
11340
11352
  return child;
11341
11353
  } catch (error) {
11342
11354
  if (takeSubagentChildStop(launchId) !== undefined)
@@ -12849,24 +12861,41 @@ function statsFor(session, fallbackSessionId) {
12849
12861
  };
12850
12862
  }
12851
12863
  }
12852
- function outputFor(session) {
12853
- return lastNonEmptyAssistantText(session?.messages, session?.getLastAssistantText());
12864
+ function messagesSinceLaunch(manager, launchLeafId) {
12865
+ const branch = manager.getBranch();
12866
+ const launchIndex = launchLeafId === null ? -1 : branch.findIndex((entry) => entry.id === launchLeafId);
12867
+ if (launchLeafId !== null && launchIndex === -1)
12868
+ return [];
12869
+ return branch.slice(launchIndex + 1).flatMap((entry) => entry.type === "message" ? [entry.message] : []);
12870
+ }
12871
+ function outputFor(ownMessages) {
12872
+ return lastNonEmptyAssistantText(ownMessages.filter((message) => message.role === "assistant"));
12854
12873
  }
12855
- function cancelledEnvelope(session, spec, stats) {
12874
+ function noSuccessfulTurnCause(ownMessages) {
12875
+ const ownTurns = ownMessages.flatMap((message) => message.role === "assistant" ? [message] : []);
12876
+ if (ownTurns.some((turn) => turn.stopReason !== "error" && turn.stopReason !== "aborted"))
12877
+ return;
12878
+ const errorMessage = ownTurns.findLast((turn) => turn.errorMessage?.trim())?.errorMessage?.trim();
12879
+ return errorMessage || "Child ended without completing an assistant turn of its own.";
12880
+ }
12881
+ function cancelledEnvelope(session, spec, stats, ownMessages) {
12856
12882
  return recoverCancelledChildOutput({
12857
12883
  progressPath: spec.progressPath,
12858
- assistantText: lastNonEmptyAssistantText(session?.messages, outputFor(session)),
12884
+ assistantText: outputFor(ownMessages),
12859
12885
  toolCount: stats.toolCalls,
12860
12886
  sessionPath: session?.sessionFile ?? spec.sessionManager?.getSessionFile(),
12861
12887
  ...spec.progressArtifactPath ? { progressArtifactPath: spec.progressArtifactPath } : {},
12862
12888
  ...spec.outputArtifactPath ? { outputArtifactPath: spec.outputArtifactPath } : {}
12863
12889
  }).text;
12864
12890
  }
12865
- function pendingMessagesOf(session, unsent) {
12891
+ var LITERAL_TEXT = { expandPromptTemplates: false };
12892
+ function pendingMessagesOf(session, unsent, taskLiteral) {
12866
12893
  const queued = session ? [...session.getSteeringMessages(), ...session.getFollowUpMessages()] : [];
12867
12894
  const [task, ...followUps] = unsent;
12868
12895
  const pending = task === undefined ? queued : [task, ...queued, ...followUps];
12869
- return pending.length > 0 ? { pendingMessages: pending } : {};
12896
+ if (pending.length === 0)
12897
+ return {};
12898
+ return { pendingMessages: pending, ...task !== undefined && !taskLiteral ? { pendingTaskExpands: true } : {} };
12870
12899
  }
12871
12900
  function attemptStatus(termination) {
12872
12901
  if (termination === "interrupt" || termination === PARENT_CANCEL_CAUSE)
@@ -13073,14 +13102,11 @@ class SubagentControlRuntime {
13073
13102
  status: killed ? "killed" : "interrupted",
13074
13103
  ...waitResult === PARENT_CANCEL_CAUSE && !killed ? {
13075
13104
  cause: PARENT_CANCEL_CAUSE,
13076
- ...pendingMessagesOf(undefined, [
13077
- admitted.spec.task,
13078
- ...admitted.spec.resumeFollowUps ?? []
13079
- ])
13105
+ ...pendingMessagesOf(undefined, [admitted.spec.task, ...admitted.spec.resumeFollowUps ?? []], admitted.spec.resumeTaskLiteral)
13080
13106
  } : {},
13081
13107
  stats,
13082
13108
  path: admitted.identity.path,
13083
- envelope: killed ? "Killed. This child cannot be resumed." : waitResult === PARENT_CANCEL_CAUSE ? cancelledEnvelope(undefined, admitted.spec, stats) : INTERRUPTED_ENVELOPE,
13109
+ envelope: killed ? "Killed. This child cannot be resumed." : waitResult === PARENT_CANCEL_CAUSE ? cancelledEnvelope(undefined, admitted.spec, stats, []) : INTERRUPTED_ENVELOPE,
13084
13110
  ...effectiveModelId === undefined ? {} : { model: effectiveModelId },
13085
13111
  ...effectiveThinking === undefined ? {} : { thinking: effectiveThinking }
13086
13112
  };
@@ -13115,6 +13141,7 @@ class SubagentControlRuntime {
13115
13141
  const token = guard.token;
13116
13142
  this.attemptTokens.set(admitted.identity.path, token);
13117
13143
  let session;
13144
+ let ownMessages = () => [];
13118
13145
  const executionEnded = new AbortController;
13119
13146
  let termination;
13120
13147
  const unsent = [admitted.spec.task, ...admitted.spec.resumeFollowUps ?? []];
@@ -13163,6 +13190,8 @@ class SubagentControlRuntime {
13163
13190
  admitted.spec.onChildSession?.(childSession);
13164
13191
  if (workflow)
13165
13192
  await sessionManager.markSessionInternal(workflow);
13193
+ const launchLeafId = sessionManager.getLeafId();
13194
+ ownMessages = () => messagesSinceLaunch(sessionManager, launchLeafId);
13166
13195
  let created;
13167
13196
  if (admitted.spec.testSession) {
13168
13197
  created = { session: createTestSession(sessionManager, admitted.spec) };
@@ -13281,7 +13310,7 @@ class SubagentControlRuntime {
13281
13310
  let activitySequence = 0;
13282
13311
  const reportModel = () => taskHooks?.reportActivity({
13283
13312
  reportId: `${activityPrefix}:model-${++activitySequence}`,
13284
- change: { kind: "model", model: effectiveModelId, thinking: effectiveThinking }
13313
+ change: { kind: "model", model: taskRecordModel(effectiveModelId), thinking: effectiveThinking }
13285
13314
  });
13286
13315
  reportModel();
13287
13316
  unsubscribe = session.subscribe((event) => {
@@ -13355,21 +13384,23 @@ class SubagentControlRuntime {
13355
13384
  for (const followUp of admitted.spec.resumeFollowUps ?? []) {
13356
13385
  if (termination)
13357
13386
  break;
13358
- await session.followUp(followUp);
13387
+ await session.followUp(followUp, undefined, LITERAL_TEXT);
13359
13388
  unsent.splice(1, 1);
13360
13389
  }
13361
13390
  if (!termination) {
13362
13391
  unsent.splice(0, 1);
13363
- await session.prompt(admitted.spec.task);
13392
+ await session.prompt(admitted.spec.task, admitted.spec.resumeTaskLiteral ? LITERAL_TEXT : undefined);
13364
13393
  }
13365
13394
  await terminating;
13366
13395
  const stats = statsFor(session, admitted.identity.path);
13367
13396
  const sessionFile = session.sessionFile;
13368
- const output = outputFor(session);
13369
- const status = attemptStatus(termination);
13397
+ const producedMessages = ownMessages();
13398
+ const output = outputFor(producedMessages);
13399
+ const noOutput = termination === undefined ? noSuccessfulTurnCause(producedMessages) : undefined;
13400
+ const status = noOutput === undefined ? attemptStatus(termination) : "error";
13370
13401
  this.native.publishChildStatus(admitted.identity.path, nativeStatus(status));
13371
13402
  this.native.finishChildAttempt(token, nativeStatus(status));
13372
- const envelope = termination === PARENT_CANCEL_CAUSE ? cancelledEnvelope(session, admitted.spec, stats) : status === "interrupted" ? INTERRUPTED_ENVELOPE : boundedEnvelope(output || termination || "(no output)", undefined);
13403
+ const envelope = termination === PARENT_CANCEL_CAUSE ? cancelledEnvelope(session, admitted.spec, stats, producedMessages) : status === "interrupted" ? INTERRUPTED_ENVELOPE : boundedEnvelope(output || noOutput || termination || "(no output)", undefined);
13373
13404
  if (status === "ok")
13374
13405
  return {
13375
13406
  status,
@@ -13386,7 +13417,10 @@ class SubagentControlRuntime {
13386
13417
  if (status === "interrupted")
13387
13418
  return {
13388
13419
  status: this.killedChildren.has(admitted.identity.path) ? "killed" : status,
13389
- ...termination === PARENT_CANCEL_CAUSE && !this.killedChildren.has(admitted.identity.path) ? { cause: PARENT_CANCEL_CAUSE, ...pendingMessagesOf(session, unsent) } : {},
13420
+ ...termination === PARENT_CANCEL_CAUSE && !this.killedChildren.has(admitted.identity.path) ? {
13421
+ cause: PARENT_CANCEL_CAUSE,
13422
+ ...pendingMessagesOf(session, unsent, admitted.spec.resumeTaskLiteral)
13423
+ } : {},
13390
13424
  stats,
13391
13425
  path: admitted.identity.path,
13392
13426
  envelope: this.killedChildren.has(admitted.identity.path) ? "Killed. This child cannot be resumed." : envelope,
@@ -13398,7 +13432,7 @@ class SubagentControlRuntime {
13398
13432
  };
13399
13433
  return {
13400
13434
  status,
13401
- cause: termination ?? "agent session ended without a successful completion",
13435
+ cause: noOutput ?? termination ?? "agent session ended without a successful completion",
13402
13436
  stats,
13403
13437
  path: admitted.identity.path,
13404
13438
  envelope,
@@ -13419,10 +13453,13 @@ class SubagentControlRuntime {
13419
13453
  if (status === "interrupted")
13420
13454
  return {
13421
13455
  status: this.killedChildren.has(admitted.identity.path) ? "killed" : status,
13422
- ...termination === PARENT_CANCEL_CAUSE && !this.killedChildren.has(admitted.identity.path) ? { cause: PARENT_CANCEL_CAUSE, ...pendingMessagesOf(session, unsent) } : {},
13456
+ ...termination === PARENT_CANCEL_CAUSE && !this.killedChildren.has(admitted.identity.path) ? {
13457
+ cause: PARENT_CANCEL_CAUSE,
13458
+ ...pendingMessagesOf(session, unsent, admitted.spec.resumeTaskLiteral)
13459
+ } : {},
13423
13460
  stats,
13424
13461
  path: admitted.identity.path,
13425
- envelope: termination === PARENT_CANCEL_CAUSE && !this.killedChildren.has(admitted.identity.path) ? cancelledEnvelope(session, admitted.spec, stats) : this.killedChildren.has(admitted.identity.path) ? "Killed. This child cannot be resumed." : INTERRUPTED_ENVELOPE,
13462
+ envelope: termination === PARENT_CANCEL_CAUSE && !this.killedChildren.has(admitted.identity.path) ? cancelledEnvelope(session, admitted.spec, stats, ownMessages()) : this.killedChildren.has(admitted.identity.path) ? "Killed. This child cannot be resumed." : INTERRUPTED_ENVELOPE,
13426
13463
  sessionFile: session?.sessionFile,
13427
13464
  ...effectiveModelId === undefined ? {} : { model: effectiveModelId },
13428
13465
  ...effectiveThinking === undefined ? {} : { thinking: effectiveThinking },
@@ -13893,7 +13930,10 @@ function resultFromOutcome(agent, task, outcome, startedAt, artifactPaths) {
13893
13930
  path: outcome.path,
13894
13931
  envelope: outcome.status === "killed" ? output : outcome.envelope,
13895
13932
  interrupted: status === "interrupted" ? true : undefined,
13896
- ...outcome.status === "interrupted" && outcome.pendingMessages ? { pendingMessages: outcome.pendingMessages } : {},
13933
+ ...outcome.status === "interrupted" && outcome.pendingMessages ? {
13934
+ pendingMessages: outcome.pendingMessages,
13935
+ ...outcome.pendingTaskExpands ? { pendingTaskExpands: true } : {}
13936
+ } : {},
13897
13937
  messages: [],
13898
13938
  usage: usageFromStats(outcome.stats),
13899
13939
  ...model === undefined ? {} : { model },
@@ -14007,6 +14047,7 @@ ${task}`);
14007
14047
  sessionManager: options.sessionManager,
14008
14048
  ...options.resumeSession ? { resumeSession: true } : {},
14009
14049
  ...options.resumeFollowUps?.length ? { resumeFollowUps: options.resumeFollowUps } : {},
14050
+ ...options.resumeTaskLiteral ? { resumeTaskLiteral: true } : {},
14010
14051
  ...options.onChildSession ? { onChildSession: options.onChildSession } : {},
14011
14052
  ...options.onChildLaunch ? { onChildLaunch: options.onChildLaunch } : {},
14012
14053
  ...options.progressPath ? { progressPath: options.progressPath } : {},
@@ -14131,6 +14172,8 @@ ${task}`);
14131
14172
  }
14132
14173
  persistRequestedOutput(recovered, options, outputSnapshot);
14133
14174
  options.onDetachedExit?.(recovered);
14175
+ }).catch((error) => {
14176
+ console.error("atomic-subagents: detached child exit handling failed", error);
14134
14177
  });
14135
14178
  const continuedModel = running.currentModel ?? candidate;
14136
14179
  const continuedThinking = running.currentThinking;
@@ -14869,15 +14912,23 @@ function resumedPrompts(record, resume) {
14869
14912
  resume?.continuation,
14870
14913
  resume?.message ? undefined : prompt
14871
14914
  ]);
14872
- const held = record.heldMessages && record.heldMessages.taskId === record.taskId ? record.heldMessages.messages.filter((message) => !continuations.has(message)) : [];
14915
+ const recorded = record.heldMessages && record.heldMessages.taskId === record.taskId ? record.heldMessages : undefined;
14916
+ const held = recorded ? recorded.messages.filter((message) => !continuations.has(message)) : [];
14917
+ const taskExpands = recorded !== undefined && heldTaskExpands(recorded) && held.length > 0 && held[0] === recorded.messages[0];
14873
14918
  const [task = prompt, ...followUps] = [...held, prompt, ...resume?.continuation ? [resume.continuation] : []];
14874
- return { task, followUps };
14919
+ return { task, followUps, taskLiteral: !taskExpands };
14875
14920
  }
14876
14921
  function freshPrompts(record, launch, resume) {
14877
- const held = record.heldMessages && record.heldMessages.taskId === record.taskId ? record.heldMessages.messages : [];
14878
- return { task: resume?.message ? `${launch.task}
14922
+ const recorded = record.heldMessages && record.heldMessages.taskId === record.taskId ? record.heldMessages : undefined;
14923
+ const held = recorded?.messages ?? [];
14924
+ const heldTask = recorded !== undefined && held[0] === launch.task && heldTaskExpands(recorded);
14925
+ return {
14926
+ task: resume?.message ? `${launch.task}
14879
14927
 
14880
- ${resume.message}` : launch.task, followUps: held };
14928
+ ${resume.message}` : launch.task,
14929
+ followUps: heldTask ? held.slice(1) : held,
14930
+ taskLiteral: false
14931
+ };
14881
14932
  }
14882
14933
  function rediscoverAgent(deps, cwd, name, source) {
14883
14934
  const scopes = source ? ["both", "user", "project"] : [resolveExecutionAgentScope(undefined)];
@@ -14901,7 +14952,7 @@ async function relaunchChild(deps, ctx, record, hold, release, onReopened, resum
14901
14952
  const host = ctx.getAgentTaskHost?.();
14902
14953
  const queued = record.taskId && host ? takeStoppedQueuedSubagentChild(record.taskId) : undefined;
14903
14954
  if (queued && host)
14904
- return startQueuedChild(deps, ctx, host, record, queued, hold, release, resume);
14955
+ return startedFresh(await startQueuedChild(deps, ctx, host, record, queued, hold, release, resume));
14905
14956
  const queuedOwnerless = record.taskId && !host ? takeStoppedQueuedOwnerlessChild(record.taskId) : undefined;
14906
14957
  const ownerSignal = host ? undefined : ctx.getCurrentSessionSignal?.() ?? ctx.sessionSignal;
14907
14958
  if (queuedOwnerless) {
@@ -14915,7 +14966,7 @@ async function relaunchChild(deps, ctx, record, hold, release, onReopened, resum
14915
14966
  ...resume?.message ? { message: resume.message } : {},
14916
14967
  ...supervisorAuthorization ? { supervisorAuthorization } : {}
14917
14968
  });
14918
- return ownerlessRelaunched(deps, record, launched, recordChild, release, hold, supervisorAuthorization, ctx);
14969
+ return startedFresh(await ownerlessRelaunched(deps, record, launched, recordChild, release, hold, supervisorAuthorization, ctx));
14919
14970
  }
14920
14971
  if (!existsSync15(record.cwd))
14921
14972
  return unrestorable(`its working directory no longer exists: ${record.cwd}`);
@@ -14970,7 +15021,7 @@ async function relaunchChild(deps, ctx, record, hold, release, onReopened, resum
14970
15021
  const prompts = fresh ? freshPrompts(record, fresh, resume) : resumedPrompts(record, resume);
14971
15022
  const options = {
14972
15023
  runningCapacity: deps.config.parallel?.runningCapacity,
14973
- getChildSessionOptions: ctx.getChildSessionOptions,
15024
+ getChildSessionOptions: ctx.getChildSessionOptionsResolver?.() ?? ctx.getChildSessionOptions,
14974
15025
  resourceLoaderInheritanceSnapshot: deps.pi.getResourceLoaderInheritanceSnapshot?.(),
14975
15026
  cwd: record.cwd,
14976
15027
  runId: record.runId,
@@ -14983,6 +15034,7 @@ async function relaunchChild(deps, ctx, record, hold, release, onReopened, resum
14983
15034
  ...fresh?.maxOutput ? { maxOutput: fresh.maxOutput } : {},
14984
15035
  ...fresh?.share === undefined ? {} : { share: fresh.share },
14985
15036
  ...prompts.followUps.length > 0 ? { resumeFollowUps: prompts.followUps } : {},
15037
+ ...prompts.taskLiteral ? { resumeTaskLiteral: true } : {},
14986
15038
  intercomEvents: deps.pi.events,
14987
15039
  parentDepth: getCurrentSubagentDepth(ctx),
14988
15040
  workflowStageSubagentGuard: isWorkflowStageOrchestrationContext(ctx),
@@ -15004,6 +15056,7 @@ async function relaunchChild(deps, ctx, record, hold, release, onReopened, resum
15004
15056
  const runtime = resolveSubagentExecutorRuntimeDeps(deps.runtime);
15005
15057
  const schedule = subagentChildSchedule(record.runId, record.index);
15006
15058
  if (!host) {
15059
+ const launchCwd = ctx.cwd;
15007
15060
  const launched = runOwnerlessChild({
15008
15061
  agent: agent.name,
15009
15062
  agentSource: agent.source,
@@ -15014,9 +15067,10 @@ async function relaunchChild(deps, ctx, record, hold, release, onReopened, resum
15014
15067
  ...ownerSignal ? { ownerSignal } : {},
15015
15068
  recordChild,
15016
15069
  ...schedule ? { schedule } : {},
15017
- runSync: (task, childOptions) => runtime.runSync(ctx.cwd, [agent], agent.name, task, childOptions)
15070
+ runSync: (task, childOptions) => runtime.runSync(launchCwd, [agent], agent.name, task, childOptions)
15018
15071
  });
15019
- return ownerlessRelaunched(deps, record, launched, recordChild, release, hold, supervisorAuthorization, ctx);
15072
+ const outcome = await ownerlessRelaunched(deps, record, launched, recordChild, release, hold, supervisorAuthorization, ctx);
15073
+ return fresh ? startedFresh(outcome) : outcome;
15020
15074
  }
15021
15075
  const response = await runAgentTask({
15022
15076
  host,
@@ -15037,7 +15091,11 @@ async function relaunchChild(deps, ctx, record, hold, release, onReopened, resum
15037
15091
  hold?.release();
15038
15092
  }
15039
15093
  });
15040
- return relaunched(deps, record, response, recordChild, release, supervisorAuthorization, intercomGroup);
15094
+ const outcome = await relaunched(deps, record, response, recordChild, release, supervisorAuthorization, intercomGroup);
15095
+ return fresh ? startedFresh(outcome) : outcome;
15096
+ }
15097
+ function startedFresh(outcome) {
15098
+ return outcome.kind === "restored" ? { ...outcome, fresh: true } : outcome;
15041
15099
  }
15042
15100
  async function startQueuedChild(deps, ctx, host, record, queued, hold, release, resume) {
15043
15101
  const supervisorAuthorization = queued.usesIntercom ? hold?.authorization : undefined;
@@ -15243,12 +15301,13 @@ function subagentChildRestoreNotices(outcomes) {
15243
15301
  `${childLabel(outcome.record, outcome.childId)} did not survive the restart and could not be restored: ${outcome.reason}. It is gone: do not send it Intercom messages or wait for its result. Launch a fresh subagent if its work is still needed.`
15244
15302
  ];
15245
15303
  const label = childLabel(outcome.record, outcome.record.childId);
15304
+ const how = outcome.fresh ? "started fresh with its task, since it had not started before the restart," : "relaunched from its own session";
15246
15305
  if (outcome.intercomLive === false)
15247
15306
  return [
15248
- `${label} was relaunched from its own session as task ${outcome.taskId}, but it did not reconnect to Intercom. Treat it as unreachable until it reconnects; its result will still arrive as a task completion.`
15307
+ `${label} was ${how} as task ${outcome.taskId}, but it did not reconnect to Intercom. Treat it as unreachable until it reconnects; its result will still arrive as a task completion.`
15249
15308
  ];
15250
15309
  return [
15251
- `${label} was interrupted by the restart and has been relaunched from its own session as task ${outcome.taskId}${outcome.intercomLive ? "; it is live on Intercom under the same name" : ""}.`
15310
+ `${label} was interrupted by the restart and has been ${how} as task ${outcome.taskId}${outcome.intercomLive ? "; it is live on Intercom under the same name" : ""}.`
15252
15311
  ];
15253
15312
  });
15254
15313
  }
@@ -16485,6 +16544,26 @@ class SubagentForkContextError extends Error {
16485
16544
  function resolveSubagentContext(value) {
16486
16545
  return value === "fork" ? "fork" : "fresh";
16487
16546
  }
16547
+ function settledForkLeaf(branch, leafId) {
16548
+ const answered = new Set;
16549
+ for (let index = branch.length - 1;index >= 0; index--) {
16550
+ const entry = branch[index];
16551
+ if (entry.type !== "message")
16552
+ continue;
16553
+ const message = entry.message;
16554
+ if (message.role === "toolResult") {
16555
+ answered.add(message.toolCallId);
16556
+ continue;
16557
+ }
16558
+ if (message.role === "user")
16559
+ return leafId;
16560
+ if (message.role !== "assistant")
16561
+ continue;
16562
+ const inFlight = message.content.some((block) => block.type === "toolCall" && !answered.has(block.id));
16563
+ return inFlight ? entry.parentId : leafId;
16564
+ }
16565
+ return leafId;
16566
+ }
16488
16567
  function createForkContextResolver(sessionManager, requestedContext) {
16489
16568
  if (resolveSubagentContext(requestedContext) !== "fork") {
16490
16569
  return { sessionManagerForIndex: async () => {
@@ -16494,7 +16573,10 @@ function createForkContextResolver(sessionManager, requestedContext) {
16494
16573
  const parentSessionId = sessionManager.getSessionId();
16495
16574
  const store = sessionManager.getSessionStore();
16496
16575
  const cwd = sessionManager.getCwd();
16497
- const leafId = sessionManager.getLeafId();
16576
+ const parentLeafId = sessionManager.getLeafId();
16577
+ if (parentLeafId === null)
16578
+ throw new SubagentForkContextError(parentSessionId, "no_leaf");
16579
+ const leafId = settledForkLeaf(sessionManager.getBranch(), parentLeafId);
16498
16580
  if (leafId === null)
16499
16581
  throw new SubagentForkContextError(parentSessionId, "no_leaf");
16500
16582
  const children = new Map;
@@ -16724,13 +16806,15 @@ async function routeSubagentModel(input) {
16724
16806
  signal: input.signal,
16725
16807
  selection: route.routerSelection
16726
16808
  });
16809
+ const { modelRegistry } = ctx;
16810
+ const launchProvider = ctx.model?.provider;
16727
16811
  return {
16728
16812
  ...route,
16729
16813
  allowsModel: fallbackRoute.allowsModel,
16730
16814
  allowsCandidate: (candidate, defaultEffort) => {
16731
- const normalized = resolveModelCandidate(candidate, ctx.modelRegistry.getAvailable().map(toModelInfo), ctx.model?.provider);
16815
+ const normalized = resolveModelCandidate(candidate, modelRegistry.getAvailable().map(toModelInfo), launchProvider);
16732
16816
  const { baseModel, thinkingSuffix } = splitKnownThinkingSuffix(normalized);
16733
- const model = ctx.modelRegistry.getAvailable().find((m) => `${m.provider}/${m.id}` === baseModel);
16817
+ const model = modelRegistry.getAvailable().find((m) => `${m.provider}/${m.id}` === baseModel);
16734
16818
  return model !== undefined && fallbackRoute.allowsModel(model, thinkingSuffix.slice(1) || defaultEffort);
16735
16819
  }
16736
16820
  };
@@ -16892,11 +16976,12 @@ async function runParallelPath(data, deps) {
16892
16976
  taskTexts[i] = wrapForkTask(taskTexts[i]);
16893
16977
  }
16894
16978
  }
16979
+ const detachedNotificationScope = { orchestrationContext: ctx.orchestrationContext };
16895
16980
  const results = await runForegroundParallelTasks({
16896
16981
  wait: params.wait,
16897
16982
  schedule: params[BURST_EXECUTION_SCHEDULE],
16898
16983
  onDetachedExit: (index, result) => {
16899
- if (workflowStageAcceptsDetachedNotification(ctx)) {
16984
+ if (workflowStageAcceptsDetachedNotification(detachedNotificationScope)) {
16900
16985
  notifyDetachedForegroundChildExit({
16901
16986
  pi: deps.pi,
16902
16987
  runId,
@@ -17173,12 +17258,13 @@ async function runSinglePath(data, deps) {
17173
17258
  };
17174
17259
  }
17175
17260
  const forwardSingleUpdate = createForwardSingleUpdate(onUpdate, foregroundControl, params.agent, 0);
17261
+ const detachedNotificationScope = { orchestrationContext: ctx.orchestrationContext };
17176
17262
  let r;
17177
17263
  try {
17178
17264
  const supervisorAuthorization = await requestSupervisorAuthorization(deps.pi.events, childIntercomTarget, ctx.getChildSessionOptions?.({ tools: agentConfig.tools }));
17179
17265
  const runOptions = {
17180
17266
  runningCapacity: deps.config.parallel?.runningCapacity,
17181
- getChildSessionOptions: ctx.getChildSessionOptions,
17267
+ getChildSessionOptions: ctx.getChildSessionOptionsResolver?.() ?? ctx.getChildSessionOptions,
17182
17268
  resourceLoaderInheritanceSnapshot: deps.pi.getResourceLoaderInheritanceSnapshot?.(),
17183
17269
  cwd: effectiveCwd,
17184
17270
  signal,
@@ -17214,7 +17300,7 @@ async function runSinglePath(data, deps) {
17214
17300
  },
17215
17301
  onDetachedExit: (result) => {
17216
17302
  cleanupTransientProgress(progressDir, artifactConfig.enabled);
17217
- if (result && workflowStageAcceptsDetachedNotification(ctx)) {
17303
+ if (result && workflowStageAcceptsDetachedNotification(detachedNotificationScope)) {
17218
17304
  notifyDetachedForegroundChildExit({ pi: deps.pi, runId, mode: "single", index: 0, result });
17219
17305
  }
17220
17306
  },
@@ -17259,6 +17345,7 @@ async function runSinglePath(data, deps) {
17259
17345
  return await taskToolResultWithOutput(response, ctx.getAgentTaskHost());
17260
17346
  r = settledChild;
17261
17347
  } else {
17348
+ const launchCwd = ctx.cwd;
17262
17349
  r = await runOwnerlessChild({
17263
17350
  agent: params.agent,
17264
17351
  agentSource: agentConfig.source,
@@ -17267,7 +17354,7 @@ async function runSinglePath(data, deps) {
17267
17354
  options: runOptions,
17268
17355
  recordChild: createSubagentChildRecordSink(deps.pi, ctx),
17269
17356
  ...params[BURST_EXECUTION_SCHEDULE] ? { schedule: params[BURST_EXECUTION_SCHEDULE] } : {},
17270
- runSync: (childTask, options) => deps.runtime.runSync(ctx.cwd, agents, params.agent, childTask, options)
17357
+ runSync: (childTask, options) => deps.runtime.runSync(launchCwd, agents, params.agent, childTask, options)
17271
17358
  }).result;
17272
17359
  }
17273
17360
  } catch (error) {
@@ -17604,7 +17691,16 @@ async function resumeChild(deps, ctx, request) {
17604
17691
  if (outcome.kind === "unrestorable")
17605
17692
  return errorResult(action, `${label(record)} could not be resumed: ${outcome.reason}.`);
17606
17693
  const intercom = outcome.intercomLive === true ? " It is live on Intercom under the same name." : outcome.intercomLive === false ? " It has not reconnected to Intercom yet; its result still arrives as a task completion." : "";
17607
- const held = record.heldMessages?.taskId === record.taskId ? " The messages it had queued and not yet read when it stopped are delivered first, once." : "";
17694
+ const heldMessages = record.heldMessages && record.heldMessages.taskId === record.taskId ? record.heldMessages.messages : [];
17695
+ if (outcome.fresh) {
17696
+ const sent = heldMessages[0] === record.launch?.task ? heldMessages.slice(1) : heldMessages;
17697
+ return controlResult(`Started ${label(record)} fresh with its task as task ${outcome.taskId}, since it stopped before it began${message ? ", with your message added to its task" : ""}.${sent.length > 0 ? " The messages it was sent and had not yet read are delivered after its task, once." : ""}${intercom}`, {
17698
+ action,
17699
+ children: [childDetails(record, "running", outcome.taskId)],
17700
+ ...message ? { message } : {}
17701
+ });
17702
+ }
17703
+ const held = heldMessages.length > 0 ? " The messages it had queued and not yet read when it stopped are delivered first, once." : "";
17608
17704
  return controlResult(`Resumed ${label(record)} from its own session as task ${outcome.taskId}${message ? interrupted ? ", with your message delivered before it continues its interrupted turn" : ", with your message as its new prompt" : ""}.${held}${intercom}`, {
17609
17705
  action,
17610
17706
  children: [childDetails(record, "running", outcome.taskId)],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.45-alpha.1",
3
+ "version": "0.9.46-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
6
6
  "contributors": [
@@ -6,6 +6,51 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.46-alpha.1] - 2026-10-27
10
+
11
+ ### Added
12
+
13
+ - `RunResult.failure` (type `RunFailureClassification`, exported from `@bastani/atomic/workflows`) describes how a failed or blocked run's failure was classified: `kind`, `code`, `retryable`, `resumable`, `recoverability`, `disposition` and `retryAfterMs`. It is absent for runs that did not fail and for results recorded by earlier releases ([#4031](https://github.com/bastani-inc/atomic/issues/4031))
14
+ - Opening a running stage that runs on a local worker (`workflows.localWorkers`) from the workflow graph or `/workflow attach` now shows its live chat: the transcript so far, the reply as it streams, and tool calls, with `on local worker` in the chat header. It no longer shows "Post-mortem chat is available only after the stage completes". Only stages you have open stream to the root, so pools with many workers send nothing extra for stages nobody watches. ([#3996](https://github.com/bastani-inc/atomic/issues/3996))
15
+ - Enter (steer) and Ctrl+F (follow-up) in the chat of a stage that runs on a local worker now reach that stage and report the same delivery as an in-process stage. Each message is admitted at most once, even when a reply is lost and the send is retried. The stage's graph node shows `✉ N queued` with no chat open. SDK runs with `localWorkers` now accept `stageControlRegistry`, whose handles for pooled stages steer, queue follow-ups and stream events the same way ([#3996](https://github.com/bastani-inc/atomic/issues/3996))
16
+ - A single stage on a local worker can now be paused and resumed (`/workflow pause <run> <stage>`, stage chat, `session.workflows.pause/resume` with `stageId`) without pausing the pool; it keeps its worker's lease while paused. When the worker running an open stage dies or its attempt is fenced, the chat shows `RECONNECTING`, reattaches to the replacement attempt, and redelivers once each, in send order and in their original mode (a steer still steers), the messages the replacement does not have ([#3996](https://github.com/bastani-inc/atomic/issues/3996))
17
+ - Children on local workers can now ask for input. Their `ctx.ui` prompts show on their stage and are answered from the stage chat or the workflow tool's `answer` action, and a stage's `ask_user_question` shows its form in the stage chat as for an in-process stage; each answer reaches only the prompt it names on the worker's current attempt. Previously `ctx.ui` in a pooled child failed the child. SDK runs with `localWorkers` now accept `ui`, which answers those prompts (a rejecting `ui` fails the child with its error), and `stageUiBroker`, which receives the stages' `ask_user_question` forms (a broker that rejects a form fails that question in the stage with its error). `ctx.ui.custom` in a pooled child fails it with an error naming the limitation. When a worker dies, the replacement asks again a `ctx.ui` prompt that was still pending, and does not ask again one already answered. A stage's `ask_user_question` still waiting when its worker dies (or when a durable run resumes a stage) shows its form again on the replacement, and the answer becomes that call's result; previously the call was reported to the model as interrupted and the stage could finish without the answer ([#3996](https://github.com/bastani-inc/atomic/issues/3996))
18
+ - Added a GPT-6.1 Terra reference to the prompt-engineering skill, based on OpenAI's updated GPT-6 guide, covering when to choose Terra, effort selection, migration from GPT-5.6 Terra, computer-use and writing prompts, and selective use of the shared templates observed on Astra.
19
+
20
+ ### Fixed
21
+
22
+ - Starting a large local worker pool (`workflows.localWorkers`) no longer freezes the root's input and rendering for hundreds of milliseconds while the workers start. When a workflow starts many pooled children at once, such as `Promise.all` over 150 `ctx.workflow` calls, their journal work used to run back to back in one turn of the event loop, and each child's dispatch listed and sorted every checkpoint of the root. While many journal operations are queued, journal work now gives the event loop a turn after 8 ms, checked before each operation, and a child's dispatch looks up only its own boundary. When those children finish at once, completing each one no longer reads every checkpoint of the root, and the sweep for undeliverable stage messages checks run ownership only for runs that have messages to settle, and children finishing together complete in turns of the event loop instead of all at once
23
+ - An Intercom send to a workflow path pattern that matches a live or pending stage, such as `workflow:<run>/**/work-2` for a stage in a child on a local worker, no longer reports `notInKnownSet` ("not in the workflow's known stage set"). The message was delivered, but the known set came only from the launch-time scan of the workflow source, which cannot see stages of children loaded with `loadWorkerWorkflow`. Stages that already exist and can receive messages now count as known; a target that matches neither the scan nor any such stage still gets the warning.
24
+ - Large pooled fan-outs no longer stall the root's main thread when the workflow graph or a stage chat is open: typing in a stage chat during dispatch, wave turnover and run end of 150+ local workers stayed responsive only with nothing attached. Each store change used to re-project, re-freeze, re-expand and re-serialize every run of the root for the graph snapshot, the graph view, the attach pane, the workflow activity bridge and the BACKGROUND widget. Unchanged runs and stages now keep their frozen graph projections and expansions, the graph view and attach pane catch up once before they render or handle input, an open stage chat projects only its own run, the activity bridge publishes each root's final activity once per turn of the event loop (reading published activity, a root run's own lifecycle event and `waitForSettled` always see it first), store-driven widget refreshes spend at most 8 ms per 50 ms after the first four, and the coordinator gets the root's boundaries once per turn instead of once per enqueue and published batch. A pooled child whose worker was replaced also keeps its last known stages, marked `reconnecting`, until the replacement republishes them, instead of briefly showing no stages ([#4009](https://github.com/bastani-inc/atomic/issues/4009))
25
+ - Stages on local workers (`workflows.localWorkers`) now share provider rate limits. When one worker's stage gets an HTTP 429 or an exhausted quota (HTTP 402), the other workers and the root apply the same cooldown or unavailable mark, with the same provider or model scope and the same `Retry-After` end. They hold requests to that provider or model back, or move their fallback walk past it, instead of each sending requests until it gets its own 429. A worker that starts later gets the limits still running. Limits are sent only when a request is rate limited, at most once per provider or model every 250 ms ([#4023](https://github.com/bastani-inc/atomic/issues/4023))
26
+ - Text typed into a stage chat reaches the stage the same way whether the stage is idle, streaming, or still preparing its turn. Only `/skill:` commands expand. Before, Enter or Ctrl+F while the stage was busy expanded prompt templates such as `/review` and ran registered extension commands, or refused them, while the same text sent to an idle stage arrived as typed
27
+ - A message given when resuming a stage that was paused in the middle of its workflow prompt is now delivered as typed, as it is when resuming an idle stage chat. It used to go through the stage prompt's own options, which expand prompt templates and run registered extension commands
28
+ - A failed child workflow (`ctx.workflow`), or a failed stage, no longer has its failure classified from workflow or stage names. Before, a name containing words such as `rate-limit`, `overloaded` or `quota` could turn an ordinary failure, such as a tool that throws `ENOENT`, into a resumable rate limit or provider outage. The root then stayed `running`, blocked for a resume that could not help, and `run()` returned status `running` with the error. A parent now classifies a failed child from the child's own classification (`RunResult.failure`), at any nesting depth, for in-process children and children on local workers, including a child result a resumed root reads back from the journal. A stage's model-resolution failure is classified from the routing error it wraps. A child that really fails on a rate limit still blocks the root resumably. Error messages are unchanged ([#4031](https://github.com/bastani-inc/atomic/issues/4031))
29
+ - A stage whose model hits an exhausted quota (HTTP 402) or a rate limit that resets beyond the retry-delay cap now moves straight to the next fallback model, without spending retries or cooldown-wait rounds on it. A model-specific rate limit no longer delays a fallback on the same provider, and an HTTP 529 overload waits for its `Retry-After` like a 503 ([#3984](https://github.com/bastani-inc/atomic/issues/3984))
30
+ - A stage whose whole model and fallback chain returns HTTP 429 now waits for the earliest recorded provider cooldown and retries, instead of failing after one attempt per model. The wait is bounded: at most `retry.maxRetries` extra rounds per prompt, and only when the earliest cooldown fits the retry-delay cap (`retry.provider.maxRetryDelayMs`, else `retry.maxAgentDelayMs`, else 60 s). A persistent 429 still fails the stage once those rounds are spent ([#3984](https://github.com/bastani-inc/atomic/issues/3984))
31
+ - A workflow run whose stage replaced its session through model fallback no longer fails with `This extension ctx is stale after session replacement` and hides the stage's real error (such as a rate limit). An unhandled stale extension-context rejection no longer fails the run. It is logged to stderr with its stack and the run id, so the code that kept the retired session's context can be found ([#3981](https://github.com/bastani-inc/atomic/issues/3981))
32
+ - Disposing an SDK session right after a workflow run reports `completed`, without awaiting `session.workflows.waitForSettled(runId)`, no longer prints one `atomic-workflows: released stage listener failed Error: SessionClosed` warning per stage ([#3966](https://github.com/bastani-inc/atomic/issues/3966))
33
+ - A workflow stage that has sent its first prompt no longer reads `startup first-dispatch (0s total, 0s on current step; dispatched)` in the workflow tool's `stages` and `stage` output, which looked like a stalled stage for the whole run. The line now reads `startup complete (dispatched after 2s)` ([#3967](https://github.com/bastani-inc/atomic/issues/3967))
34
+ - A local-worker pool no longer fails every in-flight child with `Local worker process cleanup unconfirmed; permits quarantined` when one worker exits while its tool process groups are still running. The coordinator now reaps that worker's orphaned process groups (SIGTERM, then SIGKILL, with a bounded wait), holds the child's work lease so no other worker can claim it meanwhile, releases the permits of groups that are gone or hold only zombies, and replays only that worker's child on a replacement. If a group is still alive when the bound expires, only that child fails, with a retryable `WorkflowInfrastructureError` (`worker_cleanup_unconfirmed`) that names the process groups; their permits stay quarantined and the child is not replayed alongside them, also not by recovery after a stalled worker is stopped or by a durable resume (both read the quarantine from the journal), and closing the pool reports those groups. A durable resume reaps such an attempt's groups with the same bounded sequence, then replays it in the resumed root or fails only that child ([#3986](https://github.com/bastani-inc/atomic/issues/3986))
35
+ - A direct Intercom send to a live stage in a local worker pool no longer routinely fails with `The recipient did not confirm that it saved the message in time` while the pool is busy. Saving a message to a pooled stage's session no longer waits for the worker to re-read the root workflow's journal or for a usage-total transaction; it is confirmed once the coordinator has committed it ([#3983](https://github.com/bastani-inc/atomic/issues/3983))
36
+ - Resuming a quit or crashed root whose children run in `workflows.localWorkers` no longer fails in-flight children with `Session not found`. Resume used to make the previous run's pooled children claimable before the resumed root had enqueued them again, so workers crashed on `Stale work lease`, the pool failed with `Local worker retry limit`, and later children ran in the root process, which cannot see pooled stage sessions. A pooled child is now claimed only after the resumed root enqueues it, a child started after the pool has failed gets the pool's error instead of running in the root process, the three-attempt retry limit counts only crashes within one run of the root (a child resumed many times keeps its full budget), and a worker whose attempt was handed to another worker ends that attempt and serves new work instead of crashing and using up a retry ([#3979](https://github.com/bastani-inc/atomic/issues/3979))
37
+ - Large local worker pools no longer lose child workflows to `database is locked … gave up after waiting 30000 ms for the write lock` after a few hours. Resource admission reconciliation, which every pooled worker runs before it starts a command, read the whole resource permit history twice for every live worker while it held the journal's write lock. With 160 workers and tens of thousands of permits, each pass held the lock for nearly a second. Reconciliation now uses indexes, keeps only the most recent 256 released permits, and takes the write lock only when it has a permit to fence or prune ([#3978](https://github.com/bastani-inc/atomic/issues/3978))
38
+ - Workflow runs and local worker definitions load again after something such as a `/tmp` cleanup deletes the `jiti` transpile cache directory, instead of failing with `ENOENT: no such file or directory, open '/tmp/jiti/workflows-<name>.<hash>.cjs'` until Atomic restarts ([#3985](https://github.com/bastani-inc/atomic/issues/3985))
39
+ - A sticky Intercom send to a workflow pattern target (for example `workflow:<rootRunId>/**`) no longer fails newly starting pooled stages with `Extension startup failed: … pending-stage message <id> changed during delivery`. A stage drain whose transcript snapshot was taken before a slow session append finished used to deliver the already-receipted message again and then fail. The drain now treats an existing delivery receipt for that stage and session as success, so the message reaches each stage exactly once ([#3980](https://github.com/bastani-inc/atomic/issues/3980))
40
+ - A workflow that starts many child workflows at once, such as `Promise.all` over 150 `ctx.workflow` calls, no longer stalls the root session's input and rendering while the children start. Each workflow store change used to re-read every run and stage to detect lifecycle transitions, so a fan-out's cost grew with the square of its width; only the run, stage or tool node that changed is now re-read. Lifecycle events are unchanged
41
+ - A finished stage that ran on a local worker now reopens for post-mortem chat. It used to report `The retained session is missing, unreadable, or invalid` in the same session and SESSION UNAVAILABLE after a restart, because its session lives in the workflow journal rather than the host's session store ([#3996](https://github.com/bastani-inc/atomic/issues/3996))
42
+ - A workflow heartbeat no longer goes missing when its wake-up timer fires a moment early. The early wake-up found nothing due and rescheduled, and if the boundary passed while that pass was running, the heartbeat moved to the following interval instead of being sent. The scheduler now reads the clock once per pass, so a boundary that comes due during it is still delivered
43
+ - An idle local worker that meets its root's released lease (between the root's teardown and the pool's close) now ends cleanly instead of crashing, and the pool no longer spawns replacements that crash the same way ([#3979](https://github.com/bastani-inc/atomic/issues/3979))
44
+ - A stage chat open on a `ctx.ui` prompt or `ask_user_question` form of a stage on a local worker (`workflows.localWorkers`) now takes the answer after that worker dies: it shows the prompt the replacement worker asks again and delivers your answer to it once. Before, the open chat kept a prompt that dropped typed text and ignored Enter until you closed and reopened the stage. An answer you submit after the worker died but before the replacement asks again is now kept and given to the replacement's identical prompt instead of being lost, so you are not asked twice. The re-asked `ask_user_question` form no longer shows "Tool execution was interrupted after it started…" above it. A replacement worker whose child asks a different `ctx.ui` question at the same point (its text depends on data) no longer fails the child with `Pool topology scope: direct child source orders are not unique`: the unanswered question of the dead worker is replaced, an open stage chat moves to the new question, and an answer held for the old question is discarded. A child that really takes different steps when it runs again now fails with an error that says so in plain words. The forms of `ask_user_question` and the readiness gate are now built by one helper for in-process and local-worker stages, so they cannot drift apart ([#4022](https://github.com/bastani-inc/atomic/issues/4022))
45
+ - A child on a local worker that loses its worker three times now fails alone with a retryable `WorkflowInfrastructureError` (`worker_retry_limit`) that names its child run and definition export, as #3986 already did for a child whose processes survive cleanup. Before, it failed the whole pool within milliseconds: every other child, including one waiting on a prompt whose worker was never touched, was rejected with `Local worker retry limit`. The same applies to a child whose every attempt the root refused as stale. The failed child, its nested runs and their unfinished stages show failed with its error, so the graph counts none of them as active. Workers that keep crashing without work, worker startup failures and a coordinator that dies still fail the pool. When a pool closes without a quit, the root's unfinished work rows end `failed` instead of staying `ready` or `leased`; quarantined attempts keep their leases, and a resumed root replays failed work it enqueues again ([#3996](https://github.com/bastani-inc/atomic/issues/3996))
46
+ - The post-mortem chat of a completed or failed stage that ran on a local worker no longer shows `on local worker` in its header. Post-mortem chat runs in the root, so the header now matches an in-process stage's ([#3996](https://github.com/bastani-inc/atomic/issues/3996))
47
+
48
+ ## [0.9.45] - 2026-10-25
49
+
50
+ ### Fixed
51
+
52
+ - A large local worker pool no longer fails at startup with `Local worker startup timed out after 30000 ms` on a small or busy machine. When `localWorkers.startupTimeoutMs` is not set, the readiness deadline now scales with the number of workers and the 1-minute load average per CPU core, from 30000 ms up to 600000 ms; on an idle machine with spare cores it stays 30000 ms. If workers still miss the deadline, the error now says they were too slow to start and names `workflows.localWorkers.startupTimeoutMs` ([#3962](https://github.com/bastani-inc/atomic/issues/3962))
53
+
9
54
  ## [0.9.45-alpha.1] - 2026-10-25
10
55
 
11
56
  ### Added
@@ -39,7 +39,7 @@ import {
39
39
  reviewerFailureText,
40
40
  summarizeReviewConvergence,
41
41
  reverify_consolidated_batch
42
- } from "./chunk-jvna28mq.js";
42
+ } from "./chunk-zhyqnf5g.js";
43
43
  import {
44
44
  fold_usage
45
45
  } from "./chunk-tgt0s5e5.js";