@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
@@ -17,6 +17,7 @@ import type {
17
17
  StageSnapshot,
18
18
  StageStatus,
19
19
  StoreSnapshot,
20
+ WorkflowActor,
20
21
  } from "../shared/store-types.js";
21
22
  import { deriveGraphThemeFromPiTheme, type GraphTheme } from "../tui/graph-theme.js";
22
23
  import { renderWorkflowNoticeCard, type WorkflowNoticeTone } from "../tui/workflow-notice-card.js";
@@ -26,13 +27,33 @@ import { createLifecycleNoticeDelivery } from "./lifecycle-notification-delivery
26
27
  export const LIFECYCLE_NOTICE_CUSTOM_TYPE = "workflows:lifecycle-notice";
27
28
  export const LIFECYCLE_NOTICE_SNIPPET_LIMIT = 240;
28
29
 
29
- export type WorkflowLifecycleNoticeKind = "completed" | "failed" | "blocked" | "awaiting_input";
30
+ export type WorkflowLifecycleNoticeKind =
31
+ | "started"
32
+ | "completed"
33
+ | "failed"
34
+ | "blocked"
35
+ | "awaiting_input"
36
+ | "paused"
37
+ | "quit"
38
+ | "resumed";
39
+
40
+ /**
41
+ * Control-action kinds. Unlike terminal outcomes these are reversible and can
42
+ * repeat, so their dedupe keys carry the occurrence timestamp rather than the
43
+ * run id alone, and they are reported only when a control path attributed the
44
+ * action to a user.
45
+ */
46
+ type WorkflowControlNoticeKind = "started" | "paused" | "quit" | "resumed";
30
47
 
31
48
  export const WORKFLOW_LIFECYCLE_NOTICE_KINDS = [
49
+ "started",
32
50
  "completed",
33
51
  "failed",
34
52
  "blocked",
35
53
  "awaiting_input",
54
+ "paused",
55
+ "quit",
56
+ "resumed",
36
57
  ] as const satisfies readonly WorkflowLifecycleNoticeKind[];
37
58
 
38
59
  export interface WorkflowLifecycleNotificationConfig {
@@ -57,6 +78,14 @@ export interface WorkflowLifecycleNoticeDetails {
57
78
  readonly toolName?: string;
58
79
  readonly durationMs?: number;
59
80
  readonly active?: boolean;
81
+ /** Whether a quit run advertises itself as resumable. */
82
+ readonly resumable?: boolean;
83
+ /** Who performed this lifecycle action, when a control path attributed it. */
84
+ readonly actor?: WorkflowActor;
85
+ /** Who launched the run, when the launch was attributed. */
86
+ readonly origin?: WorkflowActor;
87
+ /** Source run this run continues, for a resumed continuation with a fresh id. */
88
+ readonly continuedFromRunId?: string;
60
89
  readonly createdAt: number;
61
90
  }
62
91
 
@@ -118,6 +147,12 @@ export function seedWorkflowLifecycleNotificationState(
118
147
  state.deliveredTerminalRuns.add(key);
119
148
  }
120
149
  }
150
+ for (const occurrence of controlOccurrences(run)) {
151
+ const controlKey = controlOccurrenceKey(run, occurrence);
152
+ if (!state.pendingTerminalRuns.has(controlKey) && !state.retryableTerminalRuns.has(controlKey)) {
153
+ state.deliveredTerminalRuns.add(controlKey);
154
+ }
155
+ }
121
156
  if (run.pendingPrompt !== undefined) {
122
157
  state.deliveredInputPrompts.add(runAwaitingInputKey(run.id, run.pendingPrompt));
123
158
  }
@@ -227,6 +262,26 @@ export function installWorkflowLifecycleNotifications(options: WorkflowLifecycle
227
262
  delivery.deliver(key, makeTerminalNotice(run, kind));
228
263
  };
229
264
 
