@bastani/atomic 0.9.13-alpha.2 → 0.9.13-alpha.3

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 (255) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/builtin/i-have-adhd/package.json +1 -1
  4. package/dist/builtin/intercom/CHANGELOG.md +14 -0
  5. package/dist/builtin/intercom/README.md +12 -12
  6. package/dist/builtin/intercom/incoming-message-delivery.ts +1 -1
  7. package/dist/builtin/intercom/index.ts +1 -1
  8. package/dist/builtin/intercom/intercom-tool.ts +1 -1
  9. package/dist/builtin/intercom/intercom-utils.ts +3 -6
  10. package/dist/builtin/intercom/package.json +1 -1
  11. package/dist/builtin/intercom/result-renderers.ts +1 -1
  12. package/dist/builtin/intercom/session-target.ts +1 -15
  13. package/dist/builtin/intercom/skills/intercom/SKILL.md +6 -6
  14. package/dist/builtin/intercom/terminal-ordering-barrier.ts +2 -2
  15. package/dist/builtin/intercom/ui/inline-message.ts +7 -5
  16. package/dist/builtin/intercom/ui/session-list.ts +23 -10
  17. package/dist/builtin/mcp/package.json +1 -1
  18. package/dist/builtin/subagents/CHANGELOG.md +19 -0
  19. package/dist/builtin/subagents/README.md +29 -73
  20. package/dist/builtin/subagents/package.json +2 -2
  21. package/dist/builtin/subagents/prompts/parallel-cleanup.md +1 -1
  22. package/dist/builtin/subagents/prompts/parallel-review.md +1 -1
  23. package/dist/builtin/subagents/prompts/review-loop.md +2 -2
  24. package/dist/builtin/subagents/skills/subagent/SKILL.md +23 -59
  25. package/dist/builtin/subagents/skills/tmux/SKILL.md +1 -1
  26. package/dist/builtin/subagents/src/extension/control-notices.ts +2 -3
  27. package/dist/builtin/subagents/src/extension/doctor.ts +2 -14
  28. package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -17
  29. package/dist/builtin/subagents/src/extension/index.ts +9 -70
  30. package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
  31. package/dist/builtin/subagents/src/extension/prompt-guidance.ts +2 -2
  32. package/dist/builtin/subagents/src/extension/schemas.ts +2 -8
  33. package/dist/builtin/subagents/src/extension/tool-description.ts +2 -3
  34. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
  35. package/dist/builtin/subagents/src/intercom/result-intercom.ts +5 -46
  36. package/dist/builtin/subagents/src/runs/{background → foreground}/completion-notification.ts +2 -2
  37. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +25 -88
  38. package/dist/builtin/subagents/src/runs/{background → foreground}/notify.ts +5 -5
  39. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +11 -28
  40. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +3 -0
  41. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +2 -0
  42. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +0 -5
  43. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +30 -20
  44. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +4 -4
  45. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +0 -13
  46. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +104 -48
  47. package/dist/builtin/subagents/src/runs/inprocess/index.ts +1 -1
  48. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +5 -5
  49. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-api.ts +0 -7
  50. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-core.ts +0 -10
  51. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-projection.ts +3 -108
  52. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-sanitize.ts +0 -4
  53. package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +1 -1
  54. package/dist/builtin/subagents/src/shared/formatters.ts +3 -5
  55. package/dist/builtin/subagents/src/shared/model-resolution.ts +78 -0
  56. package/dist/builtin/subagents/src/shared/status-format.ts +4 -11
  57. package/dist/builtin/subagents/src/shared/types-config.ts +10 -9
  58. package/dist/builtin/subagents/src/shared/types-nested.ts +127 -0
  59. package/dist/builtin/subagents/src/shared/types-results.ts +2 -8
  60. package/dist/builtin/subagents/src/shared/types-runtime.ts +0 -9
  61. package/dist/builtin/subagents/src/shared/types.ts +1 -1
  62. package/dist/builtin/subagents/src/shared/utils.ts +1 -109
  63. package/dist/builtin/subagents/src/slash/slash-commands.ts +7 -17
  64. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +2 -252
  65. package/dist/builtin/subagents/src/tui/render-layout.ts +4 -4
  66. package/dist/builtin/subagents/src/tui/render-result.ts +0 -9
  67. package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -34
  68. package/dist/builtin/subagents/src/tui/render.ts +0 -2
  69. package/dist/builtin/web-access/package.json +1 -1
  70. package/dist/builtin/workflows/CHANGELOG.md +12 -0
  71. package/dist/builtin/workflows/README.md +3 -3
  72. package/dist/builtin/workflows/package.json +1 -1
  73. package/dist/builtin/workflows/src/durable/backend.ts +11 -0
  74. package/dist/builtin/workflows/src/durable/completed-catalog-stage-groups.ts +63 -25
  75. package/dist/builtin/workflows/src/durable/completed-catalog.ts +166 -25
  76. package/dist/builtin/workflows/src/durable/dbos-backend.ts +42 -24
  77. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +2 -1
  78. package/dist/builtin/workflows/src/durable/run-timing.ts +28 -7
  79. package/dist/builtin/workflows/src/durable/scoped-backend.ts +4 -0
  80. package/dist/builtin/workflows/src/durable/targeted-inspection.ts +110 -0
  81. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +2 -2
  82. package/dist/builtin/workflows/src/engine/runtime.ts +19 -2
  83. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +3 -0
  84. package/dist/builtin/workflows/src/extension/index.bundle.mjs +680 -152
  85. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +6 -0
  86. package/dist/builtin/workflows/src/extension/ui-surface.ts +2 -0
  87. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +6 -1
  88. package/dist/builtin/workflows/src/extension/workflow-targets.ts +18 -10
  89. package/dist/builtin/workflows/src/extension/workflow-tool-inspection.ts +31 -16
  90. package/dist/builtin/workflows/src/extension/workflow-tool-send.ts +75 -3
  91. package/dist/builtin/workflows/src/extension/workflow-tool.ts +53 -6
  92. package/dist/builtin/workflows/src/runs/background/run-inspect.ts +5 -1
  93. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +94 -60
  94. package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +1 -1
  95. package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +7 -7
  96. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +25 -4
  97. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +4 -1
  98. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +2 -0
  99. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +142 -4
  100. package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +2 -0
  101. package/dist/builtin/workflows/src/runs/foreground/stage-session-heartbeat.ts +88 -0
  102. package/dist/builtin/workflows/src/tui/run-detail.ts +25 -12
  103. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +1 -0
  104. package/dist/core/agent-session-events.d.ts.map +1 -1
  105. package/dist/core/agent-session-events.js +16 -5
  106. package/dist/core/agent-session-events.js.map +1 -1
  107. package/dist/core/agent-session-export.d.ts.map +1 -1
  108. package/dist/core/agent-session-export.js +2 -1
  109. package/dist/core/agent-session-export.js.map +1 -1
  110. package/dist/core/agent-session-methods.d.ts +8 -4
  111. package/dist/core/agent-session-methods.d.ts.map +1 -1
  112. package/dist/core/agent-session-methods.js.map +1 -1
  113. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  114. package/dist/core/agent-session-prompt.js +5 -0
  115. package/dist/core/agent-session-prompt.js.map +1 -1
  116. package/dist/core/agent-session-summary.d.ts +21 -0
  117. package/dist/core/agent-session-summary.d.ts.map +1 -0
  118. package/dist/core/agent-session-summary.js +156 -0
  119. package/dist/core/agent-session-summary.js.map +1 -0
  120. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  121. package/dist/core/agent-session-tool-registry.js +0 -4
  122. package/dist/core/agent-session-tool-registry.js.map +1 -1
  123. package/dist/core/agent-session-transfer.d.ts.map +1 -1
  124. package/dist/core/agent-session-transfer.js +0 -2
  125. package/dist/core/agent-session-transfer.js.map +1 -1
  126. package/dist/core/agent-session-tree.d.ts.map +1 -1
  127. package/dist/core/agent-session-tree.js +6 -0
  128. package/dist/core/agent-session-tree.js.map +1 -1
  129. package/dist/core/agent-session.d.ts +7 -3
  130. package/dist/core/agent-session.d.ts.map +1 -1
  131. package/dist/core/agent-session.js +8 -5
  132. package/dist/core/agent-session.js.map +1 -1
  133. package/dist/core/atomic-guide-command.js +2 -2
  134. package/dist/core/atomic-guide-command.js.map +1 -1
  135. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  136. package/dist/core/compaction/branch-summarization.js +1 -0
  137. package/dist/core/compaction/branch-summarization.js.map +1 -1
  138. package/dist/core/compaction/index.d.ts +1 -0
  139. package/dist/core/compaction/index.d.ts.map +1 -1
  140. package/dist/core/compaction/index.js +1 -0
  141. package/dist/core/compaction/index.js.map +1 -1
  142. package/dist/core/compaction/session-summarization.d.ts +41 -0
  143. package/dist/core/compaction/session-summarization.d.ts.map +1 -0
  144. package/dist/core/compaction/session-summarization.js +97 -0
  145. package/dist/core/compaction/session-summarization.js.map +1 -0
  146. package/dist/core/extensions/ui-types.d.ts +2 -0
  147. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  148. package/dist/core/extensions/ui-types.js.map +1 -1
  149. package/dist/core/session-manager-core.d.ts +3 -1
  150. package/dist/core/session-manager-core.d.ts.map +1 -1
  151. package/dist/core/session-manager-core.js +9 -1
  152. package/dist/core/session-manager-core.js.map +1 -1
  153. package/dist/core/session-manager-entries.d.ts +24 -1
  154. package/dist/core/session-manager-entries.d.ts.map +1 -1
  155. package/dist/core/session-manager-entries.js +48 -0
  156. package/dist/core/session-manager-entries.js.map +1 -1
  157. package/dist/core/session-manager-list.d.ts.map +1 -1
  158. package/dist/core/session-manager-list.js +10 -0
  159. package/dist/core/session-manager-list.js.map +1 -1
  160. package/dist/core/session-manager-types.d.ts +10 -1
  161. package/dist/core/session-manager-types.d.ts.map +1 -1
  162. package/dist/core/session-manager-types.js.map +1 -1
  163. package/dist/core/settings-manager-basic-accessors.d.ts +3 -0
  164. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  165. package/dist/core/settings-manager-basic-accessors.js +5 -0
  166. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  167. package/dist/core/settings-manager.d.ts +1 -1
  168. package/dist/core/settings-manager.d.ts.map +1 -1
  169. package/dist/core/settings-manager.js.map +1 -1
  170. package/dist/core/settings-types.d.ts +4 -0
  171. package/dist/core/settings-types.d.ts.map +1 -1
  172. package/dist/core/settings-types.js.map +1 -1
  173. package/dist/core/slash-commands.js +2 -2
  174. package/dist/core/slash-commands.js.map +1 -1
  175. package/dist/core/tools/bash.d.ts +0 -15
  176. package/dist/core/tools/bash.d.ts.map +1 -1
  177. package/dist/core/tools/bash.js +3 -81
  178. package/dist/core/tools/bash.js.map +1 -1
  179. package/dist/core/tools/fetch-url.d.ts +12 -0
  180. package/dist/core/tools/fetch-url.d.ts.map +1 -1
  181. package/dist/core/tools/fetch-url.js +76 -10
  182. package/dist/core/tools/fetch-url.js.map +1 -1
  183. package/dist/core/tools/index.js +4 -4
  184. package/dist/core/tools/index.js.map +1 -1
  185. package/dist/core/tools/session-temp-dir.d.ts +2 -2
  186. package/dist/core/tools/session-temp-dir.d.ts.map +1 -1
  187. package/dist/core/tools/session-temp-dir.js +6 -7
  188. package/dist/core/tools/session-temp-dir.js.map +1 -1
  189. package/dist/core/usage-totals.js +1 -1
  190. package/dist/core/usage-totals.js.map +1 -1
  191. package/dist/modes/interactive/components/footer.js +1 -1
  192. package/dist/modes/interactive/components/footer.js.map +1 -1
  193. package/dist/modes/interactive/components/host-session-picker.d.ts.map +1 -1
  194. package/dist/modes/interactive/components/host-session-picker.js +1 -0
  195. package/dist/modes/interactive/components/host-session-picker.js.map +1 -1
  196. package/dist/modes/interactive/components/session-selector-list.d.ts.map +1 -1
  197. package/dist/modes/interactive/components/session-selector-list.js +29 -4
  198. package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
  199. package/dist/modes/interactive/components/session-selector-search.js +1 -1
  200. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  201. package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/tree-selector-content.js +6 -0
  203. package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
  204. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  205. package/dist/modes/interactive-engine/protocol.js +4 -1
  206. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  207. package/docs/environment-variables.md +1 -1
  208. package/docs/intercom.md +20 -21
  209. package/docs/quickstart.md +5 -1
  210. package/docs/sdk.md +1 -1
  211. package/docs/session-format.md +15 -0
  212. package/docs/sessions.md +8 -0
  213. package/docs/settings.md +6 -0
  214. package/docs/subagents.md +19 -31
  215. package/docs/tools.md +3 -5
  216. package/docs/usage.md +1 -1
  217. package/docs/windows.md +1 -1
  218. package/docs/workflows.md +60 -32
  219. package/npm-shrinkwrap.json +29 -29
  220. package/package.json +2 -2
  221. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +0 -331
  222. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +0 -161
  223. package/dist/builtin/subagents/src/runs/inprocess/background-single.ts +0 -195
  224. package/dist/builtin/subagents/src/runs/inprocess/background.ts +0 -90
  225. package/dist/builtin/subagents/src/shared/types-async.ts +0 -247
  226. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +0 -217
  227. package/dist/builtin/subagents/src/tui/render-widget.ts +0 -366
  228. package/dist/core/async/format.d.ts +0 -3
  229. package/dist/core/async/format.d.ts.map +0 -1
  230. package/dist/core/async/format.js +0 -52
  231. package/dist/core/async/format.js.map +0 -1
  232. package/dist/core/async/job-manager.d.ts +0 -40
  233. package/dist/core/async/job-manager.d.ts.map +0 -1
  234. package/dist/core/async/job-manager.js +0 -293
  235. package/dist/core/async/job-manager.js.map +0 -1
  236. package/dist/core/async/session-manager.d.ts +0 -16
  237. package/dist/core/async/session-manager.d.ts.map +0 -1
  238. package/dist/core/async/session-manager.js +0 -34
  239. package/dist/core/async/session-manager.js.map +0 -1
  240. package/dist/core/async/types.d.ts +0 -23
  241. package/dist/core/async/types.d.ts.map +0 -1
  242. package/dist/core/async/types.js +0 -2
  243. package/dist/core/async/types.js.map +0 -1
  244. package/dist/core/tools/bash-async-execution.d.ts +0 -27
  245. package/dist/core/tools/bash-async-execution.d.ts.map +0 -1
  246. package/dist/core/tools/bash-async-execution.js +0 -81
  247. package/dist/core/tools/bash-async-execution.js.map +0 -1
  248. package/dist/core/tools/bash-async-jobs.d.ts +0 -30
  249. package/dist/core/tools/bash-async-jobs.d.ts.map +0 -1
  250. package/dist/core/tools/bash-async-jobs.js +0 -87
  251. package/dist/core/tools/bash-async-jobs.js.map +0 -1
  252. package/dist/core/tools/bash-async-output.d.ts +0 -13
  253. package/dist/core/tools/bash-async-output.d.ts.map +0 -1
  254. package/dist/core/tools/bash-async-output.js +0 -122
  255. package/dist/core/tools/bash-async-output.js.map +0 -1
