@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
@@ -162,9 +162,9 @@ Workflow invocations receive a stable, non-`default` Intercom group automaticall
162
162
 
163
163
  Foreground runs stream progress in the conversation while they run.
164
164
 
165
- Background runs keep working after control returns to you. The launch acknowledgement says `launched` and `completion pending`: the `subagent` launch tool call itself is finished, while the detached child is not. Inspect active runs with `subagent({ action: "status" })`, or a specific run with `subagent({ action: "status", id: "..." })`.
165
+ `async: true` means **do not wait**. Atomic admits an in-process child, returns its canonical child path immediately, and tracks the live child through the jobs widget. **Async work does not survive parent exit:** the live child is owned by the parent process and ends when Atomic quits; only its canonical identity and session file remain for a later cold resume.
166
166
 
167
- They also show a compact async widget with the same launch/pending distinction and send completion notifications. Parallel background runs show per-agent progress instead of fake chain steps. Chains with parallel groups keep their grouped shape in progress and results, so failed or paused agents stay visible next to completed ones.
167
+ The jobs widget shows the live status-watch state and sends one terminal completion notification. Parallel background runs show per-agent progress, and chains with parallel groups keep their grouped shape in progress and results. There is no detached runner process or PID polling loop.
168
168
 
169
169
  You can also ask naturally:
170
170
 
@@ -238,7 +238,7 @@ The child can use one dedicated coordination tool:
238
238
 
239
239
  - `contact_supervisor`: the child contacts the parent/supervisor session that delegated the task. Use `reason: "need_decision"` for blocking decisions or clarification, and `reason: "progress_update"` for short non-blocking updates when a discovery changes the plan. Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions; no-edit wins.
240
240
 
241
- Child-side routine completion handoffs are still not expected. With the intercom bridge active, parent-side `pi-subagents` sends grouped completion results through `pi-intercom`: one grouped message per foreground parent `subagent` run and one per completed async result file. Intercom-confirmed foreground delivery returns a compact receipt with artifact/session paths; without that confirmation, the normal full output is preserved. Grouped messages include child intercom targets and full child summaries. The separate in-process completion event keeps its legacy synchronous semantics: emission is accepted unless a listener explicitly rejects it during the call, and no listener is not treated as an error.
241
+ Child-side routine completion handoffs are still not expected. With the intercom bridge active, parent-side Atomic sends grouped completion results through `pi-intercom`: one grouped message per foreground parent `subagent` run and one per completed async terminal result. Intercom-confirmed foreground delivery returns a compact receipt with artifact/session paths; without that confirmation, the normal full output is preserved. Grouped messages include child intercom targets and full child summaries.
242
242
  When the companion is enabled and available, the bridge gives eligible children deterministic Intercom identities and coordination tools without connecting them automatically. Parent and child connections remain tool-driven: if a child may need live coordination, the parent model should invoke `intercom({ action: "status" })` before launch, and the child connects when it invokes `contact_supervisor` or `intercom`. Foreground/background launch and management-only actions do not force Intercom loading or broker startup.
243
243
 
244
244
  For foreground runs, Intercom uses a targeted probe/reservation before delivery: only the exact live child can claim its message. Atomic then commits detach for that child and waits for its acknowledgement before placing claimed asks, sends, decisions, interviews, and progress updates in the parent's model-visible steering queue, so cancellation between phases cannot surface an orphaned request. Blocking calls remain alive for an exact threaded reply and then resume; fire-and-forget calls create no waiter. The retained child later replaces its detached status and artifacts with the real result. Cancellation/replacement invalidates stale handshakes, duplicate delivery cannot recommit, and background or unmatched messages retain queued-until-idle behavior.
@@ -344,7 +344,7 @@ You can combine them in either order:
344
344
  /run reviewer "review this diff" --bg --fork
345
345
  ```
346
346
 
347
- Background runs are detached. A successful acknowledgement explicitly means the run was launched and child completion is pending; it is a terminal result for the launch tool, not a claim that the child has finished. If the parent agent has other independent work, it should keep working. If it has nothing useful to do until the background result arrives, it should end the turn instead of running sleep or status-polling loops. Pi will deliver the completion when the run finishes.
347
+ Background runs use the same in-process continuation as foreground detach. A successful acknowledgement means the canonical child path was returned and completion is pending; the live child remains owned by the parent process and the jobs widget tracks it. **`async: true` does not survive parent exit.** If the parent exits, the in-flight run ends; its persisted identity/session can be listed and resumed later.
348
348
 
349
349
  The `oracle` and `worker` builtins are designed for an explicit decision loop. A typical pattern is to ask `oracle` for diagnosis and a recommended execution prompt, then only run `worker` after the main agent approves that direction.
350
350
 
@@ -356,7 +356,7 @@ The human slash commands remain on their separate parsing and event-bridge path,
356
356
 
357
357
  ## Agents and chains
358
358
 
359
- Agents are markdown files with YAML frontmatter and a system prompt body. They define the specialist that will run in the child Atomic process.
359
+ Agents are markdown files with YAML frontmatter and a system prompt body. They define the specialist that will run in a child Atomic session.
360
360
 
361
361
  Agent locations, lowest to highest priority:
362
362
 
@@ -837,7 +837,7 @@ subagent({ action: "resume", id: "<run-id>", index: 1, message: "follow-up for c
837
837
  subagent({ action: "doctor" })
838
838
  ```
