@bastani/atomic 0.9.12 → 0.9.13-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 (264) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  3. package/dist/builtin/intercom/README.md +5 -12
  4. package/dist/builtin/intercom/broker/client.ts +11 -2
  5. package/dist/builtin/intercom/contact-supervisor-tool.ts +13 -3
  6. package/dist/builtin/intercom/index-heavy.ts +13 -3
  7. package/dist/builtin/intercom/index.ts +43 -19
  8. package/dist/builtin/intercom/intercom-utils.ts +15 -29
  9. package/dist/builtin/intercom/package.json +1 -1
  10. package/dist/builtin/intercom/source-ownership.ts +9 -12
  11. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  12. package/dist/builtin/mcp/README.md +3 -1
  13. package/dist/builtin/mcp/index.ts +10 -16
  14. package/dist/builtin/mcp/package.json +1 -1
  15. package/dist/builtin/mcp/startup-warmup.ts +20 -8
  16. package/dist/builtin/subagents/CHANGELOG.md +30 -0
  17. package/dist/builtin/subagents/README.md +22 -30
  18. package/dist/builtin/subagents/package.json +1 -1
  19. package/dist/builtin/subagents/src/extension/doctor.ts +1 -1
  20. package/dist/builtin/subagents/src/extension/fanout-child.ts +76 -56
  21. package/dist/builtin/subagents/src/extension/index.ts +23 -32
  22. package/dist/builtin/subagents/src/extension/startup-maintenance.ts +25 -44
  23. package/dist/builtin/subagents/src/intercom/result-intercom.ts +7 -7
  24. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +166 -434
  25. package/dist/builtin/subagents/src/runs/background/notify.ts +29 -8
  26. package/dist/builtin/subagents/src/runs/foreground/chain-execution-dynamic-step.ts +2 -2
  27. package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +4 -4
  28. package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-step.ts +2 -2
  29. package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +2 -2
  30. package/dist/builtin/subagents/src/runs/foreground/execution-run-sync.ts +1 -281
  31. package/dist/builtin/subagents/src/runs/foreground/execution-structured-retries.ts +11 -75
  32. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +457 -0
  33. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +119 -246
  34. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-chain.ts +1 -1
  35. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +18 -9
  36. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +1 -1
  37. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +4 -4
  38. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +1 -646
  39. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +2 -7
  40. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +5 -5
  41. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +7 -8
  42. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +14 -5
  43. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +158 -93
  44. package/dist/builtin/subagents/src/runs/inprocess/background-single.ts +179 -0
  45. package/dist/builtin/subagents/src/runs/inprocess/background.ts +89 -0
  46. package/dist/builtin/subagents/src/runs/inprocess/child-policy.ts +27 -0
  47. package/dist/builtin/subagents/src/runs/inprocess/control-registry.ts +55 -0
  48. package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +106 -0
  49. package/dist/builtin/subagents/src/runs/inprocess/index.ts +50 -0
  50. package/dist/builtin/subagents/src/runs/inprocess/nested-routing.ts +218 -0
  51. package/dist/builtin/subagents/src/runs/{shared/subagent-prompt-runtime.ts → inprocess/prompt-behavior.ts} +37 -75
  52. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +1138 -0
  53. package/dist/builtin/subagents/src/runs/{shared/nested-events.ts → inprocess/runtime-support/nested-api.ts} +13 -7
  54. package/dist/builtin/subagents/src/runs/{shared/nested-events-control.ts → inprocess/runtime-support/nested-control.ts} +18 -17
  55. package/dist/builtin/subagents/src/runs/{shared/nested-events-core.ts → inprocess/runtime-support/nested-core.ts} +14 -61
  56. package/dist/builtin/subagents/src/runs/{shared/nested-events-projection.ts → inprocess/runtime-support/nested-projection.ts} +4 -10
  57. package/dist/builtin/subagents/src/runs/{shared/nested-events-registry.ts → inprocess/runtime-support/nested-registry.ts} +19 -4
  58. package/dist/builtin/subagents/src/runs/{shared/nested-render.ts → inprocess/runtime-support/nested-rendering.ts} +3 -3
  59. package/dist/builtin/subagents/src/runs/{shared/nested-events-sanitize.ts → inprocess/runtime-support/nested-sanitize.ts} +3 -3
  60. package/dist/builtin/subagents/src/runs/inprocess.ts +1 -0
  61. package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +10 -4
  62. package/dist/builtin/subagents/src/runs/shared/model-candidate-filter.ts +0 -1
  63. package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +15 -492
  64. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +13 -11
  65. package/dist/builtin/subagents/src/runs/shared/run-history.ts +3 -5
  66. package/dist/builtin/subagents/src/runs/shared/single-output.ts +4 -4
  67. package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +5 -5
  68. package/dist/builtin/subagents/src/shared/types-async.ts +0 -1
  69. package/dist/builtin/subagents/src/shared/types-config.ts +18 -0
  70. package/dist/builtin/subagents/src/shared/types-results.ts +9 -2
  71. package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +2 -3
  72. package/dist/builtin/subagents/src/slash/slash-live-state.ts +2 -2
  73. package/dist/builtin/subagents/src/tui/render-chain-graph.ts +10 -4
  74. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +12 -11
  75. package/dist/builtin/subagents/src/tui/render-result-compact.ts +49 -12
  76. package/dist/builtin/subagents/src/tui/render-result.ts +46 -17
  77. package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -1
  78. package/dist/builtin/subagents/src/tui/render-status-progress.ts +8 -5
  79. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +0 -8
  80. package/dist/builtin/subagents/src/tui/render-widget.ts +0 -24
  81. package/dist/builtin/web-access/package.json +1 -1
  82. package/dist/builtin/workflows/CHANGELOG.md +25 -0
  83. package/dist/builtin/workflows/README.md +13 -3
  84. package/dist/builtin/workflows/package.json +1 -1
  85. package/dist/builtin/workflows/src/durable/backend.ts +7 -0
  86. package/dist/builtin/workflows/src/durable/dbos-metadata.ts +11 -1
  87. package/dist/builtin/workflows/src/durable/resume-runtime.ts +1 -0
  88. package/dist/builtin/workflows/src/durable/tool-primitive.ts +4 -28
  89. package/dist/builtin/workflows/src/durable/types.ts +5 -0
  90. package/dist/builtin/workflows/src/engine/run.ts +17 -1
  91. package/dist/builtin/workflows/src/extension/atomic-stage-session.ts +12 -39
  92. package/dist/builtin/workflows/src/extension/config-file-loader.ts +10 -1
  93. package/dist/builtin/workflows/src/extension/config-loader.ts +1 -1
  94. package/dist/builtin/workflows/src/extension/dispatcher.ts +4 -0
  95. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1151 -737
  96. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +303 -38
  97. package/dist/builtin/workflows/src/extension/render-result.ts +4 -0
  98. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +6 -3
  99. package/dist/builtin/workflows/src/extension/runtime.ts +7 -1
  100. package/dist/builtin/workflows/src/extension/wiring.ts +53 -11
  101. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +12 -3
  102. package/dist/builtin/workflows/src/extension/workflow-durable-resume-command.ts +5 -2
  103. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +9 -7
  104. package/dist/builtin/workflows/src/extension/workflow-status-summary.ts +11 -0
  105. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +18 -1
  106. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +10 -8
  107. package/dist/builtin/workflows/src/extension/workflow-tool.ts +9 -4
  108. package/dist/builtin/workflows/src/runs/background/quit.ts +28 -4
  109. package/dist/builtin/workflows/src/runs/background/resume-acknowledgements.ts +2 -1
  110. package/dist/builtin/workflows/src/runs/background/status.ts +55 -8
  111. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +4 -0
  112. package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +3 -1
  113. package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +1 -1
  114. package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +1 -1
  115. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +688 -38
  116. package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +3 -1
  117. package/dist/builtin/workflows/src/runs/shared/model-fallback-failures.ts +19 -514
  118. package/dist/builtin/workflows/src/runs/shared/retry.ts +44 -0
  119. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +5 -0
  120. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +6 -0
  121. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +5 -1
  122. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +5 -0
  123. package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +5 -0
  124. package/dist/builtin/workflows/src/shared/persistence-restore.ts +3 -0
  125. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
  126. package/dist/builtin/workflows/src/shared/run-indicator-status.ts +75 -0
  127. package/dist/builtin/workflows/src/shared/store-public-types.ts +43 -7
  128. package/dist/builtin/workflows/src/shared/store-run-methods.ts +42 -8
  129. package/dist/builtin/workflows/src/shared/store-stage-methods.ts +31 -8
  130. package/dist/builtin/workflows/src/shared/store-types.ts +34 -0
  131. package/dist/builtin/workflows/src/shared/timing.ts +10 -0
  132. package/dist/builtin/workflows/src/tui/chat-surface-message.ts +22 -2
  133. package/dist/builtin/workflows/src/tui/session-list.ts +1 -1
  134. package/dist/builtin/workflows/src/tui/session-overlays.ts +14 -10
  135. package/dist/builtin/workflows/src/tui/session-picker.ts +20 -4
  136. package/dist/builtin/workflows/src/tui/status-list.ts +31 -47
  137. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +9 -1
  138. package/dist/builtin/workflows/src/tui/widget.ts +16 -46
  139. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  140. package/dist/core/agent-session-auto-compaction.js +54 -11
  141. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  142. package/dist/core/agent-session-events.d.ts.map +1 -1
  143. package/dist/core/agent-session-events.js +49 -14
  144. package/dist/core/agent-session-events.js.map +1 -1
  145. package/dist/core/agent-session-methods.d.ts +13 -0
  146. package/dist/core/agent-session-methods.d.ts.map +1 -1
  147. package/dist/core/agent-session-methods.js.map +1 -1
  148. package/dist/core/agent-session-models.d.ts.map +1 -1
  149. package/dist/core/agent-session-models.js +12 -1
  150. package/dist/core/agent-session-models.js.map +1 -1
  151. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  152. package/dist/core/agent-session-prompt.js +4 -0
  153. package/dist/core/agent-session-prompt.js.map +1 -1
  154. package/dist/core/agent-session-retry.d.ts +21 -4
  155. package/dist/core/agent-session-retry.d.ts.map +1 -1
  156. package/dist/core/agent-session-retry.js +232 -106
  157. package/dist/core/agent-session-retry.js.map +1 -1
  158. package/dist/core/agent-session-state.d.ts.map +1 -1
  159. package/dist/core/agent-session-state.js +2 -1
  160. package/dist/core/agent-session-state.js.map +1 -1
  161. package/dist/core/agent-session-tool-hooks.d.ts.map +1 -1
  162. package/dist/core/agent-session-tool-hooks.js +12 -0
  163. package/dist/core/agent-session-tool-hooks.js.map +1 -1
  164. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  165. package/dist/core/agent-session-tool-registry.js +1 -1
  166. package/dist/core/agent-session-tool-registry.js.map +1 -1
  167. package/dist/core/agent-session-types.d.ts +3 -1
  168. package/dist/core/agent-session-types.d.ts.map +1 -1
  169. package/dist/core/agent-session-types.js.map +1 -1
  170. package/dist/core/agent-session.d.ts +12 -1
  171. package/dist/core/agent-session.d.ts.map +1 -1
  172. package/dist/core/agent-session.js +7 -0
  173. package/dist/core/agent-session.js.map +1 -1
  174. package/dist/core/extensions/context-types.d.ts +25 -0
  175. package/dist/core/extensions/context-types.d.ts.map +1 -1
  176. package/dist/core/extensions/context-types.js.map +1 -1
  177. package/dist/core/extensions/index.d.ts +1 -1
  178. package/dist/core/extensions/index.d.ts.map +1 -1
  179. package/dist/core/extensions/index.js.map +1 -1
  180. package/dist/core/extensions/runner-context.d.ts +2 -1
  181. package/dist/core/extensions/runner-context.d.ts.map +1 -1
  182. package/dist/core/extensions/runner-context.js +4 -0
  183. package/dist/core/extensions/runner-context.js.map +1 -1
  184. package/dist/core/extensions/runner.d.ts +3 -2
  185. package/dist/core/extensions/runner.d.ts.map +1 -1
  186. package/dist/core/extensions/runner.js +3 -1
  187. package/dist/core/extensions/runner.js.map +1 -1
  188. package/dist/core/model-fallback-failures.d.ts +30 -0
  189. package/dist/core/model-fallback-failures.d.ts.map +1 -0
  190. package/dist/core/model-fallback-failures.js +610 -0
  191. package/dist/core/model-fallback-failures.js.map +1 -0
  192. package/dist/core/retry-policy.d.ts +30 -0
  193. package/dist/core/retry-policy.d.ts.map +1 -0
  194. package/dist/core/retry-policy.js +25 -0
  195. package/dist/core/retry-policy.js.map +1 -0
  196. package/dist/core/sdk-types.d.ts +8 -2
  197. package/dist/core/sdk-types.d.ts.map +1 -1
  198. package/dist/core/sdk-types.js.map +1 -1
  199. package/dist/core/sdk.d.ts.map +1 -1
  200. package/dist/core/sdk.js +6 -1
  201. package/dist/core/sdk.js.map +1 -1
  202. package/dist/core/session-manager-core.d.ts +2 -0
  203. package/dist/core/session-manager-core.d.ts.map +1 -1
  204. package/dist/core/session-manager-core.js +7 -0
  205. package/dist/core/session-manager-core.js.map +1 -1
  206. package/dist/index-extensions.d.ts +1 -1
  207. package/dist/index-extensions.d.ts.map +1 -1
  208. package/dist/index-extensions.js.map +1 -1
  209. package/dist/index.d.ts +3 -0
  210. package/dist/index.d.ts.map +1 -1
  211. package/dist/index.js +2 -0
  212. package/dist/index.js.map +1 -1
  213. package/docs/intercom.md +5 -10
  214. package/docs/settings.md +7 -1
  215. package/docs/subagents.md +19 -19
  216. package/docs/workflows.md +21 -5
  217. package/examples/extensions/gondolin/package-lock.json +3 -3
  218. package/npm-shrinkwrap.json +29 -29
  219. package/package.json +2 -2
  220. package/dist/builtin/subagents/src/runs/background/async-event-journal.ts +0 -112
  221. package/dist/builtin/subagents/src/runs/background/async-execution-chain.ts +0 -543
  222. package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +0 -140
  223. package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +0 -279
  224. package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +0 -99
  225. package/dist/builtin/subagents/src/runs/background/async-execution.ts +0 -17
  226. package/dist/builtin/subagents/src/runs/background/async-resume.ts +0 -402
  227. package/dist/builtin/subagents/src/runs/background/async-status.ts +0 -411
  228. package/dist/builtin/subagents/src/runs/background/completion-claims.ts +0 -196
  229. package/dist/builtin/subagents/src/runs/background/completion-dedupe.ts +0 -102
  230. package/dist/builtin/subagents/src/runs/background/parallel-groups.ts +0 -59
  231. package/dist/builtin/subagents/src/runs/background/result-delivery-processor.ts +0 -300
  232. package/dist/builtin/subagents/src/runs/background/result-file-claims.ts +0 -166
  233. package/dist/builtin/subagents/src/runs/background/result-quarantine.ts +0 -77
  234. package/dist/builtin/subagents/src/runs/background/result-retry-scheduler.ts +0 -48
  235. package/dist/builtin/subagents/src/runs/background/result-status.ts +0 -87
  236. package/dist/builtin/subagents/src/runs/background/result-watcher-data.ts +0 -72
  237. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +0 -320
  238. package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +0 -105
  239. package/dist/builtin/subagents/src/runs/background/run-status.ts +0 -358
  240. package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +0 -457
  241. package/dist/builtin/subagents/src/runs/background/subagent-runner-dynamic.ts +0 -376
  242. package/dist/builtin/subagents/src/runs/background/subagent-runner-finalize.ts +0 -150
  243. package/dist/builtin/subagents/src/runs/background/subagent-runner-output.ts +0 -109
  244. package/dist/builtin/subagents/src/runs/background/subagent-runner-parallel-helpers.ts +0 -128
  245. package/dist/builtin/subagents/src/runs/background/subagent-runner-parallel.ts +0 -309
  246. package/dist/builtin/subagents/src/runs/background/subagent-runner-sequential.ts +0 -173
  247. package/dist/builtin/subagents/src/runs/background/subagent-runner-state.ts +0 -578
  248. package/dist/builtin/subagents/src/runs/background/subagent-runner-step.ts +0 -330
  249. package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +0 -330
  250. package/dist/builtin/subagents/src/runs/background/subagent-runner-types.ts +0 -210
  251. package/dist/builtin/subagents/src/runs/background/subagent-runner-utils.ts +0 -69
  252. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +0 -90
  253. package/dist/builtin/subagents/src/runs/background/top-level-async.ts +0 -12
  254. package/dist/builtin/subagents/src/runs/foreground/execution-attempt-control.ts +0 -140
  255. package/dist/builtin/subagents/src/runs/foreground/execution-attempt-finalize.ts +0 -108
  256. package/dist/builtin/subagents/src/runs/foreground/execution-attempt-types.ts +0 -17
  257. package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +0 -558
  258. package/dist/builtin/subagents/src/runs/shared/attempt-watchdog.ts +0 -126
  259. package/dist/builtin/subagents/src/runs/shared/final-drain.ts +0 -39
  260. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +0 -339
  261. package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +0 -165
  262. package/dist/builtin/subagents/src/runs/shared/spawn-env.ts +0 -21
  263. package/dist/builtin/subagents/src/shared/post-exit-stdio-guard.ts +0 -86
  264. /package/dist/builtin/subagents/src/runs/{shared/nested-path.ts → inprocess/runtime-support/nested-paths.ts} +0 -0