@@ -13,6 +13,7 @@ import {
13
13
  type ResumeDurableResult,
14
14
  resumeDurableWorkflow as resumeDurableWorkflowAdapter,
15
15
  } from "../durable/resume-runtime.js";
16
+ import { inspectTargetedDurableWorkflow, type TargetedDurableInspection } from "../durable/targeted-inspection.js";
16
17
  import type { ResumableWorkflowEntry } from "../durable/types.js";
17
18
  import type { JobTracker } from "../runs/background/job-tracker.js";
18
19
  import type { RunOpts } from "../runs/foreground/executor.js";
@@ -28,6 +29,7 @@ export interface DurableResumeRuntime {
28
29
  workflowId: string,
29
30
  options?: { readonly policy?: WorkflowExecutionPolicy; readonly actor?: WorkflowActor },
30
31
  ): Promise<ResumeDurableResult>;
32
+ inspectDurableWorkflow(workflowId: string): Promise<TargetedDurableInspection>;
31
33
  listDurableResumable(): readonly ResumableWorkflowEntry[];
32
34
  prepareDurableResumable(workflowId?: string): Promise<readonly ResumableWorkflowEntry[]>;
33
35
  prepareDurableCatalog?(): Promise<DurableWorkflowCatalogEntries>;
@@ -65,6 +67,10 @@ export function createDurableResumeRuntime(deps: DurableResumeRuntimeDeps): Dura
65
67
  };