839
839
 
840
- `resume` sends the follow-up directly when an async child is still reachable over intercom. After completion, it revives the child by starting a new async child from the stored child session file. Multi-child async runs and remembered foreground single, parallel, or chain runs can be revived by passing `index` to choose the child. Revive starts a new child process from the old session context; it does not restart the same OS process, and it requires the chosen child to have a persisted `.jsonl` session file.
840
+ `resume` sends the follow-up directly when a child is still reachable. After completion or eviction, it cold-reloads the same canonical child identity from the stored session file. Multi-child async runs and remembered foreground single, parallel, or chain runs can be revived by passing `index` to choose the child; no new OS process is created.
841
841
 
842
842
  ## Worktree isolation
843
843
 
@@ -919,7 +919,7 @@ Session directory precedence is: `params.sessionDir`, then `config.defaultSessio
919
919
  { "maxSubagentDepth": 1 }
920
920
  ```
921
921
 
922
- Controls nested delegation when no inherited `ATOMIC_SUBAGENT_MAX_DEPTH` (or legacy `PI_SUBAGENT_MAX_DEPTH`) is already in effect. Accepted values are `0` through `5`; higher values are clamped to the hard ceiling. Per-agent `maxSubagentDepth` can tighten the limit for that agent’s child runs, but cannot relax an inherited stricter limit.
922
+ Controls nested delegation through typed admission policy. Accepted values are `0` through `5`; higher values are refused at the hard ceiling. Per-agent `maxSubagentDepth` can tighten the limit for that agent’s child runs, but cannot relax an inherited stricter limit.
923
923
 
924
924
  ### `intercomBridge`
925
925
 
@@ -979,25 +979,24 @@ Debug artifacts live under `{sessionDir}/subagent-artifacts/` or a user-scoped t
979
979
  - `{runId}_{agent}.jsonl`
980
980
  - `{runId}_{agent}_meta.json`
981
981
 
982
- Metadata records timing, usage, exit code, final model, attempted models, and fallback attempt outcomes.
982
+ Metadata records timing, usage, typed status, termination cause, final model, attempted models, and fallback attempt outcomes.
983
983
 
984
- Session files are stored under a per-run session directory. With `context: "fork"`, each child starts with `--session <branched-session-file>` produced from the parent’s current leaf. That is a real session fork, not an injected summary.
984
+ Session files are stored under a per-run session directory. With `context: "fork"`, each child starts from the parent’s current leaf through the session manager; this is a real session fork, not an injected summary.
985
985
 
986
- Async completions notify only the originating session. The result watcher emits `subagent:async-complete`, and the extension consumes that event to render completion notifications.
986
+ Async completions notify only the originating session. The in-process status watch emits live lifecycle updates, and the extension consumes the terminal event to render completion notifications.
987
987
 
988
- Async runs write:
988
+ Async runs persist their durable session and user-facing artifacts beside the parent session:
989
989
 
990
990
  ```text
991
- <tmpdir>/atomic-subagents-<scope>/async-subagent-runs/<id>/
992
- status.json
993
- events.jsonl
994
- output-<n>.log
995
- subagent-log-<id>.md
991
+ {parent-session-dir}/subagent-artifacts/
992
+ {runId}_{agent}_input.md
993
+ {runId}_{agent}_output.md
994
+ {runId}_{agent}.jsonl
995
+ {runId}_{agent}_meta.json
996
+ run-history.jsonl
996
997
  ```
997
998
 