265
+ const emitControlNoticesOnce = (run: RunSnapshot): void => {
266
+ for (const occurrence of controlOccurrences(run)) {
267
+ if (!notifyOn.has(occurrence.kind)) continue;
268
+ const key = controlOccurrenceKey(run, occurrence);
269
+ if (
270
+ state.deliveredTerminalRuns.has(key) ||
271
+ state.pendingTerminalRuns.has(key) ||
272
+ state.retryableTerminalRuns.has(key)
273
+ )
274
+ continue;
275
+ if (state.suppressionDepth > 0) {
276
+ state.deliveredTerminalRuns.add(key);
277
+ state.retryableTerminalRuns.delete(key);
278
+ state.retryableTerminalNotices.delete(key);
279
+ continue;
280
+ }
281
+ delivery.deliver(key, makeControlNotice(run, occurrence));
282
+ }
283
+ };
284
+
230
285
  const emitStageAwaitingInputNoticeOnce = (run: RunSnapshot, stage: StageSnapshot): void => {
231
286
  if (stage.status !== "awaiting_input") return;
232
287
 
@@ -256,6 +311,7 @@ export function installWorkflowLifecycleNotifications(options: WorkflowLifecycle
256
311
  emitTerminalNoticeOnce(run, "completed");
257
312
  emitTerminalNoticeOnce(run, "failed");
258
313
  emitTerminalNoticeOnce(run, "blocked");
314
+ emitControlNoticesOnce(run);
259
315
 
260
316
  if (!notifyOn.has("awaiting_input")) continue;
261
317
  emitRunAwaitingInputNoticeOnce(run);
@@ -298,20 +354,51 @@ export function registerLifecycleNoticeRenderer(
298
354
 
299
355
  export function formatWorkflowLifecycleNoticeText(details: WorkflowLifecycleNoticeDetails): string {
300
356
  const workflowName = escapeQuotedText(details.workflowName);
357
+ // "which you started" / "which the user started". Omitted entirely when the
358
+ // run carries no recorded origin, rather than guessing one.
359
+ const origin =
360
+ details.origin === undefined
361
+ ? ""
362
+ : details.origin === "agent"
363
+ ? ", which you started"
364
+ : ", which the user started";
365
+ const actor = details.actor === "agent" ? "You" : "The user";
366
+ if (details.kind === "started") {
367
+ return `▶ ${actor} started workflow "${workflowName}" (run ${details.runId}). It is running in the background; you will be notified when it completes.`;
368
+ }
301
369
  if (details.kind === "completed") {
302
- return `✓ Workflow "${workflowName}" completed (run ${details.runId}). Inspect: /workflow status ${details.runId}`;
370
+ return `✓ Workflow "${workflowName}" completed (run ${details.runId})${origin}. Inspect: /workflow status ${details.runId}`;
303
371
  }
304
372
  if (details.kind === "failed") {
305
373
  const stage = details.stageName ?? details.failedStageId ?? details.stageId;
306
374
  const tool = lifecycleToolOrigin(details);
307
- const originText = stage ? `, stage ${stage}` : tool !== undefined ? `, tool ${tool}` : "";
375
+ const failureSite = stage ? `, stage ${stage}` : tool !== undefined ? `, tool ${tool}` : "";
308
376
  const errorText = details.error ? `: ${details.error}` : "";
309
- return `✗ Workflow "${workflowName}" failed (run ${details.runId}${originText})${errorText}. Inspect: /workflow status ${details.runId}`;
377
+ return `✗ Workflow "${workflowName}" failed (run ${details.runId}${failureSite})${origin}${errorText}. Inspect: /workflow status ${details.runId}`;
310
378
  }
311
379
  if (details.kind === "blocked") {
312
380
  const errorText = details.error ? `: ${details.error}` : "";
313
381
  const stateText = details.active === true ? "is blocked" : "ended blocked";
314
- return `! Workflow "${workflowName}" ${stateText} (run ${details.runId})${errorText}. Inspect: /workflow status ${details.runId}`;
382
+ return `! Workflow "${workflowName}" ${stateText} (run ${details.runId})${origin}${errorText}. Inspect: /workflow status ${details.runId}`;
383
+ }
384
+ if (details.kind === "paused" || details.kind === "quit") {
385
+ const stopStage = details.stageName ?? details.stageId;
386
+ // "(run 8c31), which you started, at stage review" — the stage clause takes a
387
+ // separating comma only when an origin clause precedes it.
388
+ const stageText = stopStage ? `${origin === "" ? "" : ","} at stage ${stopStage}` : "";
389
+ const scopeText = details.scope === "stage" ? "stage of workflow" : "workflow";
390
+ const verb = details.kind === "paused" ? "paused" : "quit";
391
+ const noun = details.kind === "paused" ? "pause" : "stop";
392
+ const glyph = details.kind === "paused" ? "⏸" : "⏹";
393
+ return `${glyph} ${actor} ${verb} the ${scopeText} "${workflowName}" (run ${details.runId})${origin}${stageText}. This ${noun} was deliberate and user-requested; do not resume it or take over the work unless asked. Resume: /workflow resume ${details.runId}`;
394
+ }
395
+ if (details.kind === "resumed") {
396
+ const resumedStage = details.stageName ?? details.stageId;
397
+ const stageText = resumedStage ? `${origin === "" ? "" : ","} at stage ${resumedStage}` : "";
398
+ const scopeText = details.scope === "stage" ? "stage of workflow" : "workflow";
399
+ const continues =
400
+ details.continuedFromRunId === undefined ? "" : `, continuing run ${details.continuedFromRunId}`;
401
+ return `▶ ${actor} resumed the ${scopeText} "${workflowName}" (run ${details.runId}${continues})${origin}${stageText}. It is running again in the background.`;
315
402
  }
316
403
  const prompt = details.promptMessage ? ` Prompt: ${details.promptMessage}` : "";
317
404
  if (details.scope === "run") {
@@ -349,10 +436,42 @@ function makeTerminalNotice(
349
436
  ...(failedToolNodeId !== undefined ? { toolNodeId: failedToolNodeId } : {}),
350
437
  ...(failedTool !== undefined ? { toolName: failedTool.name } : {}),
351
438
  ...(run.durationMs !== undefined ? { durationMs: run.durationMs } : {}),
439
+ // Attribution renders on every kind, and is omitted for a run that never
440
+ // recorded an origin rather than guessed at.
441
+ ...(run.origin !== undefined ? { origin: run.origin } : {}),
352
442
  createdAt: lifecycleOccurrenceAt(run, kind) ?? Date.now(),
353
443
  };
354
444
  }
355
445
 
446
+ function makeControlNotice(run: RunSnapshot, occurrence: ControlOccurrence): WorkflowLifecycleNoticeDetails {
447
+ const stage = occurrence.stage ?? restingStage(run);
448
+ const elapsedMs = run.durationMs ?? (occurrence.at >= run.startedAt ? occurrence.at - run.startedAt : undefined);
449
+ return {
450
+ kind: occurrence.kind,
451
+ scope: occurrence.scope,
452
+ runId: run.id,
453
+ workflowName: run.name,
454
+ status: occurrence.stage !== undefined ? occurrence.stage.status : run.status,
455
+ ...(stage !== undefined ? { stageId: stage.id, stageName: stage.name } : {}),
456
+ ...(elapsedMs !== undefined ? { durationMs: elapsedMs } : {}),
457
+ ...(occurrence.kind === "quit" && run.resumable !== undefined ? { resumable: run.resumable } : {}),
458
+ ...(occurrence.actor !== undefined ? { actor: occurrence.actor } : {}),
459
+ // The origin clause is omitted entirely for a run that never recorded one.
460
+ ...(occurrence.kind !== "started" && run.origin !== undefined ? { origin: run.origin } : {}),
461
+ ...(occurrence.kind === "resumed" && run.resumedFromRunId !== undefined
462
+ ? { continuedFromRunId: run.resumedFromRunId }
463
+ : {}),
464
+ createdAt: occurrence.at,
465
+ };
466
+ }
467
+
468
+ /** The stage a run currently rests on, preferring an explicitly paused one. */
469
+ function restingStage(run: RunSnapshot): StageSnapshot | undefined {
470
+ return (
471
+ run.stages.find((stage) => stage.status === "paused") ?? run.stages.find((stage) => stage.status === "running")
472
+ );
473
+ }
474
+
356
475
  function warnLifecycleSendFailure(error: unknown): void {
357
476
  if (process.env.ATOMIC_WORKFLOW_DEBUG !== "1") return;
358
477
  const message = error instanceof Error ? error.message : String(error);
@@ -400,6 +519,87 @@ function terminalRunKey(kind: "completed" | "failed" | "blocked", run: RunSnapsh
400
519
  return occurrence === undefined ? `${kind}:${run.id}` : `${kind}:${run.id}:${occurrence}`;
401
520
  }
402
521
 
522
+ /** One deliberate control action a run currently carries. */
523
+ interface ControlOccurrence {
524
+ readonly kind: WorkflowControlNoticeKind;
525
+ readonly scope: "run" | "stage";
526
+ readonly at: number;
527
+ readonly actor: WorkflowActor;
528
+ readonly stage?: StageSnapshot;
529
+ }
530
+
531
+ /**
532
+ * Every control action this run currently carries that a user actually asked
533
+ * for.
534
+ *
535
+ * Attribution, not the status transition, is the trigger. A run passes through
536
+ * paused on its way to a quit, and the engine resumes a run whenever a
537
+ * human-in-the-loop prompt is answered or a stage control acknowledges; none of
538
+ * those record an actor, so none of them reach the chat. Only a control path
539
+ * that names its caller does, and it names exactly one scope per request, which
540
+ * is what keeps a stage card and a run card from reporting the same event.
541
+ */
542
+ function controlOccurrences(run: RunSnapshot): ControlOccurrence[] {
543
+ const occurrences: ControlOccurrence[] = [];
544
+ // Whether this run exists because someone resumed work, whoever asked for it.
545
+ // A durable resume reuses the original run id (issue #1498) and a failed-run
546
+ // continuation takes a fresh one; both re-enter `recordRunStart`, and neither
547
+ // is a new run to the user. Keying the start suppression on the resume itself
548
+ // rather than on who requested it is what keeps an agent-requested resume of a
549
+ // user-started run from being announced as a fresh launch.
550
+ const resumedRun = run.resumeSource === "run_control" || run.resumedFromRunId !== undefined;
551
+ const userResumed = run.resumeSource === "run_control" && run.resumeActor === "user";
552
+ // A resumed run that never paused in this session reports its start timestamp:
553
+ // the re-dispatch is the occurrence.
554
+ const continuation = userResumed && run.resumedAt === undefined;
555
+ if (run.origin === "user" && !resumedRun) {
556
+ occurrences.push({ kind: "started", scope: "run", at: run.startedAt, actor: "user" });
557
+ }
558
+ if (userResumed && run.status === "running") {
559
+ occurrences.push({
560
+ kind: "resumed",
561
+ scope: "run",
562
+ at: continuation ? run.startedAt : (run.resumedAt ?? run.startedAt),
563
+ actor: "user",
564
+ });
565
+ }
566
+ if (run.endedAt === undefined && run.status === "paused" && run.pauseActor === "user") {
567
+ if (run.exitReason === "quit") {
568
+ const quitAt = run.quitAt ?? run.pausedAt;
569
+ if (quitAt !== undefined) occurrences.push({ kind: "quit", scope: "run", at: quitAt, actor: "user" });
570
+ } else if (run.pausedAt !== undefined) {
571
+ occurrences.push({ kind: "paused", scope: "run", at: run.pausedAt, actor: "user" });
572
+ }
573
+ }
574
+ const runScoped = new Set(occurrences.map((occurrence) => occurrence.kind));
575
+ if (run.endedAt !== undefined) return occurrences;
576
+ for (const stage of run.stages) {
577
+ if (stage.status === "paused" && stage.pauseActor === "user" && stage.pausedAt !== undefined) {
578
+ if (!runScoped.has("paused") && !runScoped.has("quit")) {
579
+ occurrences.push({ kind: "paused", scope: "stage", at: stage.pausedAt, actor: "user", stage });
580
+ }
581
+ }
582
+ if (stage.status === "running" && stage.resumeActor === "user" && stage.resumedAt !== undefined) {
583
+ if (!runScoped.has("resumed")) {
584
+ occurrences.push({ kind: "resumed", scope: "stage", at: stage.resumedAt, actor: "user", stage });
585
+ }
586
+ }
587
+ }
588
+ return occurrences;
589
+ }
590
+
591
+ /**
592
+ * Control actions are repeatable, so the occurrence timestamp is part of the
593
+ * key: resuming clears `pausedAt`/`quitAt`, and the next pause mints a new key
594
+ * while repeated invalidations at one unchanged state reuse the delivered one.
595
+ */
596
+ function controlOccurrenceKey(run: RunSnapshot, occurrence: ControlOccurrence): string {
597
+ if (occurrence.scope === "stage" && occurrence.stage !== undefined) {
598
+ return `${occurrence.kind}:${run.id}:stage:${occurrence.stage.id}:${occurrence.at}`;
599
+ }
600
+ return `${occurrence.kind}:${run.id}:${occurrence.at}`;
601
+ }
602
+
403
603
  function awaitingInputKey(runId: string, stage: StageSnapshot): string {
404
604
  const promptId = stage.pendingPrompt?.id ?? stage.inputRequest?.id;
405
605
  if (promptId) return `awaiting_input:${runId}:stage:${stage.id}:${promptId}`;
@@ -439,38 +639,12 @@ function renderLifecycleNoticeCard(
439
639
  details: WorkflowLifecycleNoticeDetails,
440
640
  opts: { width: number; theme?: GraphTheme; fallbackText: string },
441
641
  ): string[] {
442
- const tone: WorkflowNoticeTone =
443
- details.kind === "failed"
444
- ? "error"
445
- : details.kind === "awaiting_input" || details.kind === "blocked"
446
- ? "warning"
447
- : "success";
448
- const title =
449
- details.kind === "failed"
450
- ? "WORKFLOW FAILED"
451
- : details.kind === "awaiting_input"
452
- ? "WORKFLOW INPUT"
453
- : details.kind === "blocked"
454
- ? "WORKFLOW BLOCKED"
455
- : "WORKFLOW COMPLETE";
456
- const glyph =
457
- details.kind === "failed"
458
- ? "✗"
459
- : details.kind === "awaiting_input"
460
- ? "?"
461
- : details.kind === "blocked"
462
- ? "!"
463
- : "✓";
642
+ const tone: WorkflowNoticeTone = lifecycleNoticeTone(details.kind);
643
+ const title = lifecycleNoticeTitle(details.kind);
644
+ const glyph = lifecycleNoticeGlyph(details.kind);
464
645
  const stage = details.stageName ?? details.failedStageId ?? details.stageId;
465
646
  const tool = stage === undefined ? lifecycleToolOrigin(details) : undefined;
466
- const headline =
467
- details.kind === "failed"
468
- ? `Workflow "${details.workflowName}" failed`
469
- : details.kind === "awaiting_input"
470
- ? `Workflow "${details.workflowName}" needs input`
471
- : details.kind === "blocked"
472
- ? `Workflow "${details.workflowName}" ${details.active === true ? "is blocked" : "ended blocked"}`
473
- : `Workflow "${details.workflowName}" completed`;
647
+ const headline = lifecycleNoticeHeadline(details);
474
648
  return renderWorkflowNoticeCard({
475
649
  title,
476
650
  glyph,
@@ -479,21 +653,112 @@ function renderLifecycleNoticeCard(
479
653
  fields: [
480
654
  { label: "workflow", value: details.workflowName },
481
655
  { label: "run", value: details.runId },
656
+ { label: "continues", value: details.continuedFromRunId },
482
657
  { label: "stage", value: stage },
483
658
  { label: "tool", value: tool },
659
+ { label: "actor", value: details.actor, tone: "muted" },
660
+ { label: "launched", value: details.origin, tone: "muted" },
484
661
  { label: "prompt", value: details.promptMessage, tone: "muted" },
485
662
  { label: "error", value: details.error, tone: "error" },
663
+ { label: "resumable", value: resumableFieldValue(details), tone: "muted" },
486
664
  { label: "duration", value: formatDurationMs(details.durationMs), tone: "muted" },
487
665
  ],
488
- hints: [
489
- details.kind === "awaiting_input" ? `/workflow connect ${details.runId}` : `/workflow status ${details.runId}`,
490
- ],
666
+ hints: [lifecycleNoticeHint(details)],
491
667
  fallbackText: opts.fallbackText,
492
668
  width: opts.width,
493
669
  ...(opts.theme ? { theme: opts.theme } : {}),
494
670
  });
495
671
  }
496
672
 
673
+ function lifecycleNoticeTone(kind: WorkflowLifecycleNoticeKind): WorkflowNoticeTone {
674
+ switch (kind) {
675
+ case "failed":
676
+ return "error";
677
+ case "blocked":
678
+ case "awaiting_input":
679
+ case "paused":
680
+ case "quit":
681
+ return "warning";
682
+ default:
683
+ return "success";
684
+ }
685
+ }
686
+
687
+ function lifecycleNoticeTitle(kind: WorkflowLifecycleNoticeKind): string {
688
+ switch (kind) {
689
+ case "started":
690
+ return "WORKFLOW STARTED";
691
+ case "failed":
692
+ return "WORKFLOW FAILED";
693
+ case "awaiting_input":
694
+ return "WORKFLOW INPUT";
695
+ case "blocked":
696
+ return "WORKFLOW BLOCKED";
697
+ case "paused":
698
+ return "WORKFLOW PAUSED";
699
+ case "quit":
700
+ return "WORKFLOW QUIT";
701
+ case "resumed":
702
+ return "WORKFLOW RESUMED";
703
+ default:
704
+ return "WORKFLOW COMPLETE";
705
+ }
706
+ }
707
+
708
+ function lifecycleNoticeGlyph(kind: WorkflowLifecycleNoticeKind): string {
709
+ switch (kind) {
710
+ case "failed":
711
+ return "✗";
712
+ case "awaiting_input":
713
+ return "?";
714
+ case "blocked":
715
+ return "!";
716
+ case "paused":
717
+ return "⏸";
718
+ case "quit":
719
+ return "⏹";
720
+ case "started":
721
+ case "resumed":
722
+ return "▶";
723
+ default:
724
+ return "✓";
725
+ }
726
+ }
727
+
728
+ function lifecycleNoticeHeadline(details: WorkflowLifecycleNoticeDetails): string {
729
+ const name = details.workflowName;
730
+ const scope = details.scope === "stage" ? "Stage of workflow" : "Workflow";
731
+ switch (details.kind) {
732
+ case "started":
733
+ return `Workflow "${name}" started`;
734
+ case "failed":
735
+ return `Workflow "${name}" failed`;
736
+ case "awaiting_input":
737
+ return `Workflow "${name}" needs input`;
738
+ case "blocked":
739
+ return `Workflow "${name}" ${details.active === true ? "is blocked" : "ended blocked"}`;
740
+ case "paused":
741
+ return `${scope} "${name}" paused`;
742
+ case "quit":
743
+ return `Workflow "${name}" quit`;
744
+ case "resumed":
745
+ return `${scope} "${name}" resumed`;
746
+ default:
747
+ return `Workflow "${name}" completed`;
748
+ }
749
+ }
750
+
751
+ function lifecycleNoticeHint(details: WorkflowLifecycleNoticeDetails): string {
752
+ if (details.kind === "awaiting_input") return `/workflow connect ${details.runId}`;
753
+ if (details.kind === "paused" || details.kind === "quit") return `/workflow resume ${details.runId}`;
754
+ return `/workflow status ${details.runId}`;
755
+ }
756
+
757
+ function resumableFieldValue(details: WorkflowLifecycleNoticeDetails): string | undefined {
758
+ if (details.resumable === undefined) return undefined;
759
+ return details.resumable ? "yes" : "no";
760
+ }
761
+
497
762
  function formatDurationMs(durationMs: number | undefined): string | undefined {
498
763
  if (durationMs === undefined) return undefined;
499
764
  if (durationMs < 1000) return `${durationMs}ms`;
@@ -32,6 +32,7 @@ import { truncateToWidth } from "../tui/text-helpers.js";
32
32
  import { renderWorkflowList } from "../tui/workflow-list.js";
33
33
  import type { WorkflowReloadReport } from "./workflow-reload-report.js";
34
34
  import type { WorkflowRunStatusFilter, WorkflowRunStatusSummary } from "./workflow-status-summary.js";
35
+ import { getWorkflowStatusRenderRuns } from "./workflow-status-summary.js";
35
36
 
36
37
  // ---------------------------------------------------------------------------
37
38
  // Result variants
@@ -240,6 +241,8 @@ export interface RenderResultOpts {
240
241
  * not implement synchronized output (e.g. mosh).
241
242
  */
242
243
  now?: number;
244
+ /** Point-in-time full run collection used only to resolve hidden nested indicators. */
245
+ allRuns?: readonly RunSnapshot[];
243
246
  }
244
247
 
245
248
  /**
@@ -335,6 +338,7 @@ export function renderResult(result: WorkflowToolResult, opts?: RenderResultOpts
335
338
  theme: themed ? deriveGraphTheme({}) : undefined,
336
339
  width: opts?.width,
337
340
  now: opts?.now,
341
+ allRuns: opts?.allRuns ?? getWorkflowStatusRenderRuns(r) ?? r.snapshots,
338
342
  });
339
343
  }
340
344
 
@@ -18,7 +18,7 @@ import type { JobTracker } from "../runs/background/job-tracker.js";
18
18
  import type { RunOpts } from "../runs/foreground/executor.js";
19
19
  import type { StageAdapters } from "../runs/foreground/stage-runner.js";
20
20
  import type { Store } from "../shared/store.js";
21
- import type { RunSnapshot } from "../shared/store-types.js";
21
+ import type { RunSnapshot, WorkflowActor } from "../shared/store-types.js";
22
22
  import type { WorkflowExecutionPolicy } from "../shared/types.js";
23
23
  import type { WorkflowRegistry } from "../workflows/registry.js";
24
24
  import { discoverWorkflows } from "./discovery.js";
@@ -26,7 +26,7 @@ import { discoverWorkflows } from "./discovery.js";
26
26
  export interface DurableResumeRuntime {
27
27
  resumeDurableWorkflow(
28
28
  workflowId: string,
29
- options?: { readonly policy?: WorkflowExecutionPolicy },
29
+ options?: { readonly policy?: WorkflowExecutionPolicy; readonly actor?: WorkflowActor },
30
30
  ): Promise<ResumeDurableResult>;
31
31
  listDurableResumable(): readonly ResumableWorkflowEntry[];
32
32
  prepareDurableResumable(workflowId?: string): Promise<readonly ResumableWorkflowEntry[]>;
@@ -75,7 +75,10 @@ export function createDurableResumeRuntime(deps: DurableResumeRuntimeDeps): Dura
75
75
  if (resolved !== undefined) await backend.hydrateWorkflow(resolved.workflowId);
76
76
  const adapterDeps: ResumeDurableDeps = {
77
77
  registry: deps.registry,
78
- baseRunOpts: deps.baseRunOpts(options?.policy),
78
+ baseRunOpts: {
79
+ ...deps.baseRunOpts(options?.policy),
80
+ ...(options?.actor === undefined ? {} : { resumeActor: options.actor }),
81
+ },
79
82
  durableBackend: backend,
80
83
  resolveDefinition: async (name, cwd) =>
81
84
  (await discoverWorkflows({ cwd: cwd ?? deps.runtimeCwd })).registry.get(name),
@@ -18,7 +18,7 @@ import { type RunOpts, resolveAndValidateInputs } from "../runs/foreground/execu
18
18
  import type { StageAdapters } from "../runs/foreground/stage-runner.js";
19
19
  import type { Store } from "../shared/store.js";
20
20
  import { store as defaultStore } from "../shared/store.js";
21
- import type { RunSnapshot } from "../shared/store-types.js";
21
+ import type { RunSnapshot, WorkflowActor } from "../shared/store-types.js";
22
22
  import type {
23
23
  WorkflowDefinition,
24
24
  WorkflowExecutionPolicy,
@@ -115,6 +115,10 @@ export interface ExtensionRuntime extends DurableResumeRuntime {
115
115
  }
116
116
  export interface RuntimeDispatchOptions {
117
117
  readonly policy?: WorkflowExecutionPolicy;
118
+ /** Who launched this run. Only an attributable launcher supplies it. */
119
+ readonly origin?: WorkflowActor;
120
+ /** Who requested this resume. Only an attributable requester supplies it. */
121
+ readonly actor?: WorkflowActor;
118
122
  }
119
123
  // ---------------------------------------------------------------------------
120
124
  // Factory
@@ -266,6 +270,7 @@ export function createExtensionRuntime(opts: ExtensionRuntimeOpts = {}): Extensi
266
270
  launchDetachedUntilStartup(def, sourceInputs, {
267
271
  ...runOptions(options?.policy),
268
272
  continuation: { source, resumeFromStageId: resolvedStage.stageId },
273
+ ...(options?.actor === undefined ? {} : { resumeActor: options.actor }),
269
274
  ...(jobs !== undefined ? { jobs } : {}),
270
275
  });
271
276
  if (isActiveBlockedResumable) {
@@ -393,6 +398,7 @@ export function createExtensionRuntime(opts: ExtensionRuntimeOpts = {}): Extensi
393
398
  config,
394
399
  models,
395
400
  policy: options?.policy,
401
+ ...(options?.origin === undefined ? {} : { origin: options.origin }),
396
402
  cwd: runtimeCwd,
397
403
  ...(defaultSessionDir !== undefined ? { defaultSessionDir } : {}),
398
404
  });
@@ -134,22 +134,64 @@ function isTestContext(): boolean {
134
134
  return process.env.NODE_TEST_CONTEXT !== undefined || process.env.NODE_ENV === "test";
135
135
  }
136
136
 
137
+ type StageSettingsManager = ReturnType<PiCodingAgentSdk["SettingsManager"]["create"]>;
138
+
139
+ function attachSettingsManager(error: unknown, settingsManager: StageSettingsManager): unknown {
140
+ if (error !== null && (typeof error === "object" || typeof error === "function")) {
141
+ try {
142
+ if (Object.isExtensible(error)) {
143
+ Object.defineProperty(error, "settingsManager", {
144
+ configurable: true,
145
+ enumerable: false,
146
+ value: settingsManager,
147
+ writable: false,
148
+ });
149
+ return error;
150
+ }
151
+ } catch {
152
+ // Frozen or proxy errors cannot carry the private retry hint. Wrap them
153
+ // without replacing the original failure as the cause.
154
+ }
155
+ }
156
+ const wrapped = new Error(error instanceof Error ? error.message : String(error), { cause: error });
157
+ Object.defineProperty(wrapped, "settingsManager", {
158
+ configurable: true,
159
+ enumerable: false,
160
+ value: settingsManager,
161
+ writable: false,
162
+ });
163
+ return wrapped;
164
+ }
165
+
137
166
  async function createPiSdkAgentSession(
138
167
  options?: CreateAgentSessionOptions,
139
168
  prepareOptions?: PrepareAtomicStageSessionOptions,
140
169
  ): Promise<StageSessionCreateResult> {
141
170
  const sdk = (await import("@bastani/atomic")) as PiCodingAgentSdk;
142
- const sessionOptions = await prepareAtomicStageSessionOptions(options, sdk, prepareOptions);
143
- const result = await sdk.createAgentSession(sessionOptions);
144
- // `CreateAgentSessionResult` is `{ session, extensionsResult, modelFallbackMessage? }`;
145
- // workflow stages only consume `.session` (structurally an `AgentSession`,
146
- // which is a superset of our `StageSessionRuntime` projection).
147
- const resultSettingsManager = result.session.settingsManager;
148
- const settingsManager = sessionOptions?.settingsManager ?? resultSettingsManager;
149
- return {
150
- session: result.session,
151
- ...(settingsManager?.getCodexFastModeSettings !== undefined ? { settingsManager } : {}),
152
- };
171
+ let settingsManager: ReturnType<PiCodingAgentSdk["SettingsManager"]["create"]> | undefined;
172
+ try {
173
+ const sessionOptions = await prepareAtomicStageSessionOptions(options, sdk, {
174
+ ...prepareOptions,
175
+ onSettingsManager: (manager) => {
176
+ settingsManager = manager;
177
+ prepareOptions?.onSettingsManager?.(manager);
178
+ },
179
+ });
180
+ settingsManager = sessionOptions?.settingsManager ?? settingsManager;
181
+ const result = await sdk.createAgentSession(sessionOptions);
182
+ // `CreateAgentSessionResult` is `{ session, extensionsResult, modelFallbackMessage? }`;
183
+ // workflow stages only consume `.session` (structurally an `AgentSession`,
184
+ // which is a superset of our `StageSessionRuntime` projection).
185
+ const resultSettingsManager = result.session.settingsManager;
186
+ settingsManager = sessionOptions?.settingsManager ?? resultSettingsManager ?? settingsManager;
187
+ return {
188
+ session: result.session,
189
+ ...(settingsManager?.getCodexFastModeSettings !== undefined ? { settingsManager } : {}),
190
+ };
191
+ } catch (error) {
192
+ if (settingsManager !== undefined) throw attachSettingsManager(error, settingsManager);
193
+ throw error;
194
+ }
153
195
  }
154
196
 
155
197
  async function createTestAgentSession(_options?: CreateAgentSessionOptions): Promise<StageSessionCreateResult> {
@@ -1,5 +1,6 @@
1
1
  import { inspectRun } from "../runs/background/status.js";
2
2
  import { renderInputsSchema } from "../shared/render-inputs-schema.js";
3
+ import { resolveRunIndicatorStatuses } from "../shared/run-indicator-status.js";
3
4
  import { schemaIsRequired } from "../shared/schema-introspection.js";
4
5
  import { store } from "../shared/store.js";
5
6
  import type { WorkflowExecutionPolicy } from "../shared/types.js";
@@ -158,8 +159,14 @@ async function workflowSlashHandler(
158
159
  emitChatSurface(pi, { kind: "detail", detail: inspected.detail });
159
160
  return;
160
161
  }
161
- const rows = selectRunsForPicker(store.runs(), "", true, Date.now());
162
- emitChatSurface(pi, { kind: "status", runs: rows.map((r) => r.run) });
162
+ const capturedRuns = store.graphSnapshot().runs;
163
+ const rows = selectRunsForPicker(capturedRuns, "", true, Date.now());
164
+ const visibleRuns = rows.map((r) => r.run);
165
+ emitChatSurface(pi, {
166
+ kind: "status",
167
+ runs: visibleRuns,
168
+ indicatorStatuses: resolveRunIndicatorStatuses(visibleRuns, capturedRuns),
169
+ });
163
170
  return;
164
171
  }
165
172
  if (subcommand === "reload") {
@@ -250,7 +257,9 @@ async function workflowSlashHandler(
250
257
 
251
258
  await ensureWorkflowResourcesVisible();
252
259
  const result = await deps.runWithLifecycleSuppressedForPolicy(policy, () =>
253
- deps.runtimeForContext(ctx).dispatch({ workflow: workflowName, inputs: mergedInputs, action: "run" }, { policy }),
260
+ deps
261
+ .runtimeForContext(ctx)
262
+ .dispatch({ workflow: workflowName, inputs: mergedInputs, action: "run" }, { policy, origin: "user" }),
254
263
  );
255
264
  if (result.action !== "run" || !("runId" in result)) return;
256
265
  const runResult = result as Extract<WorkflowToolResult, { action: "run"; runId: string }>;
@@ -121,7 +121,7 @@ export async function handleDurableResume(
121
121
  fail(completedAttempt.message);
122
122
  return true;
123
123
  }
124
- const result = await runtime.resumeDurableWorkflow(target, { policy });
124
+ const result = await runtime.resumeDurableWorkflow(target, { policy, actor: "user" });
125
125
  fail(
126
126
  allOpenable.length === 0 ? result.message : `${result.message}\n\n${formatResumableWorkflowList(allOpenable)}`,
127
127
  );
@@ -216,7 +216,10 @@ async function resumeDurableTarget(
216
216
  deps: WorkflowRunControlDeps,
217
217
  runtime: ExtensionRuntime,
218
218
  ): Promise<boolean> {
219
- const result = await runtime.resumeDurableWorkflow(workflowId, { policy: workflowPolicyFromContext(ctx) });
219
+ const result = await runtime.resumeDurableWorkflow(workflowId, {
220
+ policy: workflowPolicyFromContext(ctx),
221
+ actor: "user",
222
+ });
220
223
  if (!result.ok) reporter.error(result.message);
221
224
  else {
222
225
  reporter.info(result.message);