66
68
  let preparedCatalog: readonly ResumableWorkflowEntry[] = [];
67
69
  return {
70
+ async inspectDurableWorkflow(workflowId) {
71
+ await deps.ensureReady();
72
+ return await inspectTargetedDurableWorkflow(getDurableBackend(), workflowId);
73
+ },
68
74
  async resumeDurableWorkflow(workflowId, options): Promise<ResumeDurableResult> {
69
75
  await deps.ensureReady();
70
76
  const backend = getDurableBackend();
@@ -209,6 +209,8 @@ export interface PiHostSessionPickerRow {
209
209
  modifiedAt: number;
210
210
  messageCount: number;
211
211
  firstMessage: string;
212
+ /** Generated resume summary. Absent when never generated, or stale against the latest message. */
213
+ summary?: string;
212
214
  allMessagesText?: string;
213
215
  name?: string;
214
216
  /** Optional semantic color for synthetic selector rows. */
@@ -153,7 +153,12 @@ async function workflowSlashHandler(
153
153
  if (target && !target.startsWith("--")) {
154
154
  const resolved = resolveRunId(target);
155
155
  if (resolved.kind === "malformed") return fail(resolved.message);
156
- if (resolved.kind === "not_found") return fail(`Run not found: ${target}`);
156
+ if (resolved.kind === "not_found") {
157
+ const durable = await deps.runtimeForContext(ctx).inspectDurableWorkflow(target);
158
+ if (durable.kind !== "found") return fail(durable.message);
159
+ emitChatSurface(pi, { kind: "detail", detail: durable.detail });
160
+ return;
161
+ }
157
162
  const inspected = inspectRun(resolved.runId);
158
163
  if (!inspected.ok) return fail(`Run not found: ${target}`);
159
164
  emitChatSurface(pi, { kind: "detail", detail: inspected.detail });
@@ -7,7 +7,7 @@ import {
7
7
  } from "../shared/expanded-workflow-graph.js";
8
8
  import { isFullRunId, malformedRunIdMessage, RUN_ID_LENGTH } from "../shared/run-id.js";
9
9
  import { topLevelWorkflowRuns } from "../shared/run-visibility.js";
10
- import { store } from "../shared/store.js";
10
+ import { type Store, store } from "../shared/store.js";
11
11
  import { readGraphStoreSnapshot } from "../shared/store-observation.js";
12
12
  import type { RunStatus } from "../shared/store-types.js";
13
13
  import type { OverlayPiSurface } from "../tui/overlay-adapter.js";
@@ -87,9 +87,9 @@ export type RunIdResolution =
87
87
  | { kind: "malformed"; message: string }
88
88
  | { kind: "not_found" };
89
89
 
90
- export function resolveRunId(target: string): RunIdResolution {
90
+ export function resolveRunId(target: string, activeStore: Store = store): RunIdResolution {
91
91
  if (!isFullRunId(target)) return { kind: "malformed", message: malformedRunIdMessage(target) };
92
- const exact = store.runs().find((r) => r.id === target);
92
+ const exact = activeStore.runs().find((r) => r.id === target);
93
93
  if (exact) return { kind: "exact", runId: exact.id };
94
94
  return { kind: "not_found" };
95
95
  }
@@ -100,12 +100,16 @@ export type ToolRunTarget =
100
100
  | { kind: "malformed"; target: string; message: string }
101
101
  | { kind: "not_found"; target: string; message: string };
102
102
 
103
- export function resolveToolRunTarget(args: WorkflowToolArgs, emptyMessage: string): ToolRunTarget {
103
+ export function resolveToolRunTarget(
104
+ args: WorkflowToolArgs,
105
+ emptyMessage: string,
106
+ activeStore: Store = store,
107
+ ): ToolRunTarget {
104
108
  const rawTarget = args.runId?.trim() ?? "";
105
109
  if (args.all === true || rawTarget === "--all") return { kind: "all" };
106
- const target = rawTarget || store.activeRunId() || "";
110
+ const target = rawTarget || activeStore.activeRunId() || "";
107
111
  if (!target) return { kind: "not_found", target: rawTarget, message: emptyMessage };
108
- const resolved = resolveRunId(target);
112
+ const resolved = resolveRunId(target, activeStore);
109
113
  if (resolved.kind === "exact") return { kind: "run", runId: resolved.runId };
110
114
  if (resolved.kind === "malformed") return { kind: "malformed", target, message: resolved.message };
111
115
  return { kind: "not_found", target, message: `Run not found: ${target}` };
@@ -113,10 +117,10 @@ export function resolveToolRunTarget(args: WorkflowToolArgs, emptyMessage: strin
113
117
 
114
118
  export type ToolStageTarget = { ok: true; runId?: string; stageId?: string } | { ok: false; message: string };
115
119
 
116
- export function resolveStageTarget(runId: string, stageTarget?: string): ToolStageTarget {
120
+ export function resolveStageTarget(runId: string, stageTarget?: string, activeStore: Store = store): ToolStageTarget {
117
121
  const target = stageTarget?.trim();
118
122
  if (!target) return { ok: true, runId };
119
- const graph = expandWorkflowGraph(readGraphStoreSnapshot(store), runId);
123
+ const graph = expandWorkflowGraph(readGraphStoreSnapshot(activeStore), runId);
120
124
  const exactVirtualIds = graph.stages.filter((stage) => stage.id === target);
121
125
  if (exactVirtualIds.length === 1) return resolvedStageTarget(exactVirtualIds[0]!);
122
126
  if (exactVirtualIds.length > 1) return ambiguousStageTarget(target, exactVirtualIds);
@@ -147,8 +151,12 @@ function ambiguousStageTarget(target: string, stages: readonly ExpandedWorkflowS
147
151
  };
148
152
  }
149
153
 
150
- export function resolveToolStageTarget(runId: string, stageTarget?: string): ToolStageTarget {
151
- return resolveStageTarget(runId, stageTarget);
154
+ export function resolveToolStageTarget(
155
+ runId: string,
156
+ stageTarget?: string,
157
+ activeStore: Store = store,
158
+ ): ToolStageTarget {
159
+ return resolveStageTarget(runId, stageTarget, activeStore);
152
160
  }
153
161
 
154
162
  /**
@@ -1,5 +1,7 @@
1
1
  import { stageControlRegistry } from "../runs/foreground/stage-control-registry.js";
2
- import { store } from "../shared/store.js";
2
+ import { expandWorkflowGraph } from "../shared/expanded-workflow-graph.js";
3
+ import { type Store, store } from "../shared/store.js";
4
+ import { readGraphStoreSnapshot } from "../shared/store-observation.js";
3
5
  import type { WorkflowToolArgs } from "./public-types.js";
4
6
  import type { WorkflowToolResult } from "./render-result.js";
5
7
  import {
@@ -14,8 +16,14 @@ import {
14
16
  } from "./workflow-stage-results.js";
15
17
  import { resolveToolRunTarget, resolveToolStageTarget } from "./workflow-targets.js";
16
18
 
17
- export function workflowStagesResult(args: WorkflowToolArgs): WorkflowToolResult {
18
- const target = resolveToolRunTarget(args, "No active run to inspect.");
19
+ export interface WorkflowInspectionSource {
20
+ readonly store?: Store;
21
+ readonly allowLiveHandles?: boolean;
22
+ }
23
+
24
+ export function workflowStagesResult(args: WorkflowToolArgs, source?: WorkflowInspectionSource): WorkflowToolResult {
25
+ const activeStore = source?.store ?? store;
26
+ const target = resolveToolRunTarget(args, "No active run to inspect.", activeStore);
19
27
  const filter = args.statusFilter ?? "all";
20
28
  if (target.kind === "all") {
21
29
  return {
@@ -35,22 +43,24 @@ export function workflowStagesResult(args: WorkflowToolArgs): WorkflowToolResult
35
43
  error: target.message,
36
44
  };
37
45
  }
38
- const run = store.runs().find((r) => r.id === target.runId);
39
- const stages = (run?.stages ?? [])
40
- .filter((stage) => filter === "all" || stage.status === filter)
41
- .map(summarizeStage);
46
+ const stageSnapshots =
47
+ source?.store === undefined
48
+ ? (activeStore.runs().find((run) => run.id === target.runId)?.stages ?? [])
49
+ : expandWorkflowGraph(readGraphStoreSnapshot(activeStore), target.runId).stages;
50
+ const stages = stageSnapshots.filter((stage) => filter === "all" || stage.status === filter).map(summarizeStage);
42
51
  return { action: "stages", runId: target.runId, filter, stages };
43
52
  }
44
53
 
45
- export function workflowStageResult(args: WorkflowToolArgs): WorkflowToolResult {
46
- const target = resolveToolRunTarget(args, "No active run to inspect.");
54
+ export function workflowStageResult(args: WorkflowToolArgs, source?: WorkflowInspectionSource): WorkflowToolResult {
55
+ const activeStore = source?.store ?? store;
56
+ const target = resolveToolRunTarget(args, "No active run to inspect.", activeStore);
47
57
  if (target.kind === "all") {
48
58
  return { action: "stage", runId: "--all", error: "Stage inspection requires a single run." };
49
59
  }
50
60
  if (target.kind === "malformed" || target.kind === "not_found") {
51
61
  return { action: "stage", runId: target.target, error: target.message };
52
62
  }
53
- const stage = resolveToolStageTarget(target.runId, args.stageId);
63
+ const stage = resolveToolStageTarget(target.runId, args.stageId, activeStore);
54
64
  if (!stage.ok || stage.stageId === undefined) {
55
65
  return {
56
66
  action: "stage",
@@ -59,7 +69,7 @@ export function workflowStageResult(args: WorkflowToolArgs): WorkflowToolResult
59
69
  };
60
70
  }
61
71
  const stageRunId = stage.runId ?? target.runId;
62
- const run = store.runs().find((r) => r.id === stageRunId);
72
+ const run = activeStore.runs().find((r) => r.id === stageRunId);
63
73
  const snapshot = run?.stages.find((s) => s.id === stage.stageId);
64
74
  return snapshot
65
75
  ? { action: "stage", runId: stageRunId, stage: cloneStage(snapshot) }
@@ -70,8 +80,12 @@ export function workflowStageResult(args: WorkflowToolArgs): WorkflowToolResult
70
80
  };
71
81
  }
72
82
 
73
- export function workflowTranscriptResult(args: WorkflowToolArgs): WorkflowToolResult {
74
- const target = resolveToolRunTarget(args, "No active run to inspect.");
83
+ export function workflowTranscriptResult(
84
+ args: WorkflowToolArgs,
85
+ source?: WorkflowInspectionSource,
86
+ ): WorkflowToolResult {
87
+ const activeStore = source?.store ?? store;
88
+ const target = resolveToolRunTarget(args, "No active run to inspect.", activeStore);
75
89
  if (target.kind === "all") {
76
90
  return {
77
91
  action: "transcript",
@@ -92,7 +106,7 @@ export function workflowTranscriptResult(args: WorkflowToolArgs): WorkflowToolRe
92
106
  truncated: false,
93
107
  };
94
108
  }
95
- const stage = resolveToolStageTarget(target.runId, args.stageId);
109
+ const stage = resolveToolStageTarget(target.runId, args.stageId, activeStore);
96
110
  if (!stage.ok || stage.stageId === undefined) {
97
111
  return {
98
112
  action: "transcript",
@@ -104,9 +118,10 @@ export function workflowTranscriptResult(args: WorkflowToolArgs): WorkflowToolRe
104
118
  };
105
119
  }
106
120
  const stageRunId = stage.runId ?? target.runId;
107
- const run = store.runs().find((r) => r.id === stageRunId);
121
+ const run = activeStore.runs().find((r) => r.id === stageRunId);
108
122
  const snapshot = run?.stages.find((s) => s.id === stage.stageId);
109
- const liveHandle = stageControlRegistry.get(stageRunId, stage.stageId);
123
+ const liveHandle =
124
+ source?.allowLiveHandles === false ? undefined : stageControlRegistry.get(stageRunId, stage.stageId);
110
125
  if (liveHandle !== undefined) {
111
126
  const sessionFile = liveHandle.sessionFile ?? snapshot?.sessionFile;
112
127
  const sessionId = liveHandle.sessionId ?? snapshot?.sessionId;
@@ -5,15 +5,20 @@ import {
5
5
  import type { DetachedStageHandleLease, StageControlHandle } from "../runs/foreground/stage-control-registry.js";
6
6
  import { stageControlRegistry } from "../runs/foreground/stage-control-registry.js";
7
7
  import type { StageUserMessageDeliveryAction } from "../runs/foreground/stage-runner-types.js";
8
+ import {
9
+ type ExpandedWorkflowStage,
10
+ expandedStageLabel,
11
+ expandWorkflowGraph,
12
+ } from "../shared/expanded-workflow-graph.js";
8
13
  import { coerceStageInputAnswer, hasStageInputAnswerContent, type StageInputAnswer } from "../shared/stage-prompt.js";
9
14
  import { stageUiBroker } from "../shared/stage-ui-broker.js";
10
15
  import { store } from "../shared/store.js";
11
16
  import { isTerminalRunStatus } from "../shared/store-internal.js";
12
- import { subscribeStoreInvalidation } from "../shared/store-observation.js";
17
+ import { readGraphStoreSnapshot, subscribeStoreInvalidation } from "../shared/store-observation.js";
13
18
  import { reciprocalWorkflowRootRunId } from "../shared/workflow-run-ownership.js";
14
19
  import type { WorkflowToolArgs } from "./public-types.js";
15
20
  import type { WorkflowToolResult } from "./render-result.js";
16
- import { resolveToolRunTarget, resolveToolStageTarget } from "./workflow-targets.js";
21
+ import { resolveToolRunTarget, resolveToolStageTarget, type ToolStageTarget } from "./workflow-targets.js";
17
22
 
18
23
  /**
19
24
  * Optional dependencies enabling `workflow send` to revive an eligible
@@ -49,6 +54,69 @@ function brokerAnswerFromArgs(args: WorkflowToolArgs): StageInputAnswer {
49
54
  return text !== undefined ? { text } : {};
50
55
  }
51
56
 
57
+ /**
58
+ * Whether this delivery is one whose destination a pending prompt determines.
59
+ *
60
+ * `answer` and `auto` are both documented as answering pending prompts first,
61
+ * and an explicit `promptId` names a prompt without naming its stage. Steering
62
+ * deliveries are excluded: a live stage is a destination in its own right, so
63
+ * there is nothing for a prompt to disambiguate.
64
+ */
65
+ function answersPendingPrompt(delivery: string, promptId: string | undefined): boolean {
66
+ return delivery === "answer" || delivery === "auto" || promptId !== undefined;
67
+ }
68
+
69
+ /**
70
+ * Ids of the prompts an `answer` delivery could resolve on this stage.
71
+ *
72
+ * Covers the same three prompt kinds the delivery path dispatches on —
73
+ * brokered, custom-footprint, and plain pending prompts — so a stage counted
74
+ * here is one the send would actually act on rather than merely mention.
75
+ */
76
+ function answerablePromptIds(stage: ExpandedWorkflowStage): string[] {
77
+ const target = stage.workflowGraphTarget;
78
+ const ids: string[] = [];
79
+ const brokered = stageUiBroker.peekStagePrompt(target.runId, target.stageId);
80
+ if (brokered !== undefined) ids.push(brokered.id);
81
+ if (stage.pendingPrompt !== undefined) ids.push(stage.pendingPrompt.id);
82
+ if (stage.status === "awaiting_input" && stage.promptFootprint?.kind === "custom") {
83
+ ids.push(stage.promptFootprint.id);
84
+ }
85
+ return ids;
86
+ }
87
+
88
+ /**
89
+ * Resolve the stage an answer is for when the caller named a prompt but no stage.
90
+ *
91
+ * A named `promptId` already identifies its stage, so it narrows the candidates
92
+ * before uniqueness is decided — otherwise naming one of two pending prompts
93
+ * would be rejected as ambiguous, and naming an unknown one would target the
94
+ * unrelated stage that happened to be the only one waiting.
95
+ *
96
+ * Returns the target unchanged when nothing is pending, so a run with no prompt
97
+ * still reports that a stage is required rather than inventing a destination.
98
+ */
99
+ function inferPromptStageTarget(runId: string, delivery: string, promptId: string | undefined): ToolStageTarget {
100
+ if (!answersPendingPrompt(delivery, promptId)) return { ok: true, runId };
101
+ const pending = expandWorkflowGraph(readGraphStoreSnapshot(store), runId).stages.filter((stage) => {
102
+ const ids = answerablePromptIds(stage);
103
+ return promptId === undefined ? ids.length > 0 : ids.includes(promptId);
104
+ });
105
+ const only = pending[0];
106
+ if (pending.length === 1 && only !== undefined) {
107
+ return { ok: true, runId: only.workflowGraphTarget.runId, stageId: only.workflowGraphTarget.stageId };
108
+ }
109
+ if (pending.length === 0) {
110
+ return promptId === undefined
111
+ ? { ok: true, runId }
112
+ : { ok: false, message: `No pending prompt ${promptId} in run ${runId}.` };
113
+ }
114
+ return {
115
+ ok: false,
116
+ message: `${pending.length} prompts pending; pass stageId: ${pending.map(expandedStageLabel).join(", ")}`,
117
+ };
118
+ }
119
+
52
120
  type WorkflowSendToolResult = Extract<WorkflowToolResult, { action: "send" }>;
53
121
 
54
122
  function workflowSendResult(
@@ -131,7 +199,11 @@ export async function workflowSendAction(
131
199
  if (rootRun !== undefined && isTerminalRunStatus(rootRun.status)) {
132
200
  return terminalWorkflowSendResult(rootRun.id, args.stageId?.trim() ?? "", rootRun.status);
133
201
  }
134
- const stage = resolveToolStageTarget(target.runId, args.stageId);
202
+ const requested = resolveToolStageTarget(target.runId, args.stageId);
203
+ const stage =
204
+ requested.ok && requested.stageId === undefined
205
+ ? inferPromptStageTarget(target.runId, requestedDelivery, args.promptId)
206
+ : requested;
135
207
  if (!stage.ok || stage.stageId === undefined) {
136
208
  return workflowSendResult(
137
209
  target.runId,
@@ -18,9 +18,49 @@ import {
18
18
  workflowReloadAction,
19
19
  workflowResumeAction,
20
20
  } from "./workflow-tool-control.js";
21
- import { workflowStageResult, workflowStagesResult, workflowTranscriptResult } from "./workflow-tool-inspection.js";
21
+ import {
22
+ type WorkflowInspectionSource,
23
+ workflowStageResult,
24
+ workflowStagesResult,
25
+ workflowTranscriptResult,
26
+ } from "./workflow-tool-inspection.js";
22
27
  import { type WorkflowSendDeps, workflowSendAction } from "./workflow-tool-send.js";
23
28
 
29
+ type DurableInspectionSourceResolution =
30
+ | { readonly kind: "local" }
31
+ | { readonly kind: "durable"; readonly source: WorkflowInspectionSource }
32
+ | { readonly kind: "error"; readonly message: string };
33
+
34
+ async function resolveDurableInspectionSource(
35
+ args: WorkflowToolArgs,
36
+ runtime: ExtensionRuntime,
37
+ ): Promise<DurableInspectionSourceResolution> {
38
+ const target = args.runId?.trim();
39
+ if (args.all === true || target === undefined || target.length === 0 || target === "--all") return { kind: "local" };
40
+ const local = resolveRunId(target);
41
+ if (local.kind !== "not_found") return { kind: "local" };
42
+ const durable = await runtime.inspectDurableWorkflow(target);
43
+ if (durable.kind !== "found") return { kind: "error", message: durable.message };
44
+ return { kind: "durable", source: { store: durable.store, allowLiveHandles: false } };
45
+ }
46
+
47
+ function durableInspectionError(
48
+ action: "stages" | "stage" | "transcript",
49
+ runId: string,
50
+ message: string,
51
+ ): WorkflowToolResult {
52
+ if (action === "stages") return { action, runId, filter: "all", stages: [], error: message };
53
+ if (action === "stage") return { action, runId, error: message };
54
+ return {
55
+ action,
56
+ runId,
57
+ stageId: "",
58
+ source: "error",
59
+ entries: [{ role: "notice", text: message }],
60
+ truncated: false,
61
+ };
62
+ }
63
+
24
64
  export function makeExecuteWorkflowTool(
25
65
  runtime: ExtensionRuntime | ((ctx: PiExecuteContext) => ExtensionRuntime),
26
66
  reloadWorkflowResources: () => Promise<WorkflowReloadReport | undefined> | undefined,
@@ -87,7 +127,10 @@ export function makeExecuteWorkflowTool(
87
127
  return { action: "statusDetail", runId: target, error: resolved.message };
88
128
  }
89
129
  if (resolved.kind === "not_found") {
90
- return { action: "statusDetail", runId: target, error: `run not found: ${target}` };
130
+ const durable = await getRuntime().inspectDurableWorkflow(target);
131
+ return durable.kind === "found"
132
+ ? { action: "statusDetail", runId: target, detail: durable.detail }
133
+ : { action: "statusDetail", runId: target, error: durable.message };
91
134
  }
92
135
  const result = inspectRun(resolved.runId);
93
136
  return result.ok
@@ -105,11 +148,15 @@ export function makeExecuteWorkflowTool(
105
148
  return result;
106
149
  }
107
150
  case "stages":
108
- return workflowStagesResult(args);
109
151
  case "stage":
110
- return workflowStageResult(args);
111
- case "transcript":
112
- return workflowTranscriptResult(args);
152
+ case "transcript": {
153
+ const resolved = await resolveDurableInspectionSource(args, getRuntime());
154
+ if (resolved.kind === "error") return durableInspectionError(action, args.runId ?? "", resolved.message);
155
+ const source = resolved.kind === "durable" ? resolved.source : undefined;
156
+ if (action === "stages") return workflowStagesResult(args, source);
157
+ if (action === "stage") return workflowStageResult(args, source);
158
+ return workflowTranscriptResult(args, source);
159
+ }
113
160
  case "send":
114
161
  return workflowSendAction(args, sendDeps);
115
162
  case "pause":
@@ -28,7 +28,7 @@ import type { WorkflowInputValues, WorkflowOutputValues } from "../../shared/typ
28
28
  export interface RunDetail {
29
29
  readonly runId: string;
30
30
  readonly name: string;
31
- readonly status: RunStatus;
31
+ readonly status: RunStatus | "crashed";
32
32
  readonly mode: "single" | "chain";
33
33
  readonly startedAt: number;
34
34
  readonly endedAt?: number;
@@ -54,6 +54,10 @@ export interface RunDetail {
54
54
  readonly failedToolNodeId?: string;
55
55
  readonly resumable?: boolean;
56
56
  readonly retryAfterMs?: number;
57
+ /** Actionable guidance for a snapshot hydrated from durable storage. */
58
+ readonly resumeGuidance?: string;
59
+ /** True when a fresh durable owner is active in another Atomic process. */
60
+ readonly ownerActiveElsewhere?: boolean;
57
61
  readonly blockedAt?: number;
58
62
  }
59
63
 
@@ -107,7 +107,9 @@ export function createTrackedStageCaller(input: {
107
107
  const suppressQueuedContinuation = reason === "paused-queued-user-message";
108
108
  if (suppressQueuedContinuation) runtime.state.suppressQueuedUserMessageContinuation = true;
109
109
  try {
110
- result = (await raceAbort(runtime.innerCtx.prompt(RESUME_CONTINUATION_PROMPT), runtime.signal)) as T;
110
+ result = (await runtime.raceStageSessionHeartbeat(
111
+ raceAbort(runtime.innerCtx.prompt(RESUME_CONTINUATION_PROMPT), runtime.signal),
112
+ )) as T;
111
113
  } finally {
112
114
  if (suppressQueuedContinuation) runtime.state.suppressQueuedUserMessageContinuation = false;
113
115
  }
@@ -134,61 +136,69 @@ export function createTrackedStageCaller(input: {
134
136
  }
135
137
 
136
138
  const trackStageLifecycle = !runtime.state.stageFinalized;
137
- let refreshedParentIds: readonly string[] | undefined;
138
- if (
139
- trackStageLifecycle &&
140
- !input.hasContinuation &&
141
- runtime.stageSnapshot.startedAt === undefined &&
142
- !input.hasScopedParents
143
- ) {
144
- const actualParentIds = runtime.scheduler.tracker.currentParents();
145
- const sameParents =
146
- actualParentIds.length === runtime.stageSnapshot.parentIds.length &&
147
- actualParentIds.every((value) => runtime.stageSnapshot.parentIds.includes(value));
148
- if (!sameParents) {
149
- runtime.scheduler.tracker.replaceParents(runtime.stageId, actualParentIds);
150
- refreshedParentIds = actualParentIds;
151
- }
152
- }
153
- if (trackStageLifecycle) {
154
- const now = Date.now();
155
- const hasNoExplicitModelConfig =
156
- input.options?.model === undefined && input.options?.fallbackModels === undefined;
157
- const promptAdapterHandlesInitialPrompt = input.adapters.prompt !== undefined;
139
+ let applyTerminalStageState: (() => void) | undefined;
140
+ // Set only alongside the successful "completed" terminal state, so a failed
141
+ // final durable checkpoint can replace it without touching a real
142
+ // failure/skip classification recorded by the catch block.
143
+ let terminalStateIsSuccess = false;
144
+ try {
145
+ let refreshedParentIds: readonly string[] | undefined;
158
146
  if (
159
- callOptions.eagerSession &&
160
- !promptAdapterHandlesInitialPrompt &&
161
- (hasNoExplicitModelConfig ||
162
- (await hasExplicitFastModeCandidate({
163
- model: input.options?.model,
164
- fallbackModels: input.options?.fallbackModels,
165
- models: runtime.opts.models,
166
- })))
147
+ trackStageLifecycle &&
148
+ !input.hasContinuation &&
149
+ runtime.stageSnapshot.startedAt === undefined &&
150
+ !input.hasScopedParents
167
151
  ) {
168
- try {
169
- await runtime.innerCtx.__ensureSession();
170
- runtime.captureStageSessionMeta();
171
- } catch (err) {
172
- if (!(err instanceof Error && err.message.includes("prompt adapter not configured"))) throw err;
152
+ const actualParentIds = runtime.scheduler.tracker.currentParents();
153
+ const sameParents =
154
+ actualParentIds.length === runtime.stageSnapshot.parentIds.length &&
155
+ actualParentIds.every((value) => runtime.stageSnapshot.parentIds.includes(value));
156
+ if (!sameParents) {
157
+ runtime.scheduler.tracker.replaceParents(runtime.stageId, actualParentIds);
158
+ refreshedParentIds = actualParentIds;
173
159
  }
174
160
  }
175
- if (refreshedParentIds !== undefined) {
176
- runtime.scheduler.setStageParentIds(runtime.stageSnapshot, refreshedParentIds);
161
+ if (trackStageLifecycle) {
162
+ const now = Date.now();
163
+ const hasNoExplicitModelConfig =
164
+ input.options?.model === undefined && input.options?.fallbackModels === undefined;
165
+ const promptAdapterHandlesInitialPrompt = input.adapters.prompt !== undefined;
166
+ if (
167
+ callOptions.eagerSession &&
168
+ !promptAdapterHandlesInitialPrompt &&
169
+ (hasNoExplicitModelConfig ||
170
+ (await hasExplicitFastModeCandidate({
171
+ model: input.options?.model,
172
+ fallbackModels: input.options?.fallbackModels,
173
+ models: runtime.opts.models,
174
+ })))
175
+ ) {
176
+ try {
177
+ await runtime.innerCtx.__ensureSession();
178
+ } catch (err) {
179
+ if (!(err instanceof Error && err.message.includes("prompt adapter not configured"))) throw err;
180
+ }
181
+ }
182
+ if (refreshedParentIds !== undefined) {
183
+ runtime.scheduler.setStageParentIds(runtime.stageSnapshot, refreshedParentIds);
184
+ }
185
+ runtime.stageSnapshot.status = "running";
186
+ runtime.stageSnapshot.startedAt ??= rebasedStageStartedAt(input.options?.durableAccumulatedDurationMs, now);
187
+ runtime.applyModelFallbackMeta(runtime.innerCtx.__modelFallbackMeta());
188
+ // Publish every graph-visible live write before this task can yield.
189
+ runtime.activeStore.recordStageStart(runtime.runId, runtime.stageSnapshot);
190
+ runtime.appendStageStartOnce();
191
+ const sessionMeta = runtime.innerCtx.__sessionMeta();
192
+ if (sessionMeta.sessionId !== undefined || sessionMeta.sessionFile !== undefined) {
193
+ await runtime.captureStageSessionMeta({ awaitDurable: true });
194
+ }
195
+ runtime.startStageSessionHeartbeat();
196
+ } else {
197
+ runtime.applyModelFallbackMeta(runtime.innerCtx.__modelFallbackMeta());
177
198
  }
178
- runtime.stageSnapshot.status = "running";
179
- runtime.stageSnapshot.startedAt ??= rebasedStageStartedAt(input.options?.durableAccumulatedDurationMs, now);
180
- runtime.applyModelFallbackMeta(runtime.innerCtx.__modelFallbackMeta());
181
- // Publish every graph-visible live write before this task can yield.
182
- runtime.activeStore.recordStageStart(runtime.runId, runtime.stageSnapshot);
183
- runtime.appendStageStartOnce();
184
- } else {
185
- runtime.applyModelFallbackMeta(runtime.innerCtx.__modelFallbackMeta());
186
- }
187
199
 
188
- runtime.mcpScope.apply();
200
+ runtime.mcpScope.apply();
189
201
 
190
- let applyTerminalStageState: (() => void) | undefined;
191
- try {
192
202
  const abortSession = (): void => {
193
203
  void runtime.innerCtx.abort().catch(() => {});
194
204
  };
@@ -198,7 +208,7 @@ export function createTrackedStageCaller(input: {
198
208
  try {
199
209
  runtime.state.askUserQuestionObservedThisTurn = false;
200
210
  runtime.state.chatAnswerObservedThisTurn = false;
201
- result = await raceAbort(call(), runtime.signal);
211
+ result = await runtime.raceStageSessionHeartbeat(raceAbort(call(), runtime.signal));
202
212
  const initialDrain = await drainResumeContinuations(result);
203
213
  result = initialDrain.result;
204
214
  let repeatReadinessAfterChatTurn = initialDrain.chatAnswerObserved;
@@ -226,12 +236,14 @@ export function createTrackedStageCaller(input: {
226
236
  runtime.state.chatAnswerObservedThisTurn = false;
227
237
  runtime.state.waitingForStageChatTurn = true;
228
238
  try {
229
- await raceAbort(
230
- new Promise<void>((resolve) => {
231
- resolveNextTurnEnd = resolve;
232
- runtime.state.wakeWaitingForStageChatTurn = resolve;
233
- }),
234
- runtime.signal,
239
+ await runtime.raceStageSessionHeartbeat(
240
+ raceAbort(
241
+ new Promise<void>((resolve) => {
242
+ resolveNextTurnEnd = resolve;
243
+ runtime.state.wakeWaitingForStageChatTurn = resolve;
244
+ }),
245
+ runtime.signal,
246
+ ),
235
247
  );
236
248
  } finally {
237
249
  runtime.state.wakeWaitingForStageChatTurn = undefined;
@@ -254,7 +266,7 @@ export function createTrackedStageCaller(input: {
254
266
  runtime.signal.removeEventListener("abort", abortSession);
255
267
  }
256
268
  await runtime.innerCtx.__closeGeneration();
257
- runtime.captureStageSessionMeta();
269
+ await runtime.captureStageSessionMeta({ awaitDurable: true });
258
270
  runtime.applyModelFallbackMeta(runtime.innerCtx.__modelFallbackMeta());
259
271
  if (
260
272
  trackStageLifecycle &&
@@ -267,6 +279,7 @@ export function createTrackedStageCaller(input: {
267
279
  if (trackStageLifecycle && runtime.state.stageFinalized) throw runtime.parallelFailFastError();
268
280
  if (trackStageLifecycle) {
269
281
  const assistantText = runtime.innerCtx.__getLastAssistantText();
282
+ terminalStateIsSuccess = true;
270
283
  applyTerminalStageState = () => {
271
284
  runtime.stageSnapshot.status = "completed";
272
285
  if (assistantText !== undefined) runtime.stageSnapshot.result = assistantText;
@@ -294,15 +307,36 @@ export function createTrackedStageCaller(input: {
294
307
  // If finalizeStageSnapshot() throws, the limiter must still be released
295
308
  // so the concurrency semaphore is not leaked.
296
309
  // cross-ref: issue #1498 — durable finalization failures must not leak the stage limiter.
297
- runtime.mcpScope.clear();
298
310
  let finalizationError: { readonly thrown: true; readonly error: unknown } | undefined;
311
+ // Tracked separately from `finalizationError`: only a failed final durable
312
+ // checkpoint may rewrite an otherwise-successful terminal stage state.
313
+ let durableCheckpointError: { readonly thrown: true; readonly error: unknown } | undefined;
299
314
  try {
300
- await runtime.innerCtx.__closeGeneration();
301
- runtime.captureStageSessionMeta();
315
+ runtime.mcpScope.clear();
302
316
  } catch (err) {
303
317
  finalizationError = { thrown: true, error: err };
304
318
  }
319
+ try {
320
+ // Drain rather than stop: a checkpoint still in flight must be awaited
321
+ // here, or its late rejection is discarded with the cancelled timer.
322
+ await runtime.drainStageSessionHeartbeat();
323
+ } catch (err) {
324
+ durableCheckpointError = { thrown: true, error: err };
325
+ }
326
+ try {
327
+ await runtime.innerCtx.__closeGeneration();
328
+ await runtime.captureStageSessionMeta({ awaitDurable: true });
329
+ } catch (err) {
330
+ durableCheckpointError ??= { thrown: true, error: err };
331
+ }
332
+ finalizationError ??= durableCheckpointError;
305
333
  if (trackStageLifecycle) {
334
+ // A stage whose final durability checkpoint failed must never be
335
+ // persisted as `completed` while its caller receives that rejection.
336
+ if (durableCheckpointError !== undefined && terminalStateIsSuccess) {
337
+ const failure = runtime.classifyExecutorFailure(durableCheckpointError.error);
338
+ applyTerminalStageState = () => applyFailureToStage(runtime.stageSnapshot, failure);
339
+ }
306
340
  if (!runtime.state.stageFinalized) applyTerminalStageState?.();
307
341
  // `finalizeStageSnapshot` calls the version-bumping `recordStageEnd`
308
342
  // before its first yield, so the live write cannot outpace the cache.
@@ -52,7 +52,7 @@ export function createStageContext(input: {
52
52
  try {
53
53
  runtime.mcpScope.clear();
54
54
  } finally {
55
- runtime.captureStageSessionMeta();
55
+ void runtime.captureStageSessionMeta();
56
56
  runtime.applyModelFallbackMeta(innerCtx.__modelFallbackMeta());
57
57
  }
58
58
  }