998
- `status.json` powers the widget and `subagent({ action: "status" })` output. `events.jsonl` contains wrapper events plus bounded child telemetry annotated with run and step metadata: streaming deltas keep compact incremental metadata rather than cumulative partial-message snapshots, and raw child stdout/stderr consume the same byte budget. One truncation marker records exhaustion, while the full finalized `message_end` and later control/lifecycle/terminal events remain available. Writer reacquisition uses a bounded identity/fingerprint cache so unchanged or append-only journals avoid full rescans while same-inode rewrites, truncate/regrow cycles, replacements, and externally appended markers reset state correctly. `output-<n>.log` is a live human-readable tail. Fallback information is persisted so background runs are debuggable after completion.
999
-
1000
- The result watcher waits for modern run status to become terminal using capped exponential rechecks, so a late repaired status still delivers without a fixed-rate polling loop. Intercom confirmation and local synchronous acceptance are tracked as separate phases; a successful phase is not replayed when another phase retries or the watcher is replaced. Equivalent result aliases coalesce by canonical run identity, but aliases with different user-visible output or parent targets are retained under collision-resistant names in the non-scanned `<results>/.undelivered/` directory. The same directory retains a still-owned result after a finite no-progress retry budget, and Atomic logs the retained path instead of retrying forever, overwriting earlier evidence, or deleting the payload.
999
+ The Rust registry and status watch power the widget and `subagent({ action: "status" })` output. Terminal delivery is an in-memory bounded envelope persisted once with typed `status`, `cause`, and `stats`; there is no `status.json`, `events.jsonl`, PID reconciler, result watcher, or claim pipeline.
1001
1000
 
1002
1001
  ## Completion and output
1003
1002
 
@@ -1039,17 +1038,10 @@ By default, nesting is capped at five delegated subagent levels below the main s
1039
1038
 
1040
1039
  Configure a lower or equal limit with:
1041
1040
 
1042
- 1. `ATOMIC_SUBAGENT_MAX_DEPTH` (or legacy `PI_SUBAGENT_MAX_DEPTH`) before starting Atomic; values above `5` are clamped to `5`
1043
- 2. `config.maxSubagentDepth`
1044
- 3. `maxSubagentDepth` in agent frontmatter, which can only tighten the inherited limit
1045
-
1046
- ```bash
1047
- export ATOMIC_SUBAGENT_MAX_DEPTH=5
1048
- export ATOMIC_SUBAGENT_MAX_DEPTH=3
1049
- export ATOMIC_SUBAGENT_MAX_DEPTH=0
1050
- ```
1041
+ 1. `config.maxSubagentDepth`
1042
+ 2. `maxSubagentDepth` in agent frontmatter, which can only tighten the admitted limit
1051
1043
 
1052
- `ATOMIC_SUBAGENT_DEPTH` is internal and propagated automatically. Do not set it manually. Legacy `PI_SUBAGENT_MAX_DEPTH` and `PI_SUBAGENT_DEPTH` are still read for compatibility.
1044
+ The depth policy is typed admission state and is not inherited through an environment variable.
1053
1045
 
1054
1046
  ## Events
1055
1047
 
@@ -1063,7 +1055,7 @@ Intercom delivery events:
1063
1055
  - `subagent:control-intercom`
1064
1056
  - `subagent:result-intercom`
1065
1057
 
1066
- The result watcher emits `subagent:async-complete`; `src/extension/index.ts` registers the notification handler that consumes it. Control/attention events are surfaced as visible parent notices and persisted for async runs. With `pi-intercom`, needs-attention notices and grouped parent-side subagent result deliveries can reach the orchestrator over intercom.
1058
+ The in-process status watch emits `subagent:async-complete`; `src/extension/index.ts` registers the notification handler that consumes it. Control/attention events are surfaced as visible parent notices, and typed terminal records carry the canonical path, status, cause, and session statistics. With `pi-intercom`, needs-attention notices and grouped parent-side subagent result deliveries can reach the orchestrator over intercom.
1067
1059
 
1068
1060
  ## Prompt-template integration