@@ -7,6 +7,7 @@ import type {
7
7
  StageSnapshot,
8
8
  StageStatus,
9
9
  ToolNodeSnapshot,
10
+ WorkflowActor,
10
11
  WorkflowFailureCode,
11
12
  WorkflowFailureDisposition,
12
13
  WorkflowFailureKind,
@@ -406,6 +407,7 @@ export function restoreTerminalRuns(entries: readonly SessionEntry[], store: Sto
406
407
  ...(runMeta.rootRunId !== undefined ? { rootRunId: runMeta.rootRunId } : {}),
407
408
  ...(runMeta.resumedFromRunId !== undefined ? { resumedFromRunId: runMeta.resumedFromRunId } : {}),
408
409
  ...(runMeta.resumeFromStageId !== undefined ? { resumeFromStageId: runMeta.resumeFromStageId } : {}),
410
+ ...(runMeta.origin !== undefined ? { origin: runMeta.origin } : {}),
409
411
  ...(runMeta.accumulatedDurationMs !== undefined
410
412
  ? { accumulatedDurationMs: runMeta.accumulatedDurationMs }
411
413
  : {}),
@@ -481,6 +483,7 @@ export function findRunStartMetadata(
481
483
  readonly resumedFromRunId?: string;
482
484
  readonly resumeFromStageId?: string;
483
485
  readonly accumulatedDurationMs?: number;
486
+ readonly origin?: WorkflowActor;
484
487
  } {
485
488
  for (const entry of entries) {
486
489
  if (entry.type !== "workflow.run.start" || entry.payload.runId !== runId) continue;
@@ -490,6 +493,7 @@ export function findRunStartMetadata(
490
493
  const resumedFromRunId = entry.payload.resumedFromRunId;
491
494
  const resumeFromStageId = entry.payload.resumeFromStageId;
492
495
  const accumulatedDurationMs = entry.payload.accumulatedDurationMs;
496
+ const origin = entry.payload.origin;
493
497
  return {
494
498
  ...(typeof parentRunId === "string" ? { parentRunId } : {}),
495
499
  ...(typeof parentStageId === "string" ? { parentStageId } : {}),
@@ -501,6 +505,7 @@ export function findRunStartMetadata(
501
505
  accumulatedDurationMs > 0
502
506
  ? { accumulatedDurationMs }
503
507
  : {}),
508
+ ...(origin === "user" || origin === "agent" ? { origin } : {}),
504
509
  };
505
510
  }
506
511
  return {};
@@ -209,6 +209,7 @@ export function restoreOnSessionStart(
209
209
  ...(runMeta.parentStageId !== undefined ? { parentStageId: runMeta.parentStageId } : {}),
210
210
  ...(runMeta.rootRunId !== undefined ? { rootRunId: runMeta.rootRunId } : {}),
211
211
  ...(runMeta.resumedFromRunId !== undefined ? { resumedFromRunId: runMeta.resumedFromRunId } : {}),
212
+ ...(runMeta.origin !== undefined ? { origin: runMeta.origin } : {}),
212
213
  ...(runMeta.resumeFromStageId !== undefined ? { resumeFromStageId: runMeta.resumeFromStageId } : {}),
213
214
  ...(runMeta.accumulatedDurationMs !== undefined
214
215
  ? { accumulatedDurationMs: runMeta.accumulatedDurationMs }
@@ -244,6 +245,7 @@ export function restoreOnSessionStart(
244
245
  ...(runMeta.parentStageId !== undefined ? { parentStageId: runMeta.parentStageId } : {}),
245
246
  ...(runMeta.rootRunId !== undefined ? { rootRunId: runMeta.rootRunId } : {}),
246
247
  ...(runMeta.resumedFromRunId !== undefined ? { resumedFromRunId: runMeta.resumedFromRunId } : {}),
248
+ ...(runMeta.origin !== undefined ? { origin: runMeta.origin } : {}),
247
249
  ...(runMeta.resumeFromStageId !== undefined ? { resumeFromStageId: runMeta.resumeFromStageId } : {}),
248
250
  ...(runMeta.accumulatedDurationMs !== undefined
249
251
  ? { accumulatedDurationMs: runMeta.accumulatedDurationMs }
@@ -268,6 +270,7 @@ export function restoreOnSessionStart(
268
270
  ...(runMeta.parentStageId !== undefined ? { parentStageId: runMeta.parentStageId } : {}),
269
271
  ...(runMeta.rootRunId !== undefined ? { rootRunId: runMeta.rootRunId } : {}),
270
272
  ...(runMeta.resumedFromRunId !== undefined ? { resumedFromRunId: runMeta.resumedFromRunId } : {}),
273
+ ...(runMeta.origin !== undefined ? { origin: runMeta.origin } : {}),
271
274
  ...(runMeta.resumeFromStageId !== undefined ? { resumeFromStageId: runMeta.resumeFromStageId } : {}),
272
275
  ...(runMeta.accumulatedDurationMs !== undefined
273
276
  ? { accumulatedDurationMs: runMeta.accumulatedDurationMs }
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type {
10
10
  ToolNodeSnapshot,
11
+ WorkflowActor,
11
12
  WorkflowFailureCode,
12
13
  WorkflowFailureDisposition,
13
14
  WorkflowFailureKind,
@@ -40,6 +41,8 @@ export interface RunStartPayload {
40
41
  readonly parentStageId?: string;
41
42
  readonly rootRunId?: string;
42
43
  readonly resumedFromRunId?: string;
44
+ /** Who launched this run, when the launcher was attributable. */
45
+ readonly origin?: WorkflowActor;
43
46
  readonly resumeFromStageId?: string;
44
47
  /** Elapsed ms inherited from prior sessions of a resumed run. */
45
48
  readonly accumulatedDurationMs?: number;
@@ -151,6 +154,7 @@ export function appendRunStart(api: PersistenceAPI, payload: RunStartPayload): v
151
154
  ...(payload.parentStageId !== undefined ? { parentStageId: payload.parentStageId } : {}),
152
155
  ...(payload.rootRunId !== undefined ? { rootRunId: payload.rootRunId } : {}),
153
156
  ...(payload.resumedFromRunId !== undefined ? { resumedFromRunId: payload.resumedFromRunId } : {}),
157
+ ...(payload.origin !== undefined ? { origin: payload.origin } : {}),
154
158
  ...(payload.resumeFromStageId !== undefined ? { resumeFromStageId: payload.resumeFromStageId } : {}),
155
159
  ...(payload.accumulatedDurationMs !== undefined ? { accumulatedDurationMs: payload.accumulatedDurationMs } : {}),
156
160
  ts: payload.ts,
@@ -0,0 +1,75 @@
1
+ import { effectiveRunStatus } from "./returned-run-status.js";
2
+ import type { RunSnapshot, RunStatus } from "./store-types.js";
3
+
4
+ /** The status represented by a run's primary indicator. */
5
+ export type RunIndicatorStatus = RunStatus | "awaiting_input";
6
+
7
+ const TERMINAL_OR_BLOCKED = new Set<RunStatus>(["completed", "failed", "killed", "cancelled", "skipped", "blocked"]);
8
+
9
+ /**
10
+ * Resolve the status represented by a run's primary indicator.
11
+ *
12
+ * A live run with a pending run/stage prompt is awaiting input. When the
13
+ * caller supplies the complete run collection, pending prompts in hidden
14
+ * nested descendants are attributed to the visible ancestor by following
15
+ * `rootRunId` and `parentRunId`. Effective terminal and blocked statuses are
16
+ * authoritative, even when a stale prompt marker remains in a snapshot.
17
+ */
18
+ export function runIndicatorStatus(run: RunSnapshot, allRuns: readonly RunSnapshot[] = [run]): RunIndicatorStatus {
19
+ const status = effectiveRunStatus(run);
20
+ if (TERMINAL_OR_BLOCKED.has(status)) return status;
21
+ if (runHasPendingInput(run)) return "awaiting_input";
22
+
23
+ const runsById = new Map(allRuns.map((candidate) => [candidate.id, candidate]));
24
+ for (const candidate of allRuns) {
25
+ if (candidate.id === run.id || !runBelongsTo(candidate, run, runsById)) continue;
26
+ const candidateStatus = effectiveRunStatus(candidate);
27
+ if (!TERMINAL_OR_BLOCKED.has(candidateStatus) && runHasPendingInput(candidate)) return "awaiting_input";
28
+ }
29
+ return status;
30
+ }
31
+
32
+ /**
33
+ * Precompute the indicator status of each listed run against the complete
34
+ * run collection. The result is plain serializable data, so a surface whose
35
+ * payload is persisted and re-rendered after a session restore (e.g. the
36
+ * `/workflow status` chat entry) keeps hidden-descendant prompt attribution
37
+ * without serializing the hidden run snapshots themselves.
38
+ */
39
+ export function resolveRunIndicatorStatuses(
40
+ runs: readonly RunSnapshot[],
41
+ allRuns: readonly RunSnapshot[],
42
+ ): Readonly<Record<string, RunIndicatorStatus>> {
43
+ const statuses: Record<string, RunIndicatorStatus> = {};
44
+ for (const run of runs) statuses[run.id] = runIndicatorStatus(run, allRuns);
45
+ return statuses;
46
+ }
47
+
48
+ function runHasPendingInput(run: RunSnapshot): boolean {
49
+ if (run.pendingPrompt !== undefined) return true;
50
+ return run.stages.some(
51
+ (stage) =>
52
+ stage.status === "awaiting_input" ||
53
+ stage.awaitingInputSince !== undefined ||
54
+ stage.pendingPrompt !== undefined ||
55
+ stage.inputRequest !== undefined,
56
+ );
57
+ }
58
+
59
+ function runBelongsTo(
60
+ candidate: RunSnapshot,
61
+ ancestor: RunSnapshot,
62
+ runsById: ReadonlyMap<string, RunSnapshot>,
63
+ ): boolean {
64
+ if (candidate.rootRunId === ancestor.id) return true;
65
+
66
+ const visited = new Set<string>();
67
+ let current: RunSnapshot | undefined = candidate;
68
+ while (current !== undefined && current.parentRunId !== undefined) {
69
+ if (current.parentRunId === ancestor.id) return true;
70
+ if (visited.has(current.id)) return false;
71
+ visited.add(current.id);
72
+ current = runsById.get(current.parentRunId);
73
+ }
74
+ return false;
75
+ }
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  PendingPrompt,
3
3
  PromptKind,
4
+ RunResumeSource,
4
5
  RunSnapshot,
5
6
  RunStatus,
6
7
  StageInputRequest,
@@ -9,6 +10,7 @@ import type {
9
10
  StoreSnapshot,
10
11
  ToolEvent,
11
12
  ToolNodeSnapshot,
13
+ WorkflowActor,
12
14
  WorkflowChildRunRef,
13
15
  WorkflowFailureCode,
14
16
  WorkflowFailureDisposition,
@@ -33,10 +35,30 @@ export interface RunEndMetadata {
33
35
  readonly exitReason?: string;
34
36
  }
35
37
 
38
+ export type { RunResumeSource, WorkflowActor } from "./store-types.js";
39
+
36
40
  export interface RunPauseMetadata {
37
41
  readonly resumable?: boolean;
38
42
  readonly exitReason?: string;
43
+ /** Who paused or quit this run. Omitted for internal engine pauses. */
44
+ readonly actor?: WorkflowActor;
45
+ }
46
+
47
+ export interface RunResumeMetadata {
48
+ /** Who resumed this run. Omitted for internal engine resumes. */
49
+ readonly actor?: WorkflowActor;
50
+ /**
51
+ * Required so a new call site cannot silently start notifying: every caller
52
+ * states its own path, and only `run_control` is user-visible.
53
+ */
54
+ readonly source: RunResumeSource;
55
+ }
56
+
57
+ /** Attribution for a stage-scoped pause or resume. */
58
+ export interface StageControlMetadata {
59
+ readonly actor?: WorkflowActor;
39
60
  }
61
+
40
62
  export interface RunBlockedMetadata extends RunEndMetadata {
41
63
  readonly failureRecoverability: "recoverable";
42
64
  readonly failedStageId: string;
@@ -214,17 +236,31 @@ export interface Store {
214
236
  recordStageInputRequest(runId: string, stageId: string, request: StageInputRequest): boolean;
215
237
  /** Clear a stage's brokered structured-prompt descriptor. */
216
238
  clearStageInputRequest(runId: string, stageId: string): boolean;
217
- /** Mark a stage as `paused` and record `pausedAt`. */
218
- recordStagePaused(runId: string, stageId: string, pausedAt?: number): boolean;
219
- /** Clear `paused`/`blocked` state on a stage and record `resumedAt`. */
220
- recordStageResumed(runId: string, stageId: string, resumedAt?: number): boolean;
239
+ /**
240
+ * Mark a stage as `paused` and record `pausedAt`. Metadata on an already-paused
241
+ * stage records attribution alone, so a control path can claim a pause the
242
+ * engine already published.
243
+ */
244
+ recordStagePaused(runId: string, stageId: string, pausedAt?: number, metadata?: StageControlMetadata): boolean;
245
+ /**
246
+ * Clear `paused`/`blocked` state on a stage and record `resumedAt`. Metadata on
247
+ * an already-running stage records attribution alone.
248
+ */
249
+ recordStageResumed(runId: string, stageId: string, resumedAt?: number, metadata?: StageControlMetadata): boolean;
221
250
  recordStageBlocked(runId: string, stageId: string, blockedBy: string): boolean;
222
251
  recordStageUnblocked(runId: string, stageId: string): boolean;
223
252
  recordStageNotice(runId: string, stageId: string, notice: StageNotice): boolean;
224
- /** Mark a run as `paused`. Optional metadata can annotate resumable quit/detach state. */
253
+ /** Mark a run as `paused`. Optional metadata can annotate resumable quit/detach state and attribution. */
225
254
  recordRunPaused(runId: string, pausedAt?: number, metadata?: RunPauseMetadata): boolean;
226
- /** Restore a run from `paused` back to `running`. */
227
- recordRunResumed(runId: string, resumedAt?: number): boolean;
255
+ /**
256
+ * Restore a run from `paused` back to `running`.
257
+ *
258
+ * Metadata states which path resumed the run. A run-control resume records its
259
+ * attribution even when an inner engine path already flipped the status, which
260
+ * is the normal order: stage control and the acknowledgement pass both resume
261
+ * the run before public run control returns.
262
+ */
263
+ recordRunResumed(runId: string, resumedAt?: number, metadata?: RunResumeMetadata): boolean;
228
264
  /** Drop every run and notice. */
229
265
  clear(): void;
230
266
  snapshot(): StoreSnapshot;
@@ -6,9 +6,15 @@ import {
6
6
  type StoreContext,
7
7
  TERMINAL_STATUSES,
8
8
  } from "./store-internal.js";
9
- import type { RunBlockedMetadata, RunEndMetadata, RunPauseMetadata, Store } from "./store-public-types.js";
9
+ import type {
10
+ RunBlockedMetadata,
11
+ RunEndMetadata,
12
+ RunPauseMetadata,
13
+ RunResumeMetadata,
14
+ Store,
15
+ } from "./store-public-types.js";
10
16
  import type { RunSnapshot, RunStatus, StoreSnapshot, WorkflowNotice } from "./store-types.js";
11
- import { accumulatePausedDurationMs, elapsedRunMs } from "./timing.js";
17
+ import { accumulatePausedDurationMs, elapsedRunMs, nextControlTimestamp } from "./timing.js";
12
18
  import type { WorkflowOutputValues } from "./types.js";
13
19
 
14
20
  type RunStoreMethods = Pick<
@@ -181,32 +187,60 @@ export function createRunStoreMethods(context: StoreContext): RunStoreMethods {
181
187
  if (!run) return false;
182
188
  if (TERMINAL_STATUSES.has(run.status)) return false;
183
189
  const wasPaused = run.status === "paused";
184
- const enteringQuit = metadata?.exitReason === "quit" && run.exitReason !== "quit";
185
190
  if (!wasPaused) {
186
191
  run.status = "paused";
187
- run.pausedAt = pausedAt ?? Date.now();
192
+ run.pausedAt = nextControlTimestamp(pausedAt, run.resumedAt);
188
193
  run.resumedAt = undefined;
194
+ delete run.pauseActor;
195
+ delete run.resumeActor;
196
+ delete run.resumeSource;
189
197
  }
190
198
  if (metadata?.resumable !== undefined) run.resumable = metadata.resumable;
191
199
  if (metadata?.exitReason !== undefined) run.exitReason = metadata.exitReason;
192
- if (enteringQuit) run.quitAt = Date.now();
200
+ if (metadata?.exitReason === "quit" && run.quitAt === undefined)
201
+ run.quitAt = nextControlTimestamp(undefined, run.pausedAt);
202
+ // A control path claims the stop it just performed, even when the engine
203
+ // already published the paused state on its way there.
204
+ if (metadata?.actor !== undefined) run.pauseActor = metadata.actor;
193
205
  if (wasPaused && metadata === undefined) return false;
194
206
  context.bumpAndNotify();
195
207
  return true;
196
208
  },
197
209
 
198
- recordRunResumed(runId: string, resumedAt?: number): boolean {
210
+ recordRunResumed(runId: string, resumedAt?: number, metadata?: RunResumeMetadata): boolean {
199
211
  const run = context.findRun(runId);
200
212
  if (!run) return false;
201
213
  if (TERMINAL_STATUSES.has(run.status)) return false;
202
- if (run.status !== "paused") return false;
203
- const resumedTs = resumedAt ?? Date.now();
214
+ // Run control is the last step of a resume, by which point stage control
215
+ // or the acknowledgement pass has usually flipped the status already.
216
+ // Recording its attribution anyway is what distinguishes a deliberate
217
+ // resume from the engine continuing work on its own.
218
+ const claimsRunControl = metadata?.source === "run_control";
219
+ if (run.status !== "paused") {
220
+ if (!claimsRunControl || run.status !== "running" || run.resumedAt === undefined) return false;
221
+ if (run.resumeSource === "run_control" && run.resumeActor === metadata?.actor) return false;
222
+ run.resumeSource = "run_control";
223
+ if (metadata?.actor === undefined) delete run.resumeActor;
224
+ else run.resumeActor = metadata.actor;
225
+ context.bumpAndNotify();
226
+ return false;
227
+ }
228
+ const resumedTs = nextControlTimestamp(resumedAt, run.pausedAt);
204
229
  run.status = "running";
205
230
  run.pausedDurationMs = accumulatePausedDurationMs(run.pausedDurationMs, run.pausedAt, resumedTs);
206
231
  run.resumedAt = resumedTs;
207
232
  run.pausedAt = undefined;
208
233
  delete run.quitAt;
209
234
  delete run.exitReason;
235
+ delete run.pauseActor;
236
+ if (metadata === undefined) {
237
+ delete run.resumeActor;
238
+ delete run.resumeSource;
239
+ } else {
240
+ run.resumeSource = metadata.source;
241
+ if (metadata.actor === undefined) delete run.resumeActor;
242
+ else run.resumeActor = metadata.actor;
243
+ }
210
244
  context.bumpAndNotify();
211
245
  return true;
212
246
  },
@@ -6,10 +6,10 @@ import {
6
6
  type StoreContext,
7
7
  TERMINAL_STATUSES,
8
8
  } from "./store-internal.js";
9
- import type { Store } from "./store-public-types.js";
9
+ import type { StageControlMetadata, Store } from "./store-public-types.js";
10
10
  import { nextExecutionOrder } from "./store-tool-node-methods.js";
11
11
  import type { StageInputRequest, StageNotice, StageSnapshot, ToolEvent, WorkflowChildRunRef } from "./store-types.js";
12
- import { accumulatePausedDurationMs } from "./timing.js";
12
+ import { accumulatePausedDurationMs, nextControlTimestamp } from "./timing.js";
13
13
 
14
14
  type StageStoreMethods = Pick<
15
15
  Store,
@@ -269,29 +269,49 @@ export function createStageStoreMethods(context: StoreContext): StageStoreMethod
269
269
  return true;
270
270
  },
271
271
 
272
- recordStagePaused(runId: string, stageId: string, pausedAt?: number): boolean {
272
+ recordStagePaused(runId: string, stageId: string, pausedAt?: number, metadata?: StageControlMetadata): boolean {
273
273
  const run = context.findRun(runId);
274
274
  if (!run) return false;
275
275
  if (TERMINAL_STATUSES.has(run.status)) return false;
276
276
  const stage = context.findStage(run, stageId);
277
277
  if (!stage) return false;
278
- if (cannotPause(stage.status)) return false;
278
+ if (cannotPause(stage.status)) {
279
+ // Attribution-only claim: the control path paused this stage through
280
+ // the engine and is naming itself afterwards.
281
+ if (stage.status !== "paused" || metadata?.actor === undefined) return false;
282
+ if (stage.pauseActor === metadata.actor) return false;
283
+ stage.pauseActor = metadata.actor;
284
+ context.bumpAndNotify();
285
+ return false;
286
+ }
279
287
  stage.status = "paused";
280
- stage.pausedAt = pausedAt ?? Date.now();
288
+ stage.pausedAt = nextControlTimestamp(pausedAt, stage.resumedAt);
281
289
  stage.resumedAt = undefined;
282
290
  delete stage.awaitingInputSince;
291
+ delete stage.resumeActor;
292
+ if (metadata?.actor === undefined) delete stage.pauseActor;
293
+ else stage.pauseActor = metadata.actor;
283
294
  context.bumpAndNotify();
284
295
  return true;
285
296
  },
286
297
 
287
- recordStageResumed(runId: string, stageId: string, resumedAt?: number): boolean {
298
+ recordStageResumed(runId: string, stageId: string, resumedAt?: number, metadata?: StageControlMetadata): boolean {
288
299
  const run = context.findRun(runId);
289
300
  if (!run) return false;
290
301
  if (TERMINAL_STATUSES.has(run.status)) return false;
291
302
  const stage = context.findStage(run, stageId);
292
303
  if (!stage) return false;
293
- if (stage.status !== "paused" && stage.status !== "blocked") return false;
294
- const resumedTs = resumedAt ?? Date.now();
304
+ if (stage.status !== "paused" && stage.status !== "blocked") {
305
+ // Attribution-only claim, mirroring the paused path above.
306
+ if (stage.status !== "running" || stage.resumedAt === undefined || metadata?.actor === undefined) {
307
+ return false;
308
+ }
309
+ if (stage.resumeActor === metadata.actor) return false;
310
+ stage.resumeActor = metadata.actor;
311
+ context.bumpAndNotify();
312
+ return false;
313
+ }
314
+ const resumedTs = nextControlTimestamp(resumedAt, stage.pausedAt);
295
315
  stage.status = "running";
296
316
  if (stage.startedAt !== undefined) {
297
317
  stage.pausedDurationMs = accumulatePausedDurationMs(stage.pausedDurationMs, stage.pausedAt, resumedTs);
@@ -300,6 +320,9 @@ export function createStageStoreMethods(context: StoreContext): StageStoreMethod
300
320
  stage.pausedAt = undefined;
301
321
  delete stage.blockedByStageId;
302
322
  delete stage.awaitingInputSince;
323
+ delete stage.pauseActor;
324
+ if (metadata?.actor === undefined) delete stage.resumeActor;
325
+ else stage.resumeActor = metadata.actor;
303
326
  context.bumpAndNotify();
304
327
  return true;
305
328
  },
@@ -261,8 +261,27 @@ export interface StageSnapshot {
261
261
  pausedAt?: number;
262
262
  /** Timestamp recorded on the most recent resume from a paused state. */
263
263
  resumedAt?: number;
264
+ /** Who paused this stage. Set only by a deliberate control action. */
265
+ pauseActor?: WorkflowActor;
266
+ /** Who resumed this stage. Set only by a deliberate control action. */
267
+ resumeActor?: WorkflowActor;
264
268
  }
265
269
 
270
+ /**
271
+ * Who performed a workflow lifecycle action. A run's `origin` answers "who
272
+ * launched it"; an actor on a pause, quit, or resume answers "who did this one
273
+ * thing". They differ routinely — the agent starts a run and the user quits it.
274
+ */
275
+ export type WorkflowActor = "user" | "agent";
276
+
277
+ /**
278
+ * Which code path resumed a run. Only `run_control` is a deliberate control
279
+ * action; the rest continue work already in progress — answering a
280
+ * human-in-the-loop prompt, per-stage control, and the acknowledgement pass —
281
+ * and must never be reported as a lifecycle event of their own.
282
+ */
283
+ export type RunResumeSource = "run_control" | "prompt_answer" | "stage_control" | "acknowledgement";
284
+
266
285
  export interface RunSnapshot {
267
286
  readonly id: string;
268
287
  readonly name: string;
@@ -286,6 +305,21 @@ export interface RunSnapshot {
286
305
  pausedAt?: number;
287
306
  /** Timestamp when the run entered resumable quit state; display-only expiry marker. */
288
307
  quitAt?: number;
308
+ /**
309
+ * Who paused or quit this run, and who resumed it. A control path records
310
+ * these; the engine's own pauses and resumes leave them unset, which is what
311
+ * keeps an internal continuation from being reported as a user action.
312
+ */
313
+ pauseActor?: WorkflowActor;
314
+ resumeActor?: WorkflowActor;
315
+ /** Which path performed the most recent resume. */
316
+ resumeSource?: RunResumeSource;
317
+ /**
318
+ * Who launched this run. Set once at dispatch and inherited by a continuation
319
+ * from the run it continues. Absent on legacy and restored runs that never
320
+ * recorded it, in which case attribution is omitted rather than guessed.
321
+ */
322
+ origin?: WorkflowActor;
289
323
  /** Timestamp recorded on the most recent resume from a paused state. */
290
324
  resumedAt?: number;
291
325
  result?: WorkflowOutputValues;
@@ -19,6 +19,16 @@ interface RunTimerSnapshot {
19
19
  function nonNegative(ms: number): number {
20
20
  return Math.max(0, ms);
21
21
  }
22
+ /**
23
+ * Return an occurrence timestamp that remains ordered when a clock tick is
24
+ * shared by two deliberate control transitions. Lifecycle notice keys use
25
+ * these timestamps as occurrence identities, so a later transition must not
26
+ * reuse the prior transition's key.
27
+ */
28
+ export function nextControlTimestamp(requestedAt: number | undefined, previousAt: number | undefined): number {
29
+ const candidate = requestedAt ?? Date.now();
30
+ return previousAt === undefined || candidate > previousAt ? candidate : previousAt + 1;
31
+ }
22
32
 
23
33
  export function rebasedStageStartedAt(accumulatedDurationMs: number | undefined, resumedAt: number): number {
24
34
  return resumedAt - nonNegative(accumulatedDurationMs ?? 0);
@@ -27,6 +27,7 @@
27
27
 
28
28
  import type { ExtensionAPI } from "../extension/index.js";
29
29
  import type { RunDetail } from "../runs/background/status.js";
30
+ import type { RunIndicatorStatus } from "../shared/run-indicator-status.js";
30
31
  import type { RunSnapshot } from "../shared/store-types.js";
31
32
  import type { WorkflowInputValues } from "../shared/types.js";
32
33
  import { renderDispatchConfirm } from "./dispatch-confirm.js";
@@ -67,6 +68,15 @@ export interface DispatchPayload {
67
68
  export interface StatusPayload {
68
69
  kind: "status";
69
70
  runs: readonly RunSnapshot[];
71
+ /**
72
+ * Emit-time indicator status per visible run id, resolved against the
73
+ * complete point-in-time run collection (including hidden nested
74
+ * descendants). Persisted with the payload because chat entries are
75
+ * re-rendered from `details` after a session restore, where the full
76
+ * collection is gone; storing only the derived statuses keeps hidden
77
+ * run snapshots out of the serialized message.
78
+ */
79
+ indicatorStatuses?: Readonly<Record<string, RunIndicatorStatus>>;
70
80
  }
71
81
 
72
82
  /** Workflow catalogue after `/workflow list`. */
@@ -164,7 +174,12 @@ export function renderChatSurfacePlainText(
164
174
  return [rendered, `run id: ${payload.runId}`, `inputs: ${formatPlainRecord(payload.inputs)}`].join("\n");
165
175
  }
166
176
  case "status": {
167
- const rendered = renderStatusList(payload.runs, { width, now, ...themed });
177
+ const rendered = renderStatusList(payload.runs, {
178
+ width,
179
+ now,
180
+ ...themed,
181
+ indicatorStatuses: payload.indicatorStatuses,
182
+ });
168
183
  if (payload.runs.length === 0) return rendered;
169
184
  return [
170
185
  rendered,
@@ -288,7 +303,12 @@ function renderPayload(payload: ChatSurfacePayload, theme: GraphTheme, width: nu
288
303
  width,
289
304
  });
290
305
  case "status":
291
- return renderStatusList(payload.runs, { theme, width, now });
306
+ return renderStatusList(payload.runs, {
307
+ theme,
308
+ width,
309
+ now,
310
+ indicatorStatuses: payload.indicatorStatuses,
311
+ });
292
312
  case "list":
293
313
  return renderWorkflowList(payload.entries, { theme, width });
294
314
  case "detail":
@@ -34,5 +34,5 @@ export function renderSessionList(runs: readonly RunSnapshot[], opts: SessionLis
34
34
  toolNodes: graph.tools.map((tool) => structuredClone(tool)),
35
35
  };
36
36
  });
37
- return renderStatusList(filtered, { theme: opts.theme, now });
37
+ return renderStatusList(filtered, { theme: opts.theme, now, allRuns: runs });
38
38
  }
@@ -25,7 +25,7 @@
25
25
 
26
26
  import type { PiCustomComponent, PiCustomOverlayFactoryTui, PiCustomOverlayFunction } from "../extension/wiring.js";
27
27
  import type { Store } from "../shared/store.js";
28
- import { readGraphStoreSnapshot, subscribeStoreInvalidation } from "../shared/store-observation.js";
28
+ import { subscribeStoreInvalidation } from "../shared/store-observation.js";
29
29
  import type { GraphTheme } from "./graph-theme.js";
30
30
  import {
31
31
  createSessionPickerResumeCandidateCache,
@@ -81,6 +81,7 @@ export function openSessionPicker(
81
81
  const state = createSessionPickerState();
82
82
  let settled = false;
83
83
  let unsubscribe: (() => void) | null = null;
84
+ let pickerRevision = 0;
84
85
 
85
86
  const resumeCandidateCache = createSessionPickerResumeCandidateCache();
86
87
 
@@ -100,16 +101,16 @@ export function openSessionPicker(
100
101
  };
101
102
  // Re-render on store changes so newly-started runs appear and
102
103
  // status icons refresh without the user having to press a key.
103
- // Read one memoized payload-free projection per render rather than
104
- // caching a snapshot from the subscription callback: the
105
- // invalidation channel carries no snapshot, so a cached one would
106
- // go stale and newly-started runs would never appear.
104
+ // The picker only needs live run references for rendering; a small local
105
+ // revision keeps resume-probe caching coherent without cloning the store.
107
106
  const selectRows = (): ReturnType<typeof selectRunsForPicker> => {
108
- const snapshot = readGraphStoreSnapshot(store);
107
+ const liveRuns = store.runs();
109
108
  const resumeCandidateLookup =
110
- intent === "resume" ? resumeCandidateCache({ ...snapshot, runs: store.runs() }) : undefined;
109
+ intent === "resume"
110
+ ? resumeCandidateCache({ runs: liveRuns, notices: store.notices(), version: pickerRevision })
111
+ : undefined;
111
112
  return selectRunsForPicker(
112
- snapshot.runs,
113
+ liveRuns,
113
114
  state.query,
114
115
  state.includeAll,
115
116
  Date.now(),
@@ -117,11 +118,14 @@ export function openSessionPicker(
117
118
  resumeCandidateLookup,
118
119
  );
119
120
  };
120
- unsubscribe = subscribeStoreInvalidation(store, () => tui.requestRender?.());
121
+ unsubscribe = subscribeStoreInvalidation(store, () => {
122
+ pickerRevision++;
123
+ tui.requestRender?.();
124
+ });
121
125
  return {
122
126
  render: (width: number) => {
123
127
  const rows = selectRows();
124
- return renderSessionPicker({ width, theme, rows, state });
128
+ return renderSessionPicker({ width, theme, rows, state, allRuns: store.runs() });
125
129
  },
126
130
  handleInput: (data: string) => {
127
131
  const rows = selectRows();