1069
1061
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.12",
3
+ "version": "0.9.13-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for delegating tasks to subagents with chains, parallel execution, and background runs. Fork of: https://github.com/nicobailon/pi-subagents",
6
6
  "contributors": [
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { type AgentSource, discoverAgentsAll } from "../agents/agents.ts";
4
4
  import { discoverAvailableSkills, type SkillSource } from "../agents/skills.ts";
5
5
  import { diagnoseIntercomBridge, type IntercomBridgeDiagnostic } from "../intercom/intercom-bridge.ts";
6
- import { isAsyncAvailable } from "../runs/background/async-execution.ts";
6
+ import { isAsyncAvailable } from "../runs/inprocess/background.ts";
7
7
  import {
8
8
  ASYNC_DIR,
9
9
  CHAIN_RUNS_DIR,
@@ -2,19 +2,20 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import type { ExtensionAPI, ToolDefinition } from "@bastani/atomic";
5
- import { APP_NAME, getEnvValue } from "@bastani/atomic";
5
+ import { APP_NAME } from "@bastani/atomic";
6
6
 
7
7
  import { discoverAgents } from "../agents/agents.ts";
8
8
  import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.ts";
9
9
  import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom.ts";
10
10
  import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
11
+ import type { ChildModePolicy } from "../runs/inprocess/child-policy.ts";
12
+ import { hasInProcessNestedRoute, subscribeInProcessControlRequests } from "../runs/inprocess/nested-routing.ts";
11
13
  import {
12
14
  type NestedRoute,
13
15
  readNestedControlRequests,
14
16
  resolveNestedRouteFromEnv,
15
17
  writeNestedControlResult,
16
- } from "../runs/shared/nested-events.ts";
17
- import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
18
+ } from "../runs/inprocess/runtime-support/nested-api.ts";
18
19
  import { getArtifactsDir } from "../shared/artifacts.ts";
19
20
  import type { Details, SubagentState } from "../shared/types.ts";
20
21
  import { beginApiLifecycle } from "./api-lifecycle.ts";
@@ -63,8 +64,6 @@ const TRIMMED_SEEN_REQUEST_IDS = 512;
63
64
  const MAX_PENDING_RESULTS = 256;
64
65
  const MAX_PENDING_RESULT_ATTEMPTS = 10;
65
66
  const MAX_PENDING_RESULT_AGE_MS = 60_000;
66
- const NESTED_CONTROL_INBOX_TIMER_KEY = "fanout-child:nested-control-inbox";
67
-
68
67
  type NestedControlResultPayload = Parameters<typeof writeNestedControlResult>[1];
69
68
 
70
69
  interface PendingControlResult {
@@ -139,7 +138,15 @@ function buildNestedControlResult(
139
138
  })();
140
139
  }
141
140
 
142
- export function startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {
141
+ const nestedControlListenerCleanups = new WeakMap<ExtensionAPI, () => void>();
142
+
143
+ export function stopNestedControlInboxListener(pi: ExtensionAPI): void {
144
+ nestedControlListenerCleanups.get(pi)?.();
145
+ nestedControlListenerCleanups.delete(pi);
146
+ }
147
+
148
+ export function startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): (() => void) | undefined {
149
+ stopNestedControlInboxListener(pi);
143
150
  let route: NestedRoute | undefined;
144
151
  try {
145
152
  route = resolveNestedRouteFromEnv();
@@ -150,70 +157,82 @@ export function startNestedControlInboxListener(pi: ExtensionAPI, state: Subagen
150
157
  const seen = new Set<string>();
151
158
  const inFlight = new Set<string>();
152
159
  const pendingResults = new Map<string, PendingControlResult>();
153
- const timer = setInterval(() => {
154
- try {
155
- for (const request of readNestedControlRequests(route)) {
156
- if (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;
157
- inFlight.add(request.requestId);
158
- void (async () => {
159
- try {
160
- const pending = pendingResults.get(request.requestId);
161
- const result = pending?.result ?? (await buildNestedControlResult(pi, state, request));
162
- try {
163
- writeNestedControlResult(route, result);
164
- } catch (error) {
165
- const now = Date.now();
166
- const nextPending: PendingControlResult = {
167
- result,
168
- firstFailureAt: pending?.firstFailureAt ?? now,
169
- attempts: (pending?.attempts ?? 0) + 1,
170
- };
171
- pendingResults.set(request.requestId, nextPending);
172
- if (shouldDropPendingResult(nextPending, pendingResults.size, now)) {
173
- pendingResults.delete(request.requestId);
174
- rememberSeenRequest(seen, request.requestId);
175
- dropControlRequestFile(request.filePath, request.requestId);
176
- console.error(
177
- `Dropping nested control result for request '${request.requestId}' targeting '${request.targetRunId}' after ${nextPending.attempts} failed write attempts via inbox '${route.controlInbox}':`,
178
- error,
179
- );
180
- } else if (nextPending.attempts === 1) {
181
- console.error(
182
- `Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`,
183
- error,
184
- );
185
- }
186
- return;
187
- }
160
+ const processRequest = (request: ReturnType<typeof readNestedControlRequests>[number]): void => {
161
+ if (seen.has(request.requestId) || inFlight.has(request.requestId)) return;
162
+ inFlight.add(request.requestId);
163
+ void (async () => {
164
+ try {
165
+ const pending = pendingResults.get(request.requestId);
166
+ const result = pending?.result ?? (await buildNestedControlResult(pi, state, request));
167
+ try {
168
+ writeNestedControlResult(route!, result);
169
+ } catch (error) {
170
+ const now = Date.now();
171
+ const nextPending: PendingControlResult = {
172
+ result,
173
+ firstFailureAt: pending?.firstFailureAt ?? now,
174
+ attempts: (pending?.attempts ?? 0) + 1,
175
+ };
176
+ pendingResults.set(request.requestId, nextPending);
177
+ if (shouldDropPendingResult(nextPending, pendingResults.size, now)) {
188
178
  pendingResults.delete(request.requestId);
189
179
  rememberSeenRequest(seen, request.requestId);
190
180
  dropControlRequestFile(request.filePath, request.requestId);
191
- } finally {
192
- inFlight.delete(request.requestId);
181
+ console.error(
182
+ `Dropping nested control result for request '${request.requestId}' targeting '${request.targetRunId}' after ${nextPending.attempts} failed writes via inbox '${route!.controlInbox}':`,
183
+ error,
184
+ );
185
+ } else if (nextPending.attempts === 1) {
186
+ console.error(
187
+ `Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route!.controlInbox}'; keeping request for retry:`,
188
+ error,
189
+ );
193
190
  }
194
- })();
191
+ return;
192
+ }
193
+ pendingResults.delete(request.requestId);
194
+ rememberSeenRequest(seen, request.requestId);
195
+ dropControlRequestFile(request.filePath, request.requestId);
196
+ } finally {
197
+ inFlight.delete(request.requestId);
195
198
  }
199
+ })();
200
+ };
201
+ const processPending = (): void => {
202
+ for (const request of readNestedControlRequests(route!)) processRequest(request);
203
+ };
204
+ processPending();
205
+ const memoryCleanup = hasInProcessNestedRoute(route)
206
+ ? subscribeInProcessControlRequests(route, processRequest)
207
+ : undefined;
208
+ let watcher: fs.FSWatcher | undefined;
209
+ if (!memoryCleanup) {
210
+ try {
211
+ watcher = fs.watch(route.controlInbox, { persistent: false }, () => processPending());
196
212
  } catch (error) {
197
- console.error(
198
- `Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`,
199
- error,
200
- );
213
+ console.error(`Failed to watch nested control inbox '${route.controlInbox}':`, error);
201
214
  }
202
- }, 200);
203
- timer.unref?.();
204
- state.cleanupTimers.set(NESTED_CONTROL_INBOX_TIMER_KEY, timer);
205
- return timer;
215
+ }
216
+ const cleanup = () => {
217
+ memoryCleanup?.();
218
+ watcher?.close();
219
+ if (nestedControlListenerCleanups.get(pi) === cleanup) nestedControlListenerCleanups.delete(pi);
220
+ };
221
+ nestedControlListenerCleanups.set(pi, cleanup);
222
+ return cleanup;
206
223
  }
207
224
 
208
- export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {
209
- if (getEnvValue(SUBAGENT_CHILD_ENV) !== "1" || getEnvValue(SUBAGENT_FANOUT_CHILD_ENV) !== "1") return;
225
+ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, childPolicy?: ChildModePolicy): void {
226
+ if (!childPolicy?.fanoutAuthorized) return;
210
227
 
211
228
  const lifecycle = beginApiLifecycle(pi);
212
229
 
213
230
  try {
214
231
  const config = loadConfig();
215
232
  const state = createChildSafeState();
233
+ let nestedListenerCleanup: (() => void) | undefined;
216
234
  lifecycle.setCleanup(() => {
235
+ nestedListenerCleanup?.();
217
236
  for (const timer of state.cleanupTimers.values()) clearInterval(timer);
218
237
  state.cleanupTimers.clear();
219
238
  });
@@ -226,7 +245,8 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
226
245
  getSubagentSessionRoot,
227
246
  expandTilde,
228
247
  discoverAgents,
229
- allowMutatingManagementActions: false,
248
+ childPolicy,
249
+ allowMutatingManagementActions: childPolicy ? childPolicy.managementActions === "full" : false,
230
250
  });
231
251
 
232
252
  const tool: ToolDefinition<typeof SubagentParams, Details> = {
@@ -246,7 +266,7 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
246
266
  };
247
267
 
248
268
  pi.registerTool(tool);
249
- startNestedControlInboxListener(pi, state);
269
+ nestedListenerCleanup = startNestedControlInboxListener(pi, state);
250
270
  pi.on?.("session_shutdown", () => lifecycle.dispose());
251
271
  } catch (error) {
252
272
  lifecycle.dispose();
@@ -6,7 +6,6 @@ import {
6
6
  APP_NAME,
7
7
  type ExtensionAPI,
8
8
  type ExtensionContext,
9
- getEnvValue,
10
9
  keyHintIfBound,
11
10
  type ToolDefinition,
12
11
  } from "@bastani/atomic";
@@ -25,7 +24,6 @@ import { discoverAgents } from "../agents/agents.ts";
25
24
  import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
26
25
  import registerSubagentNotify, { type SubagentNotifyDetails } from "../runs/background/notify.ts";
27
26
  import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
28
- import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
29
27
  import { getArtifactsDir } from "../shared/artifacts.ts";
30
28
  import { formatDuration, shortenPath } from "../shared/formatters.ts";
31
29
  import { resolveCurrentSessionId } from "../shared/session-identity.ts";
@@ -48,7 +46,6 @@ import {
48
46
  stopWidgetAnimation,
49
47
  } from "../tui/render.ts";
50
48
  import { loadConfig } from "./config.ts";
51
- import registerFanoutChildSubagentExtension from "./fanout-child.ts";
52
49
  import { parseSubagentNotifyContent } from "./notification-content.ts";
53
50
  import { DEFAULT_PROMPT_GUIDANCE } from "./prompt-guidance.ts";
54
51
  import { SubagentParams } from "./schemas.ts";
@@ -60,7 +57,6 @@ import {
60
57
  ASYNC_DIR,
61
58
  DEFAULT_ARTIFACT_CONFIG,
62
59
  type Details,
63
- RESULTS_DIR,
64
60
  SLASH_RESULT_TYPE,
65
61
  SUBAGENT_ASYNC_COMPLETE_EVENT,
66
62
  SUBAGENT_ASYNC_STARTED_EVENT,
@@ -90,18 +86,6 @@ function getSubagentSessionRoot(parentSessionFile: string | null): string {
90
86
  function expandTilde(p: string): string {
91
87
  return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
92
88
  }
93
- function ensureAccessibleDir(dirPath: string): void {
94
- fs.mkdirSync(dirPath, { recursive: true });
95
- try {
96
- fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
97
- } catch {
98
- try {
99
- fs.rmSync(dirPath, { recursive: true, force: true });
100
- } catch {}
101
- fs.mkdirSync(dirPath, { recursive: true });
102
- fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
103
- }
104
- }
105
89
  function isSlashResultRunning(result: { details?: Details }): boolean {
106
90
  return (
107
91
  result.details?.progress?.some((entry) => entry.status === "running") ||
@@ -111,7 +95,7 @@ function isSlashResultRunning(result: { details?: Details }): boolean {
111
95
  }
112
96
  function isSlashResultError(result: { details?: Details }): boolean {
113
97
  return (
114
- result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== "running") || false
98
+ result.details?.results.some((entry) => entry.status === "error" && entry.progress?.status !== "running") || false
115
99
  );
116
100
  }
117
101
  type SubagentToolRenderState = SubagentResultRenderState;
@@ -221,16 +205,10 @@ class SubagentControlNoticeComponent implements Component {
221
205
  }
222
206
 
223
207
  export default function registerSubagentExtension(pi: ExtensionAPI): void {
224
- if (getEnvValue(SUBAGENT_CHILD_ENV) === "1") {
225
- if (getEnvValue(SUBAGENT_FANOUT_CHILD_ENV) === "1") registerFanoutChildSubagentExtension(pi);
226
- return;
227
- }
228
208
  const lifecycle = beginApiLifecycle(pi);
229
209
  const registrationFailureCleanups: Array<() => void> = [];
230
210
  let runtimeCleanupInstalled = false;
231
211
  try {
232
- ensureAccessibleDir(RESULTS_DIR);
233
- ensureAccessibleDir(ASYNC_DIR);
234
212
  const config = loadConfig();
235
213
  const asyncByDefault = config.asyncByDefault === true;
236
214
  const tempArtifactsDir = getArtifactsDir(null);
@@ -254,18 +232,14 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
254
232
  clear: () => {},
255
233
  },
256
234
  };
257
- const maintenance = createSubagentStartupMaintenance(pi, state, {
258
- resultsDir: RESULTS_DIR,
235
+ const maintenance = createSubagentStartupMaintenance(state, {
259
236
  artifactCleanupDays: DEFAULT_ARTIFACT_CONFIG.cleanupDays,
260
- resultTtlMs: 10 * 60 * 1000,
261
237
  });
262
238
  maintenance.scheduleStartupCleanup();
263
239
  registrationFailureCleanups.push(() => maintenance.stop());
264
- maintenance.startResultWatcherDeferred();
265
- maintenance.primeExistingResultsDeferred();
266
240
  const { ensurePoller, handleStarted, handleComplete, resetJobs, hydrateActiveJobsDeferred } =
267
241
  createAsyncJobTracker(pi, state, ASYNC_DIR);
268
- const executor = createSubagentExecutor({
242
+ const executorDeps = {
269
243
  pi,
270
244
  state,
271
245
  config,
@@ -274,7 +248,23 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
274
248
  getSubagentSessionRoot,
275
249
  expandTilde,
276
250
  discoverAgents,
277
- });
251
+ };
252
+ const executor = createSubagentExecutor(executorDeps);
253
+ const childExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
254
+ const executorForContext = (ctx: ExtensionContext): ReturnType<typeof createSubagentExecutor> => {
255
+ const policy = ctx.subagentPolicy;
256
+ if (!policy) return executor;
257
+ const key = `${policy.managementActions}:${policy.fanoutAuthorized ? "fanout" : "no-fanout"}`;
258
+ const cached = childExecutors.get(key);
259
+ if (cached) return cached;
260
+ const childExecutor = createSubagentExecutor({
261
+ ...executorDeps,
262
+ childPolicy: policy,
263
+ allowMutatingManagementActions: policy.managementActions === "full",
264
+ });
265
+ childExecutors.set(key, childExecutor);
266
+ return childExecutor;
267
+ };
278
268
  pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
279
269
  const details = resolveSlashMessageDetails(message.details);
280
270
  if (!details) return undefined;
@@ -304,7 +294,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
304
294
  state.lastUiContext = ctx;
305
295
  ctx.ui.setToolsExpanded(false);
306
296
  }
307
- return executor.execute(id, params, signal, onUpdate, ctx);
297
+ return executorForContext(ctx).execute(id, params, signal, onUpdate, ctx);
308
298
  };
309
299
  const slashBridge = registerSlashSubagentBridge({
310
300
  events: pi.events,
@@ -454,6 +444,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
454
444
  ...Array.from(state.cleanupTimers.values(), (timer) => () => clearTimeout(timer)),
455
445
  () => state.cleanupTimers.clear(),
456
446
  () => state.asyncJobs.clear(),
447
+ () => childExecutors.clear(),
457
448
  () => clearSlashSnapshots(pi),
458
449
  () => slashBridge.cancelAll(),
459
450
  () => slashBridge.dispose(),
@@ -489,7 +480,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
489
480
  resetJobs(ctx);
490
481
  hydrateActiveJobsDeferred(ctx);
491
482
  restoreSlashFinalSnapshots(ctx.sessionManager.getEntries(), pi);
492
- maintenance.primeExistingResultsDeferred();
483
+ restoreSlashFinalSnapshots(ctx.sessionManager.getEntries(), pi);
493
484
  };
494
485
  pi.on("session_start", (_event, ctx) => {
495
486
  if (lifecycle.isCurrent()) resetSessionState(ctx);
@@ -1,15 +1,12 @@
1
1
  import * as path from "node:path";
2
- import type { ExtensionAPI, ExtensionContext } from "@bastani/atomic";
3
- import { createResultWatcher } from "../runs/background/result-watcher.ts";
4
- import { cleanupOldNestedRuntimeDirs } from "../runs/shared/nested-events.ts";
2
+ import type { ExtensionContext } from "@bastani/atomic";
3
+ import { cleanupOldNestedRuntimeDirs } from "../runs/inprocess/runtime-support/nested-api.ts";
5
4
  import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
6
5
  import { cleanupOldChainDirs } from "../shared/settings.ts";
7
6
  import type { SubagentState } from "../shared/types.ts";
8
7
 
9
8
  export interface SubagentStartupMaintenance {
10
9
  scheduleStartupCleanup(): void;
11
- startResultWatcherDeferred(): void;
12
- primeExistingResultsDeferred(): void;
13
10
  cleanupSessionArtifactsDeferred(ctx: ExtensionContext): void;
14
11
  stop(): void;
15
12
  }
@@ -47,54 +44,48 @@ function swallowCleanup(task: () => void): void {
47
44
  } catch {}
48
45
  }
49
46
 
47
+ interface StartupMaintenanceOptions {
48
+ artifactCleanupDays: number;
49
+ resultsDir?: string;
50
+ resultTtlMs?: number;
51
+ scheduleMacrotask?: (task: () => void) => () => void;
52
+ scheduleDelayed?: (task: () => void, delayMs: number) => () => void;
53
+ }
54
+
55
+ export function createSubagentStartupMaintenance(
56
+ state: SubagentState,
57
+ options: StartupMaintenanceOptions,
58
+ ): SubagentStartupMaintenance;
50
59
  export function createSubagentStartupMaintenance(
51
- pi: ExtensionAPI,
60
+ _pi: unknown,
52
61
  state: SubagentState,
53
- options: {
54
- resultsDir: string;
55
- artifactCleanupDays: number;
56
- resultTtlMs: number;
57
- scheduleMacrotask?: (task: () => void) => () => void;
58
- scheduleDelayed?: (task: () => void, delayMs: number) => () => void;
59
- },
62
+ options: StartupMaintenanceOptions,
63
+ ): SubagentStartupMaintenance;
64
+ export function createSubagentStartupMaintenance(
65
+ _first: SubagentState | unknown,
66
+ second: StartupMaintenanceOptions | SubagentState,
67
+ third?: StartupMaintenanceOptions,
60
68
  ): SubagentStartupMaintenance {
61
- const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
62
- pi,
63
- state,
64
- options.resultsDir,
65
- options.resultTtlMs,
66
- );
69
+ const options = third ?? (second as StartupMaintenanceOptions);
67
70
  const cancelTasks = new Set<() => void>();
68
- // The sessions root derived from a host-provided (non-default, e.g. isolated
69
- // programmatic agentDir) session directory. When set, the global artifact scan
70
- // stays inside it instead of touching the real global/legacy sessions roots.
71
- // A custom session dir that does not follow the <agentDir>/sessions/<safe-cwd>
72
- // layout (the default layout the SDK derives for a programmatic agentDir, with
73
- // <safe-cwd> encoding the current working directory) yields no roots at all, so
74
- // cleanup never broadens into an arbitrary directory's siblings.
75
71
  let contextSessionsRoots: readonly string[] | undefined;
76
72
  const noteSessionContext = (ctx: ExtensionContext): void => {
77
73
  try {
78
74
  if (ctx.sessionManager.usesDefaultSessionDir()) {
79
- // Only a valid default-session context intentionally restores the
80
- // env/global cleanup roots.
81
75
  contextSessionsRoots = undefined;
82
76
  return;
83
77
  }
84
78
  const sessionDir = ctx.sessionManager.getSessionDir();
85
- // A partial or throwing context keeps the last validated root: destructive
86
- // cleanup must never broaden from an isolated root to the global roots on
87
- // the strength of an unusable context.
88
79
  if (!sessionDir) return;
89
80
  const parent = path.dirname(sessionDir);
90
81
  const safeCwd = `--${path
91
82
  .resolve(ctx.cwd)
92
83
  .replace(/^[/\\]/, "")
93
84
  .replace(/[/\\:]/g, "-")}--`;
94
- const followsAgentDirLayout = path.basename(parent) === "sessions" && path.basename(sessionDir) === safeCwd;
95
- contextSessionsRoots = followsAgentDirLayout ? [parent] : [];
85
+ contextSessionsRoots =
86
+ path.basename(parent) === "sessions" && path.basename(sessionDir) === safeCwd ? [parent] : [];
96
87
  } catch {
97
- // A stale or partial context leaves cleanup on the env/global fallback.
88
+ // A stale or partial context leaves cleanup on the last validated root.
98
89
  }
99
90
  };
100
91
  const schedule = (task: () => void): void => {
@@ -117,21 +108,12 @@ export function createSubagentStartupMaintenance(
117
108
  schedule(() => {
118
109
  swallowCleanup(cleanupOldChainDirs);
119
110
  swallowCleanup(() => cleanupOldNestedRuntimeDirs(options.artifactCleanupDays));
120
- // The global session-artifact scan can touch thousands of historical session
121
- // directories, so it waits until well after startup instead of running on the
122
- // activation macrotask.
123
111
  scheduleDelayed(
124
112
  () => swallowCleanup(() => cleanupAllArtifactDirs(options.artifactCleanupDays, contextSessionsRoots)),
125
113
  STARTUP_ARTIFACT_SCAN_DELAY_MS,
126
114
  );
127
115
  });
128
116
  },
129
- startResultWatcherDeferred() {
130
- schedule(startResultWatcher);
131
- },
132
- primeExistingResultsDeferred() {
133
- schedule(primeExistingResults);
134
- },
135
117
  cleanupSessionArtifactsDeferred(ctx) {
136
118
  noteSessionContext(ctx);
137
119
  let sessionFile: string | null | undefined;
@@ -148,7 +130,6 @@ export function createSubagentStartupMaintenance(
148
130
  stop() {
149
131
  for (const cancel of cancelTasks) cancel();
150
132
  cancelTasks.clear();
151
- stopResultWatcher();
152
133
  },
153
134
  };
154
135
  }