@bastani/atomic 0.8.13-0 → 0.8.14-0

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 (355) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/host-html-template.ts +1 -1
  4. package/dist/builtin/mcp/init.ts +15 -2
  5. package/dist/builtin/mcp/mcp-callback-server.ts +10 -9
  6. package/dist/builtin/mcp/package.json +1 -1
  7. package/dist/builtin/mcp/ui-session.ts +9 -6
  8. package/dist/builtin/subagents/CHANGELOG.md +8 -1
  9. package/dist/builtin/subagents/README.md +39 -32
  10. package/dist/builtin/subagents/package.json +1 -1
  11. package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
  12. package/dist/builtin/subagents/src/agents/agent-management.ts +6 -1
  13. package/dist/builtin/subagents/src/agents/agent-serializer.ts +2 -0
  14. package/dist/builtin/subagents/src/agents/agents.ts +44 -19
  15. package/dist/builtin/subagents/src/extension/config.ts +16 -0
  16. package/dist/builtin/subagents/src/extension/fanout-child.ts +246 -0
  17. package/dist/builtin/subagents/src/extension/index.ts +466 -603
  18. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +6 -4
  19. package/dist/builtin/subagents/src/intercom/result-intercom.ts +109 -1
  20. package/dist/builtin/subagents/src/runs/background/async-execution.ts +124 -19
  21. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +41 -6
  22. package/dist/builtin/subagents/src/runs/background/async-resume.ts +28 -15
  23. package/dist/builtin/subagents/src/runs/background/async-status.ts +60 -30
  24. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +111 -54
  25. package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +83 -0
  26. package/dist/builtin/subagents/src/runs/background/run-status.ts +79 -3
  27. package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +46 -1
  28. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +66 -14
  29. package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +10 -3
  30. package/dist/builtin/subagents/src/runs/foreground/execution.ts +14 -2
  31. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +320 -23
  32. package/dist/builtin/subagents/src/runs/shared/completion-guard.ts +23 -1
  33. package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +369 -0
  34. package/dist/builtin/subagents/src/runs/shared/nested-events.ts +935 -0
  35. package/dist/builtin/subagents/src/runs/shared/nested-path.ts +52 -0
  36. package/dist/builtin/subagents/src/runs/shared/nested-render.ts +115 -0
  37. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
  38. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +82 -9
  39. package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +1 -1
  40. package/dist/builtin/subagents/src/runs/shared/single-output.ts +12 -2
  41. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +32 -10
  42. package/dist/builtin/subagents/src/runs/shared/worktree.ts +3 -2
  43. package/dist/builtin/subagents/src/shared/artifacts.ts +0 -1
  44. package/dist/builtin/subagents/src/shared/types.ts +96 -1
  45. package/dist/builtin/subagents/src/shared/utils.ts +10 -2
  46. package/dist/builtin/subagents/src/slash/slash-commands.ts +468 -625
  47. package/dist/builtin/subagents/src/tui/render.ts +1227 -2093
  48. package/dist/builtin/web-access/package.json +1 -1
  49. package/dist/builtin/workflows/CHANGELOG.md +24 -0
  50. package/dist/builtin/workflows/README.md +28 -11
  51. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +323 -40
  52. package/dist/builtin/workflows/builtin/ralph.ts +362 -176
  53. package/dist/builtin/workflows/package.json +2 -5
  54. package/dist/builtin/workflows/skills/research-codebase/SKILL.md +1 -1
  55. package/dist/builtin/workflows/skills/skill-creator/LICENSE.txt +202 -0
  56. package/dist/builtin/workflows/skills/skill-creator/SKILL.md +489 -0
  57. package/dist/builtin/workflows/skills/skill-creator/agents/analyzer.md +274 -0
  58. package/dist/builtin/workflows/skills/skill-creator/agents/comparator.md +202 -0
  59. package/dist/builtin/workflows/skills/skill-creator/agents/grader.md +223 -0
  60. package/dist/builtin/workflows/skills/skill-creator/assets/eval_review.html +146 -0
  61. package/dist/builtin/workflows/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  62. package/dist/builtin/workflows/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  63. package/dist/builtin/workflows/skills/skill-creator/references/schemas.md +430 -0
  64. package/dist/builtin/workflows/skills/skill-creator/scripts/__init__.py +0 -0
  65. package/dist/builtin/workflows/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  66. package/dist/builtin/workflows/skills/skill-creator/scripts/generate_report.py +326 -0
  67. package/dist/builtin/workflows/skills/skill-creator/scripts/improve_description.py +247 -0
  68. package/dist/builtin/workflows/skills/skill-creator/scripts/package_skill.py +136 -0
  69. package/dist/builtin/workflows/skills/skill-creator/scripts/quick_validate.py +103 -0
  70. package/dist/builtin/workflows/skills/skill-creator/scripts/run_eval.py +310 -0
  71. package/dist/builtin/workflows/skills/skill-creator/scripts/run_loop.py +328 -0
  72. package/dist/builtin/workflows/skills/skill-creator/scripts/utils.py +47 -0
  73. package/dist/builtin/workflows/src/extension/index.ts +869 -93
  74. package/dist/builtin/workflows/src/extension/render-call.ts +34 -1
  75. package/dist/builtin/workflows/src/extension/render-result.ts +126 -21
  76. package/dist/builtin/workflows/src/extension/runtime.ts +91 -3
  77. package/dist/builtin/workflows/src/extension/wiring.ts +38 -12
  78. package/dist/builtin/workflows/src/extension/workflow-schema.ts +62 -5
  79. package/dist/builtin/workflows/src/runs/background/runner.ts +3 -3
  80. package/dist/builtin/workflows/src/runs/background/status.ts +42 -8
  81. package/dist/builtin/workflows/src/runs/foreground/executor.ts +410 -95
  82. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +5 -2
  83. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +8 -0
  84. package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +6 -4
  85. package/dist/builtin/workflows/src/runs/shared/worktree.ts +3 -2
  86. package/dist/builtin/workflows/src/shared/persistence-restore.ts +138 -5
  87. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +30 -0
  88. package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +78 -120
  89. package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +193 -0
  90. package/dist/builtin/workflows/src/shared/store-types.ts +26 -1
  91. package/dist/builtin/workflows/src/shared/store.ts +145 -17
  92. package/dist/builtin/workflows/src/shared/timing.ts +6 -2
  93. package/dist/builtin/workflows/src/shared/workflow-failures.ts +375 -0
  94. package/dist/builtin/workflows/src/tui/chat-surface.ts +68 -17
  95. package/dist/builtin/workflows/src/tui/connectors.ts +2 -2
  96. package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +24 -26
  97. package/dist/builtin/workflows/src/tui/graph-canvas.ts +4 -8
  98. package/dist/builtin/workflows/src/tui/graph-view.ts +17 -14
  99. package/dist/builtin/workflows/src/tui/header.ts +38 -0
  100. package/dist/builtin/workflows/src/tui/inline-form-card.ts +161 -238
  101. package/dist/builtin/workflows/src/tui/inline-form-editor.ts +68 -73
  102. package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +2 -3
  103. package/dist/builtin/workflows/src/tui/inline-form-store.ts +2 -1
  104. package/dist/builtin/workflows/src/tui/inputs-overlay.ts +1 -3
  105. package/dist/builtin/workflows/src/tui/inputs-picker.ts +286 -399
  106. package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +11 -0
  107. package/dist/builtin/workflows/src/tui/node-card.ts +2 -1
  108. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -1
  109. package/dist/builtin/workflows/src/tui/prompt-card.ts +46 -19
  110. package/dist/builtin/workflows/src/tui/run-detail.ts +63 -80
  111. package/dist/builtin/workflows/src/tui/session-confirm.ts +9 -3
  112. package/dist/builtin/workflows/src/tui/session-picker.ts +19 -16
  113. package/dist/builtin/workflows/src/tui/stage-chat-layout.ts +88 -0
  114. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +368 -879
  115. package/dist/builtin/workflows/src/tui/status-helpers.ts +4 -0
  116. package/dist/builtin/workflows/src/tui/status-list.ts +67 -75
  117. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +50 -12
  118. package/dist/builtin/workflows/src/tui/submit-pane.ts +164 -0
  119. package/dist/builtin/workflows/src/tui/switcher.ts +27 -4
  120. package/dist/builtin/workflows/src/tui/text-helpers.ts +98 -4
  121. package/dist/builtin/workflows/src/tui/widget.ts +90 -68
  122. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +23 -2
  123. package/dist/builtin/workflows/src/tui/workflow-list.ts +44 -68
  124. package/dist/cli/file-processor.d.ts.map +1 -1
  125. package/dist/cli/file-processor.js +2 -3
  126. package/dist/cli/file-processor.js.map +1 -1
  127. package/dist/config.d.ts.map +1 -1
  128. package/dist/config.js +3 -10
  129. package/dist/config.js.map +1 -1
  130. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  131. package/dist/core/agent-session-runtime.js +2 -1
  132. package/dist/core/agent-session-runtime.js.map +1 -1
  133. package/dist/core/agent-session-services.d.ts.map +1 -1
  134. package/dist/core/agent-session-services.js +3 -2
  135. package/dist/core/agent-session-services.js.map +1 -1
  136. package/dist/core/agent-session.d.ts +6 -0
  137. package/dist/core/agent-session.d.ts.map +1 -1
  138. package/dist/core/agent-session.js +16 -2
  139. package/dist/core/agent-session.js.map +1 -1
  140. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  141. package/dist/core/atomic-guide-command.js +8 -9
  142. package/dist/core/atomic-guide-command.js.map +1 -1
  143. package/dist/core/auth-storage.d.ts.map +1 -1
  144. package/dist/core/auth-storage.js +3 -2
  145. package/dist/core/auth-storage.js.map +1 -1
  146. package/dist/core/bash-executor.d.ts.map +1 -1
  147. package/dist/core/bash-executor.js +2 -1
  148. package/dist/core/bash-executor.js.map +1 -1
  149. package/dist/core/export-html/index.d.ts.map +1 -1
  150. package/dist/core/export-html/index.js +8 -6
  151. package/dist/core/export-html/index.js.map +1 -1
  152. package/dist/core/export-html/template.js +6 -3
  153. package/dist/core/extensions/loader.d.ts.map +1 -1
  154. package/dist/core/extensions/loader.js +12 -29
  155. package/dist/core/extensions/loader.js.map +1 -1
  156. package/dist/core/model-registry.d.ts.map +1 -1
  157. package/dist/core/model-registry.js +5 -1
  158. package/dist/core/model-registry.js.map +1 -1
  159. package/dist/core/package-manager.d.ts +8 -0
  160. package/dist/core/package-manager.d.ts.map +1 -1
  161. package/dist/core/package-manager.js +145 -58
  162. package/dist/core/package-manager.js.map +1 -1
  163. package/dist/core/prompt-templates.d.ts.map +1 -1
  164. package/dist/core/prompt-templates.js +6 -20
  165. package/dist/core/prompt-templates.js.map +1 -1
  166. package/dist/core/resource-loader.d.ts.map +1 -1
  167. package/dist/core/resource-loader.js +38 -31
  168. package/dist/core/resource-loader.js.map +1 -1
  169. package/dist/core/sdk.d.ts.map +1 -1
  170. package/dist/core/sdk.js +9 -4
  171. package/dist/core/sdk.js.map +1 -1
  172. package/dist/core/session-manager.d.ts.map +1 -1
  173. package/dist/core/session-manager.js +32 -24
  174. package/dist/core/session-manager.js.map +1 -1
  175. package/dist/core/settings-manager.d.ts.map +1 -1
  176. package/dist/core/settings-manager.js +8 -15
  177. package/dist/core/settings-manager.js.map +1 -1
  178. package/dist/core/skills.d.ts.map +1 -1
  179. package/dist/core/skills.js +8 -22
  180. package/dist/core/skills.js.map +1 -1
  181. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts +5 -4
  182. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
  183. package/dist/core/tools/ask-user-question/state/questionnaire-session.js +34 -11
  184. package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
  185. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +1 -0
  186. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
  187. package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
  188. package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
  189. package/dist/core/tools/ask-user-question/state/selectors/projections.js +1 -0
  190. package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
  191. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts +1 -2
  192. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
  193. package/dist/core/tools/ask-user-question/state/state-reducer.js +26 -9
  194. package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
  195. package/dist/core/tools/ask-user-question/state/state.d.ts +4 -0
  196. package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
  197. package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
  198. package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts +1 -0
  199. package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts.map +1 -1
  200. package/dist/core/tools/ask-user-question/view/components/option-list-view.js +1 -0
  201. package/dist/core/tools/ask-user-question/view/components/option-list-view.js.map +1 -1
  202. package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts +9 -6
  203. package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
  204. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +28 -7
  205. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
  206. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
  207. package/dist/core/tools/ask-user-question/view/props-adapter.js +4 -1
  208. package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
  209. package/dist/core/tools/bash.d.ts.map +1 -1
  210. package/dist/core/tools/bash.js +56 -53
  211. package/dist/core/tools/bash.js.map +1 -1
  212. package/dist/core/tools/edit-diff.d.ts +3 -1
  213. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  214. package/dist/core/tools/edit-diff.js +8 -1
  215. package/dist/core/tools/edit-diff.js.map +1 -1
  216. package/dist/core/tools/edit.d.ts +3 -1
  217. package/dist/core/tools/edit.d.ts.map +1 -1
  218. package/dist/core/tools/edit.js +44 -81
  219. package/dist/core/tools/edit.js.map +1 -1
  220. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  221. package/dist/core/tools/file-mutation-queue.js +27 -12
  222. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  223. package/dist/core/tools/find.d.ts.map +1 -1
  224. package/dist/core/tools/find.js +2 -3
  225. package/dist/core/tools/find.js.map +1 -1
  226. package/dist/core/tools/grep.d.ts.map +1 -1
  227. package/dist/core/tools/grep.js +3 -3
  228. package/dist/core/tools/grep.js.map +1 -1
  229. package/dist/core/tools/ls.d.ts.map +1 -1
  230. package/dist/core/tools/ls.js +5 -5
  231. package/dist/core/tools/ls.js.map +1 -1
  232. package/dist/core/tools/output-accumulator.d.ts +2 -0
  233. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  234. package/dist/core/tools/output-accumulator.js +11 -4
  235. package/dist/core/tools/output-accumulator.js.map +1 -1
  236. package/dist/core/tools/path-utils.d.ts +2 -0
  237. package/dist/core/tools/path-utils.d.ts.map +1 -1
  238. package/dist/core/tools/path-utils.js +39 -21
  239. package/dist/core/tools/path-utils.js.map +1 -1
  240. package/dist/core/tools/read.d.ts.map +1 -1
  241. package/dist/core/tools/read.js +9 -8
  242. package/dist/core/tools/read.js.map +1 -1
  243. package/dist/core/tools/truncate.d.ts.map +1 -1
  244. package/dist/core/tools/truncate.js +12 -2
  245. package/dist/core/tools/truncate.js.map +1 -1
  246. package/dist/core/tools/write.d.ts.map +1 -1
  247. package/dist/core/tools/write.js +20 -35
  248. package/dist/core/tools/write.js.map +1 -1
  249. package/dist/index.d.ts +2 -1
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js +4 -1
  252. package/dist/index.js.map +1 -1
  253. package/dist/main.d.ts.map +1 -1
  254. package/dist/main.js +5 -6
  255. package/dist/main.js.map +1 -1
  256. package/dist/modes/interactive/chat-input-actions.d.ts +24 -0
  257. package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -0
  258. package/dist/modes/interactive/chat-input-actions.js +179 -0
  259. package/dist/modes/interactive/chat-input-actions.js.map +1 -0
  260. package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -0
  261. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  262. package/dist/modes/interactive/components/chat-message-renderer.js +14 -3
  263. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  264. package/dist/modes/interactive/components/chat-session-host.d.ts +157 -0
  265. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -0
  266. package/dist/modes/interactive/components/chat-session-host.js +1007 -0
  267. package/dist/modes/interactive/components/chat-session-host.js.map +1 -0
  268. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  269. package/dist/modes/interactive/components/config-selector.js +1 -1
  270. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  271. package/dist/modes/interactive/components/footer.d.ts +1 -0
  272. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  273. package/dist/modes/interactive/components/footer.js +14 -5
  274. package/dist/modes/interactive/components/footer.js.map +1 -1
  275. package/dist/modes/interactive/components/index.d.ts +1 -0
  276. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  277. package/dist/modes/interactive/components/index.js +1 -0
  278. package/dist/modes/interactive/components/index.js.map +1 -1
  279. package/dist/modes/interactive/components/login-dialog.d.ts +9 -1
  280. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/login-dialog.js +29 -4
  282. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  283. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  284. package/dist/modes/interactive/interactive-mode.js +18 -67
  285. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  286. package/dist/utils/child-process.d.ts +1 -0
  287. package/dist/utils/child-process.d.ts.map +1 -1
  288. package/dist/utils/child-process.js +8 -0
  289. package/dist/utils/child-process.js.map +1 -1
  290. package/dist/utils/clipboard-native.d.ts +3 -1
  291. package/dist/utils/clipboard-native.d.ts.map +1 -1
  292. package/dist/utils/clipboard-native.js +14 -8
  293. package/dist/utils/clipboard-native.js.map +1 -1
  294. package/dist/utils/image-resize-core.d.ts +30 -0
  295. package/dist/utils/image-resize-core.d.ts.map +1 -0
  296. package/dist/utils/image-resize-core.js +124 -0
  297. package/dist/utils/image-resize-core.js.map +1 -0
  298. package/dist/utils/image-resize-worker.d.ts +2 -0
  299. package/dist/utils/image-resize-worker.d.ts.map +1 -0
  300. package/dist/utils/image-resize-worker.js +31 -0
  301. package/dist/utils/image-resize-worker.js.map +1 -0
  302. package/dist/utils/image-resize.d.ts +7 -27
  303. package/dist/utils/image-resize.d.ts.map +1 -1
  304. package/dist/utils/image-resize.js +75 -115
  305. package/dist/utils/image-resize.js.map +1 -1
  306. package/dist/utils/paths.d.ts +16 -1
  307. package/dist/utils/paths.d.ts.map +1 -1
  308. package/dist/utils/paths.js +49 -7
  309. package/dist/utils/paths.js.map +1 -1
  310. package/docs/changelog.mdx +29 -0
  311. package/docs/compaction.md +1 -1
  312. package/docs/custom-provider.md +2 -2
  313. package/docs/development.md +1 -1
  314. package/docs/docs.json +98 -143
  315. package/docs/extensions.md +29 -16
  316. package/docs/favicon.svg +29 -0
  317. package/docs/images/interactive-mode.png +0 -0
  318. package/docs/images/tree-view.png +0 -0
  319. package/docs/images/workflow-command.png +0 -0
  320. package/docs/images/workflow-graph.png +0 -0
  321. package/docs/images/workflow-input-picker.png +0 -0
  322. package/docs/images/workflow-list.png +0 -0
  323. package/docs/index.md +10 -1
  324. package/docs/logo.svg +59 -0
  325. package/docs/packages.md +3 -3
  326. package/docs/providers.md +1 -1
  327. package/docs/quickstart.md +98 -2
  328. package/docs/rpc.md +8 -8
  329. package/docs/sdk.md +23 -12
  330. package/docs/sessions.md +1 -1
  331. package/docs/skills.md +15 -1
  332. package/docs/termux.md +11 -1
  333. package/docs/themes.md +6 -6
  334. package/docs/tui.md +18 -18
  335. package/docs/usage.md +1 -1
  336. package/docs/workflows.md +172 -2
  337. package/examples/extensions/subagent/index.ts +2 -1
  338. package/package.json +6 -6
  339. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/SKILL.md +0 -0
  340. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/element-attributes.md +0 -0
  341. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/playwright-tests.md +0 -0
  342. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/request-mocking.md +0 -0
  343. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/running-code.md +0 -0
  344. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/session-management.md +0 -0
  345. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/spec-driven-testing.md +0 -0
  346. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/storage-state.md +0 -0
  347. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/test-generation.md +0 -0
  348. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/tracing.md +0 -0
  349. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/video-recording.md +0 -0
  350. /package/dist/builtin/{workflows → subagents}/skills/tdd/SKILL.md +0 -0
  351. /package/dist/builtin/{workflows → subagents}/skills/tdd/deep-modules.md +0 -0
  352. /package/dist/builtin/{workflows → subagents}/skills/tdd/interface-design.md +0 -0
  353. /package/dist/builtin/{workflows → subagents}/skills/tdd/mocking.md +0 -0
  354. /package/dist/builtin/{workflows → subagents}/skills/tdd/refactoring.md +0 -0
  355. /package/dist/builtin/{workflows → subagents}/skills/tdd/tests.md +0 -0
@@ -15,82 +15,52 @@
15
15
  import * as fs from "node:fs";
16
16
  import * as os from "node:os";
17
17
  import * as path from "node:path";
18
- import { getAgentConfigPaths, getEnvValue } from "@bastani/atomic";
19
18
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
20
- import {
21
- type ExtensionAPI,
22
- type ExtensionContext,
23
- type ToolDefinition,
24
- } from "@bastani/atomic";
25
- import {
26
- Box,
27
- Container,
28
- Spacer,
29
- Text,
30
- truncateToWidth,
31
- visibleWidth,
32
- wrapTextWithAnsi,
33
- type Component,
34
- } from "@earendil-works/pi-tui";
19
+ import { APP_NAME, getEnvValue } from "@bastani/atomic";
20
+ import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@bastani/atomic";
21
+ import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
35
22
  import { discoverAgents } from "../agents/agents.ts";
36
- import {
37
- cleanupAllArtifactDirs,
38
- cleanupOldArtifacts,
39
- getArtifactsDir,
40
- } from "../shared/artifacts.ts";
23
+ import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
41
24
  import { resolveCurrentSessionId } from "../shared/session-identity.ts";
42
25
  import { cleanupOldChainDirs } from "../shared/settings.ts";
43
- import {
44
- renderWidget,
45
- renderSubagentResult,
46
- stopResultAnimations,
47
- stopWidgetAnimation,
48
- syncResultAnimation,
49
- } from "../tui/render.ts";
26
+ import { clearLegacyResultAnimationTimer, renderWidget, renderSubagentResult } from "../tui/render.ts";
50
27
  import { SubagentParams } from "./schemas.ts";
51
- import {
52
- createSubagentExecutor,
53
- type SubagentParamsLike,
54
- } from "../runs/foreground/subagent-executor.ts";
28
+ import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
55
29
  import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
56
30
  import { createResultWatcher } from "../runs/background/result-watcher.ts";
57
31
  import { registerSlashCommands } from "../slash/slash-commands.ts";
58
32
  import { registerPromptTemplateDelegationBridge } from "../slash/prompt-template-bridge.ts";
59
33
  import { registerSlashSubagentBridge } from "../slash/slash-bridge.ts";
60
- import {
61
- clearSlashSnapshots,
62
- getSlashRenderableSnapshot,
63
- resolveSlashMessageDetails,
64
- restoreSlashFinalSnapshots,
65
- type SlashMessageDetails,
66
- } from "../slash/slash-live-state.ts";
34
+ import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "../slash/slash-live-state.ts";
67
35
  import { inspectSubagentStatus } from "../runs/background/run-status.ts";
68
- import registerSubagentNotify, {
69
- type SubagentNotifyDetails,
70
- } from "../runs/background/notify.ts";
71
- import { SUBAGENT_CHILD_ENV } from "../runs/shared/pi-args.ts";
36
+ import registerSubagentNotify, { type SubagentNotifyDetails } from "../runs/background/notify.ts";
37
+ import { cleanupOldNestedRuntimeDirs } from "../runs/shared/nested-events.ts";
38
+ import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
39
+ import registerFanoutChildSubagentExtension from "./fanout-child.ts";
72
40
  import { formatDuration, shortenPath } from "../shared/formatters.ts";
41
+ import { loadConfig } from "./config.ts";
73
42
  import {
74
- type Details,
75
- type ExtensionConfig,
76
- type SubagentState,
77
- ASYNC_DIR,
78
- DEFAULT_ARTIFACT_CONFIG,
79
- RESULTS_DIR,
80
- SLASH_RESULT_TYPE,
81
- SUBAGENT_ASYNC_COMPLETE_EVENT,
82
- SUBAGENT_ASYNC_STARTED_EVENT,
83
- SUBAGENT_CONTROL_EVENT,
84
- WIDGET_KEY,
43
+ type Details,
44
+ type SubagentState,
45
+ ASYNC_DIR,
46
+ DEFAULT_ARTIFACT_CONFIG,
47
+ RESULTS_DIR,
48
+ SLASH_RESULT_TYPE,
49
+ SUBAGENT_ASYNC_COMPLETE_EVENT,
50
+ SUBAGENT_ASYNC_STARTED_EVENT,
51
+ SUBAGENT_CONTROL_EVENT,
52
+ WIDGET_KEY,
85
53
  } from "../shared/types.ts";
86
54
  import {
87
- clearPendingForegroundControlNotices,
88
- formatSubagentControlNotice,
89
- handleSubagentControlNotice,
90
- SUBAGENT_CONTROL_MESSAGE_TYPE,
91
- type SubagentControlMessageDetails,
55
+ clearPendingForegroundControlNotices,
56
+ formatSubagentControlNotice,
57
+ handleSubagentControlNotice,
58
+ SUBAGENT_CONTROL_MESSAGE_TYPE,
59
+ type SubagentControlMessageDetails,
92
60
  } from "./control-notices.ts";
93
61
 
62
+ export { loadConfig } from "./config.ts";
63
+
94
64
  /**
95
65
  * Derive subagent session base directory from parent session file.
96
66
  * If parent session is ~/.atomic/agent/sessions/abc123.jsonl,
@@ -99,38 +69,16 @@ import {
99
69
  * Falls back to a unique temp directory if no parent session.
100
70
  */
101
71
  function getSubagentSessionRoot(parentSessionFile: string | null): string {
102
- if (parentSessionFile) {
103
- const baseName = path.basename(parentSessionFile, ".jsonl");
104
- const sessionsDir = path.dirname(parentSessionFile);
105
- return path.join(sessionsDir, baseName);
106
- }
107
- return fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-session-"));
108
- }
109
-
110
- function loadConfig(): ExtensionConfig {
111
- for (const configPath of getAgentConfigPaths(
112
- "extensions",
113
- "subagent",
114
- "config.json",
115
- )) {
116
- try {
117
- if (fs.existsSync(configPath)) {
118
- return JSON.parse(
119
- fs.readFileSync(configPath, "utf-8"),
120
- ) as ExtensionConfig;
121
- }
122
- } catch (error) {
123
- console.error(
124
- `Failed to load subagent config from '${configPath}':`,
125
- error,
126
- );
127
- }
128
- }
129
- return {};
72
+ if (parentSessionFile) {
73
+ const baseName = path.basename(parentSessionFile, ".jsonl");
74
+ const sessionsDir = path.dirname(parentSessionFile);
75
+ return path.join(sessionsDir, baseName);
76
+ }
77
+ return fs.mkdtempSync(path.join(os.tmpdir(), `${APP_NAME}-subagent-session-`));
130
78
  }
131
79
 
132
80
  function expandTilde(p: string): string {
133
- return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
81
+ return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
134
82
  }
135
83
 
136
84
  /**
@@ -141,380 +89,308 @@ function expandTilde(p: string): string {
141
89
  * the directory completely inaccessible to the creating user.
142
90
  */
143
91
  function ensureAccessibleDir(dirPath: string): void {
144
- fs.mkdirSync(dirPath, { recursive: true });
145
- try {
146
- fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
147
- } catch {
148
- try {
149
- fs.rmSync(dirPath, { recursive: true, force: true });
150
- } catch {
151
- // Best effort: retry mkdir/access even if cleanup fails.
152
- }
153
- fs.mkdirSync(dirPath, { recursive: true });
154
- fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
155
- }
92
+ fs.mkdirSync(dirPath, { recursive: true });
93
+ try {
94
+ fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
95
+ } catch {
96
+ try {
97
+ fs.rmSync(dirPath, { recursive: true, force: true });
98
+ } catch {
99
+ // Best effort: retry mkdir/access even if cleanup fails.
100
+ }
101
+ fs.mkdirSync(dirPath, { recursive: true });
102
+ fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
103
+ }
156
104
  }
157
105
 
158
106
  function isSlashResultRunning(result: { details?: Details }): boolean {
159
- return (
160
- result.details?.progress?.some((entry) => entry.status === "running") ||
161
- result.details?.results.some(
162
- (entry) => entry.progress?.status === "running",
163
- ) ||
164
- false
165
- );
107
+ return result.details?.progress?.some((entry) => entry.status === "running")
108
+ || result.details?.results.some((entry) => entry.progress?.status === "running")
109
+ || false;
166
110
  }
167
111
 
168
112
  function isSlashResultError(result: { details?: Details }): boolean {
169
- return (
170
- result.details?.results.some(
171
- (entry) => entry.exitCode !== 0 && entry.progress?.status !== "running",
172
- ) || false
173
- );
113
+ return result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== "running") || false;
174
114
  }
175
115
 
176
116
  function isStaleExtensionContextError(error: unknown): boolean {
177
- return (
178
- error instanceof Error &&
179
- error.message.includes("Extension context no longer active")
180
- );
117
+ return error instanceof Error && error.message.includes("Extension context no longer active");
181
118
  }
182
119
 
183
120
  function rebuildSlashResultContainer(
184
- container: Container,
185
- result: AgentToolResult<Details>,
186
- options: { expanded: boolean },
187
- theme: ExtensionContext["ui"]["theme"],
121
+ container: Container,
122
+ result: AgentToolResult<Details>,
123
+ options: { expanded: boolean },
124
+ theme: ExtensionContext["ui"]["theme"],
188
125
  ): void {
189
- container.clear();
190
- container.addChild(new Spacer(1));
191
- const boxTheme = isSlashResultRunning(result)
192
- ? "toolPendingBg"
193
- : isSlashResultError(result)
194
- ? "toolErrorBg"
195
- : "toolSuccessBg";
196
- const box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));
197
- box.addChild(renderSubagentResult(result, options, theme));
198
- container.addChild(box);
126
+ container.clear();
127
+ container.addChild(new Spacer(1));
128
+ const boxTheme = isSlashResultRunning(result) ? "toolPendingBg" : isSlashResultError(result) ? "toolErrorBg" : "toolSuccessBg";
129
+ const box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));
130
+ box.addChild(renderSubagentResult(result, options, theme));
131
+ container.addChild(box);
199
132
  }
200
133
 
201
134
  function createSlashResultComponent(
202
- details: SlashMessageDetails,
203
- options: { expanded: boolean },
204
- theme: ExtensionContext["ui"]["theme"],
205
- requestRender: () => void,
135
+ details: SlashMessageDetails,
136
+ options: { expanded: boolean },
137
+ theme: ExtensionContext["ui"]["theme"],
206
138
  ): Container {
207
- const container = new Container();
208
- const animationState: { subagentResultAnimationTimer?: ReturnType<typeof setInterval> } = {};
209
- let lastVersion = -1;
210
- container.render = (width: number): string[] => {
211
- const snapshot = getSlashRenderableSnapshot(details);
212
- syncResultAnimation(snapshot.result, { state: animationState, invalidate: requestRender });
213
- if (
214
- snapshot.version !== lastVersion ||
215
- isSlashResultRunning(snapshot.result)
216
- ) {
217
- lastVersion = snapshot.version;
218
- rebuildSlashResultContainer(container, snapshot.result, options, theme);
219
- }
220
- return Container.prototype.render.call(container, width);
221
- };
222
- return container;
139
+ const container = new Container();
140
+ let lastVersion = -1;
141
+ container.render = (width: number): string[] => {
142
+ const snapshot = getSlashRenderableSnapshot(details);
143
+ if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {
144
+ lastVersion = snapshot.version;
145
+ rebuildSlashResultContainer(container, snapshot.result, options, theme);
146
+ }
147
+ return Container.prototype.render.call(container, width);
148
+ };
149
+ return container;
223
150
  }
224
151
 
225
- function parseSubagentNotifyContent(
226
- content: string,
227
- ): SubagentNotifyDetails | undefined {
228
- const lines = content.split("\n");
229
- const header = lines[0] ?? "";
230
- const match = header.match(
231
- /^Background task (completed|failed|paused): \*\*(.+?)\*\*(?:\s+(\([^)]*\)))?$/,
232
- );
233
- if (!match) return undefined;
234
- const body = lines.slice(2);
235
- let sessionIndex = -1;
236
- for (let i = body.length - 1; i >= 1; i--) {
237
- if (
238
- body[i - 1]?.trim() === "" &&
239
- /^(Session|Session file|Session share error):\s+/.test(body[i]!)
240
- ) {
241
- sessionIndex = i;
242
- break;
243
- }
244
- }
245
- const sessionLine = sessionIndex >= 0 ? body[sessionIndex] : undefined;
246
- const resultLines = sessionIndex >= 0 ? body.slice(0, sessionIndex) : body;
247
- const resultPreview = resultLines.join("\n").trim() || "(no output)";
248
- let sessionLabel: string | undefined;
249
- let sessionValue: string | undefined;
250
- if (sessionLine) {
251
- const separator = sessionLine.indexOf(":");
252
- sessionLabel = sessionLine.slice(0, separator).toLowerCase();
253
- sessionValue = sessionLine.slice(separator + 1).trim();
254
- }
255
- return {
256
- agent: match[2]!,
257
- status: match[1] as SubagentNotifyDetails["status"],
258
- ...(match[3] ? { taskInfo: match[3] } : {}),
259
- resultPreview,
260
- ...(sessionLabel && sessionValue ? { sessionLabel, sessionValue } : {}),
261
- };
152
+ function parseSubagentNotifyContent(content: string): SubagentNotifyDetails | undefined {
153
+ const lines = content.split("\n");
154
+ const header = lines[0] ?? "";
155
+ const match = header.match(/^Background task (completed|failed|paused): \*\*(.+?)\*\*(?:\s+(\([^)]*\)))?$/);
156
+ if (!match) return undefined;
157
+ const body = lines.slice(2);
158
+ let sessionIndex = -1;
159
+ for (let i = body.length - 1; i >= 1; i--) {
160
+ if (body[i - 1]?.trim() === "" && /^(Session|Session file|Session share error):\s+/.test(body[i]!)) {
161
+ sessionIndex = i;
162
+ break;
163
+ }
164
+ }
165
+ const sessionLine = sessionIndex >= 0 ? body[sessionIndex] : undefined;
166
+ const resultLines = sessionIndex >= 0 ? body.slice(0, sessionIndex) : body;
167
+ const resultPreview = resultLines.join("\n").trim() || "(no output)";
168
+ let sessionLabel: string | undefined;
169
+ let sessionValue: string | undefined;
170
+ if (sessionLine) {
171
+ const separator = sessionLine.indexOf(":");
172
+ sessionLabel = sessionLine.slice(0, separator).toLowerCase();
173
+ sessionValue = sessionLine.slice(separator + 1).trim();
174
+ }
175
+ return {
176
+ agent: match[2]!,
177
+ status: match[1] as SubagentNotifyDetails["status"],
178
+ ...(match[3] ? { taskInfo: match[3] } : {}),
179
+ resultPreview,
180
+ ...(sessionLabel && sessionValue ? { sessionLabel, sessionValue } : {}),
181
+ };
262
182
  }
263
183
 
264
184
  class SubagentControlNoticeComponent implements Component {
265
- constructor(
266
- private readonly details: SubagentControlMessageDetails,
267
- private readonly theme: ExtensionContext["ui"]["theme"],
268
- ) {}
269
-
270
- invalidate(): void {}
271
-
272
- render(width: number): string[] {
273
- const eventLabel = this.details.event.type.replaceAll("_", " ");
274
- if (width < 3) return [truncateToWidth(`Subagent ${eventLabel}`, width)];
275
- const bodyWidth = Math.max(1, width - 2);
276
- const borderChar = "─";
277
- const header = ` ⚠ Subagent ${eventLabel}: ${this.details.event.agent} `;
278
- const headerText = truncateToWidth(header, bodyWidth, "");
279
- const headerPadding = Math.max(0, bodyWidth - visibleWidth(headerText));
280
- const lines = [
281
- this.theme.fg(
282
- "accent",
283
- `╭${headerText}${borderChar.repeat(headerPadding)}╮`,
284
- ),
285
- ];
286
-
287
- for (const line of wrapTextWithAnsi(
288
- formatSubagentControlNotice(this.details),
289
- bodyWidth,
290
- )) {
291
- const text = truncateToWidth(line, bodyWidth, "");
292
- const padding = Math.max(0, bodyWidth - visibleWidth(text));
293
- lines.push(this.theme.fg("accent", `│${text}${" ".repeat(padding)}│`));
294
- }
295
- lines.push(this.theme.fg("accent", `╰${borderChar.repeat(bodyWidth)}╯`));
296
- return lines;
297
- }
185
+ constructor(
186
+ private readonly details: SubagentControlMessageDetails,
187
+ private readonly theme: ExtensionContext["ui"]["theme"],
188
+ ) {}
189
+
190
+ invalidate(): void {}
191
+
192
+ render(width: number): string[] {
193
+ const eventLabel = this.details.event.type.replaceAll("_", " ");
194
+ if (width < 3) return [truncateToWidth(`Subagent ${eventLabel}`, width)];
195
+ const bodyWidth = Math.max(1, width - 2);
196
+ const borderChar = "─";
197
+ const header = ` ⚠ Subagent ${eventLabel}: ${this.details.event.agent} `;
198
+ const headerText = truncateToWidth(header, bodyWidth, "");
199
+ const headerPadding = Math.max(0, bodyWidth - visibleWidth(headerText));
200
+ const lines = [this.theme.fg("accent", `╭${headerText}${borderChar.repeat(headerPadding)}╮`)];
201
+
202
+ for (const line of wrapTextWithAnsi(formatSubagentControlNotice(this.details), bodyWidth)) {
203
+ const text = truncateToWidth(line, bodyWidth, "");
204
+ const padding = Math.max(0, bodyWidth - visibleWidth(text));
205
+ lines.push(this.theme.fg("accent", `│${text}${" ".repeat(padding)}│`));
206
+ }
207
+ lines.push(this.theme.fg("accent", `╰${borderChar.repeat(bodyWidth)}╯`));
208
+ return lines;
209
+ }
298
210
  }
299
211
 
300
212
  export default function registerSubagentExtension(pi: ExtensionAPI): void {
301
- if (getEnvValue(SUBAGENT_CHILD_ENV) === "1") return;
302
- const globalStore = globalThis as Record<string, unknown>;
303
- const runtimeCleanupStoreKey = "__piSubagentRuntimeCleanup";
304
- const previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];
305
- if (typeof previousRuntimeCleanup === "function") {
306
- try {
307
- previousRuntimeCleanup();
308
- } catch {
309
- // Best effort cleanup for stale timers from an older reload.
310
- }
311
- }
312
-
313
- ensureAccessibleDir(RESULTS_DIR);
314
- ensureAccessibleDir(ASYNC_DIR);
315
- cleanupOldChainDirs();
316
-
317
- const config = loadConfig();
318
- const asyncByDefault = config.asyncByDefault === true;
319
- const tempArtifactsDir = getArtifactsDir(null);
320
- cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
321
-
322
- const state: SubagentState = {
323
- baseCwd: "",
324
- currentSessionId: null,
325
- asyncJobs: new Map(),
326
- foregroundRuns: new Map(),
327
- foregroundControls: new Map(),
328
- lastForegroundControlId: null,
329
- pendingForegroundControlNotices: new Map(),
330
- cleanupTimers: new Map(),
331
- lastUiContext: null,
332
- poller: null,
333
- completionSeen: new Map(),
334
- watcher: null,
335
- watcherRestartTimer: null,
336
- resultFileCoalescer: {
337
- schedule: () => false,
338
- clear: () => {},
339
- },
340
- };
341
-
342
- const { startResultWatcher, primeExistingResults, stopResultWatcher } =
343
- createResultWatcher(pi, state, RESULTS_DIR, 10 * 60 * 1000);
344
- startResultWatcher();
345
- primeExistingResults();
346
-
347
- const runtimeCleanup = () => {
348
- stopWidgetAnimation();
349
- stopResultAnimations();
350
- stopResultWatcher();
351
- clearPendingForegroundControlNotices(state);
352
- if (state.poller) {
353
- clearInterval(state.poller);
354
- state.poller = null;
355
- }
356
- };
357
- globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
358
-
359
- const { ensurePoller, handleStarted, handleComplete, resetJobs } =
360
- createAsyncJobTracker(pi, state, ASYNC_DIR);
361
- const executor = createSubagentExecutor({
362
- pi,
363
- state,
364
- config,
365
- asyncByDefault,
366
- tempArtifactsDir,
367
- getSubagentSessionRoot,
368
- expandTilde,
369
- discoverAgents,
370
- });
371
-
372
- pi.registerMessageRenderer<SlashMessageDetails>(
373
- SLASH_RESULT_TYPE,
374
- (message, options, theme) => {
375
- const details = resolveSlashMessageDetails(message.details);
376
- if (!details) return undefined;
377
- return createSlashResultComponent(details, options, theme, () => state.lastUiContext?.ui.requestRender?.());
378
- },
379
- );
380
-
381
- pi.registerMessageRenderer<SubagentNotifyDetails>(
382
- "subagent-notify",
383
- (message, options, theme) => {
384
- const content =
385
- typeof message.content === "string" ? message.content : "";
386
- const details =
387
- (message.details as SubagentNotifyDetails | undefined) ??
388
- parseSubagentNotifyContent(content);
389
- if (!details) return new Text(content, 0, 0);
390
- const icon =
391
- details.status === "completed"
392
- ? theme.fg("success", "✓")
393
- : details.status === "paused"
394
- ? theme.fg("warning", "■")
395
- : theme.fg("error", "✗");
396
- const parts: string[] = [];
397
- if (details.taskInfo) parts.push(details.taskInfo);
398
- if (details.durationMs !== undefined)
399
- parts.push(formatDuration(details.durationMs));
400
- let text = `${icon} ${theme.bold(details.agent)} ${theme.fg("dim", details.status)}`;
401
- if (parts.length > 0)
402
- text += ` ${theme.fg("dim", "·")} ${parts.map((part) => theme.fg("dim", part)).join(` ${theme.fg("dim", "·")} `)}`;
403
- const trimmedPreview = details.resultPreview.trim();
404
- const previewLines = options.expanded
405
- ? trimmedPreview.split("\n").filter((line) => line.trim())
406
- : [trimmedPreview.split("\n", 1)[0] ?? ""].filter((line) =>
407
- line.trim(),
408
- );
409
- for (const line of previewLines.length > 0
410
- ? previewLines
411
- : ["(no output)"]) {
412
- text += `\n ${theme.fg("dim", `⎿ ${line}`)}`;
413
- }
414
- if (!options.expanded && trimmedPreview.includes("\n")) {
415
- text += `\n ${theme.fg("dim", "ctrl+o full notification")}`;
416
- }
417
- if (details.sessionLabel && details.sessionValue) {
418
- text += `\n ${theme.fg("muted", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`;
419
- }
420
- return new Text(text, 0, 0);
421
- },
422
- );
423
-
424
- pi.registerMessageRenderer<SubagentControlMessageDetails>(
425
- SUBAGENT_CONTROL_MESSAGE_TYPE,
426
- (message, _options, theme) => {
427
- const details = message.details as
428
- | SubagentControlMessageDetails
429
- | undefined;
430
- if (!details?.event) return undefined;
431
- const content =
432
- typeof message.content === "string" ? message.content : undefined;
433
- return new SubagentControlNoticeComponent(
434
- {
435
- ...details,
436
- noticeText: formatSubagentControlNotice(details, content),
437
- },
438
- theme,
439
- );
440
- },
441
- );
442
-
443
- const executeSubagentCollapsed = (
444
- id: string,
445
- params: SubagentParamsLike,
446
- signal: AbortSignal,
447
- onUpdate: ((result: AgentToolResult<Details>) => void) | undefined,
448
- ctx: ExtensionContext,
449
- ) => {
450
- if (ctx.hasUI) ctx.ui.setToolsExpanded(false);
451
- return executor.execute(id, params, signal, onUpdate, ctx);
452
- };
453
-
454
- const slashBridge = registerSlashSubagentBridge({
455
- events: pi.events,
456
- getContext: () => state.lastUiContext,
457
- execute: (id, params, signal, onUpdate, ctx) =>
458
- executeSubagentCollapsed(id, params, signal, onUpdate, ctx),
459
- });
460
-
461
- const promptTemplateBridge = registerPromptTemplateDelegationBridge({
462
- events: pi.events,
463
- getContext: () => state.lastUiContext,
464
- execute: async (requestId, request, signal, ctx, onUpdate) => {
465
- if (request.tasks && request.tasks.length > 0) {
466
- return executeSubagentCollapsed(
467
- requestId,
468
- {
469
- tasks: request.tasks,
470
- context: request.context,
471
- cwd: request.cwd,
472
- worktree: request.worktree,
473
- async: false,
474
- clarify: false,
475
- },
476
- signal,
477
- onUpdate,
478
- ctx,
479
- );
480
- }
481
- return executeSubagentCollapsed(
482
- requestId,
483
- {
484
- agent: request.agent,
485
- task: request.task,
486
- context: request.context,
487
- cwd: request.cwd,
488
- model: request.model,
489
- async: false,
490
- clarify: false,
491
- },
492
- signal,
493
- onUpdate,
494
- ctx,
495
- );
496
- },
497
- });
498
-
499
- function effectiveParallelTaskCount(
500
- tasks: Array<{ count?: unknown }> | undefined,
501
- ): number {
502
- if (!tasks || tasks.length === 0) return 0;
503
- return tasks.reduce((total, task) => {
504
- const count =
505
- typeof task.count === "number" &&
506
- Number.isInteger(task.count) &&
507
- task.count >= 1
508
- ? task.count
509
- : 1;
510
- return total + count;
511
- }, 0);
512
- }
513
-
514
- const tool: ToolDefinition<typeof SubagentParams, Details> = {
515
- name: "subagent",
516
- label: "Subagent",
517
- description: `Delegate to subagents or manage agent definitions.
213
+ if (getEnvValue(SUBAGENT_CHILD_ENV) === "1") {
214
+ if (getEnvValue(SUBAGENT_FANOUT_CHILD_ENV) === "1") registerFanoutChildSubagentExtension(pi);
215
+ return;
216
+ }
217
+ const globalStore = globalThis as Record<string, unknown>;
218
+ const runtimeCleanupStoreKey = "__piSubagentRuntimeCleanup";
219
+ const previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];
220
+ if (typeof previousRuntimeCleanup === "function") {
221
+ try {
222
+ previousRuntimeCleanup();
223
+ } catch {
224
+ // Best effort cleanup for stale timers from an older reload.
225
+ }
226
+ }
227
+
228
+ ensureAccessibleDir(RESULTS_DIR);
229
+ ensureAccessibleDir(ASYNC_DIR);
230
+ cleanupOldChainDirs();
231
+
232
+ const config = loadConfig();
233
+ const asyncByDefault = config.asyncByDefault === true;
234
+ const tempArtifactsDir = getArtifactsDir(null);
235
+ cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
236
+ cleanupOldNestedRuntimeDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
237
+
238
+ const state: SubagentState = {
239
+ baseCwd: "",
240
+ currentSessionId: null,
241
+ asyncJobs: new Map(),
242
+ foregroundRuns: new Map(),
243
+ foregroundControls: new Map(),
244
+ lastForegroundControlId: null,
245
+ pendingForegroundControlNotices: new Map(),
246
+ cleanupTimers: new Map(),
247
+ lastUiContext: null,
248
+ poller: null,
249
+ completionSeen: new Map(),
250
+ watcher: null,
251
+ watcherRestartTimer: null,
252
+ resultFileCoalescer: {
253
+ schedule: () => false,
254
+ clear: () => {},
255
+ },
256
+ };
257
+
258
+ const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
259
+ pi,
260
+ state,
261
+ RESULTS_DIR,
262
+ 10 * 60 * 1000,
263
+ );
264
+ startResultWatcher();
265
+ primeExistingResults();
266
+
267
+ const runtimeCleanup = () => {
268
+ stopResultWatcher();
269
+ clearPendingForegroundControlNotices(state);
270
+ if (state.poller) {
271
+ clearInterval(state.poller);
272
+ state.poller = null;
273
+ }
274
+ };
275
+ globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
276
+
277
+ const { ensurePoller, handleStarted, handleComplete, resetJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR);
278
+ const executor = createSubagentExecutor({
279
+ pi,
280
+ state,
281
+ config,
282
+ asyncByDefault,
283
+ tempArtifactsDir,
284
+ getSubagentSessionRoot,
285
+ expandTilde,
286
+ discoverAgents,
287
+ });
288
+
289
+ pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
290
+ const details = resolveSlashMessageDetails(message.details);
291
+ if (!details) return undefined;
292
+ return createSlashResultComponent(details, options, theme);
293
+ });
294
+
295
+ pi.registerMessageRenderer<SubagentNotifyDetails>("subagent-notify", (message, options, theme) => {
296
+ const content = typeof message.content === "string" ? message.content : "";
297
+ const details = (message.details as SubagentNotifyDetails | undefined) ?? parseSubagentNotifyContent(content);
298
+ if (!details) return new Text(content, 0, 0);
299
+ const icon = details.status === "completed"
300
+ ? theme.fg("success", "✓")
301
+ : details.status === "paused"
302
+ ? theme.fg("warning", "■")
303
+ : theme.fg("error", "");
304
+ const parts: string[] = [];
305
+ if (details.taskInfo) parts.push(details.taskInfo);
306
+ if (details.durationMs !== undefined) parts.push(formatDuration(details.durationMs));
307
+ let text = `${icon} ${theme.bold(details.agent)} ${theme.fg("dim", details.status)}`;
308
+ if (parts.length > 0) text += ` ${theme.fg("dim", "·")} ${parts.map((part) => theme.fg("dim", part)).join(` ${theme.fg("dim", "·")} `)}`;
309
+ const trimmedPreview = details.resultPreview.trim();
310
+ const previewLines = options.expanded
311
+ ? trimmedPreview.split("\n").filter((line) => line.trim())
312
+ : [trimmedPreview.split("\n", 1)[0] ?? ""].filter((line) => line.trim());
313
+ for (const line of previewLines.length > 0 ? previewLines : ["(no output)"]) {
314
+ text += `\n ${theme.fg("dim", `⎿ ${line}`)}`;
315
+ }
316
+ if (!options.expanded && trimmedPreview.includes("\n")) {
317
+ text += `\n ${theme.fg("dim", "ctrl+o full notification")}`;
318
+ }
319
+ if (details.sessionLabel && details.sessionValue) {
320
+ text += `\n ${theme.fg("muted", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`;
321
+ }
322
+ return new Text(text, 0, 0);
323
+ });
324
+
325
+ pi.registerMessageRenderer<SubagentControlMessageDetails>(SUBAGENT_CONTROL_MESSAGE_TYPE, (message, _options, theme) => {
326
+ const details = message.details as SubagentControlMessageDetails | undefined;
327
+ if (!details?.event) return undefined;
328
+ const content = typeof message.content === "string" ? message.content : undefined;
329
+ return new SubagentControlNoticeComponent({ ...details, noticeText: formatSubagentControlNotice(details, content) }, theme);
330
+ });
331
+
332
+ const executeSubagentCollapsed = (id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((result: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => {
333
+ if (ctx.hasUI) ctx.ui.setToolsExpanded(false);
334
+ return executor.execute(id, params, signal, onUpdate, ctx);
335
+ };
336
+
337
+ const slashBridge = registerSlashSubagentBridge({
338
+ events: pi.events,
339
+ getContext: () => state.lastUiContext,
340
+ execute: (id, params, signal, onUpdate, ctx) =>
341
+ executeSubagentCollapsed(id, params, signal, onUpdate, ctx),
342
+ });
343
+
344
+ const promptTemplateBridge = registerPromptTemplateDelegationBridge({
345
+ events: pi.events,
346
+ getContext: () => state.lastUiContext,
347
+ execute: async (requestId, request, signal, ctx, onUpdate) => {
348
+ if (request.tasks && request.tasks.length > 0) {
349
+ return executeSubagentCollapsed(
350
+ requestId,
351
+ {
352
+ tasks: request.tasks,
353
+ context: request.context,
354
+ cwd: request.cwd,
355
+ worktree: request.worktree,
356
+ async: false,
357
+ clarify: false,
358
+ },
359
+ signal,
360
+ onUpdate,
361
+ ctx,
362
+ );
363
+ }
364
+ return executeSubagentCollapsed(
365
+ requestId,
366
+ {
367
+ agent: request.agent,
368
+ task: request.task,
369
+ context: request.context,
370
+ cwd: request.cwd,
371
+ model: request.model,
372
+ async: false,
373
+ clarify: false,
374
+ },
375
+ signal,
376
+ onUpdate,
377
+ ctx,
378
+ );
379
+ },
380
+ });
381
+
382
+ function effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {
383
+ if (!tasks || tasks.length === 0) return 0;
384
+ return tasks.reduce((total, task) => {
385
+ const count = typeof task.count === "number" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;
386
+ return total + count;
387
+ }, 0);
388
+ }
389
+
390
+ const tool: ToolDefinition<typeof SubagentParams, Details> = {
391
+ name: "subagent",
392
+ label: "Subagent",
393
+ description: `Delegate to subagents or manage agent definitions.
518
394
 
519
395
  EXECUTION (use exactly ONE mode):
520
396
  • Before executing, use { action: "list" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled.
@@ -526,7 +402,7 @@ EXECUTION (use exactly ONE mode):
526
402
  CHAIN TEMPLATE VARIABLES (use in task strings):
527
403
  • {task} - The original task/request from the user
528
404
  • {previous} - Text response from the previous step (empty for first step)
529
- • {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/pi-subagents-<scope>/chain-runs/abc123/)
405
+ • {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/${APP_NAME}-subagents-<scope>/chain-runs/abc123/)
530
406
 
531
407
  Example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {agent:"agent-b", task:"Plan based on {previous}"}] }
532
408
 
@@ -545,169 +421,156 @@ CONTROL:
545
421
 
546
422
  DIAGNOSTICS:
547
423
  • { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`,
548
- parameters: SubagentParams,
549
-
550
- execute(id, params, signal, onUpdate, ctx) {
551
- return executeSubagentCollapsed(id, params, signal, onUpdate, ctx);
552
- },
553
-
554
- renderCall(args, theme) {
555
- if (args.action) {
556
- const target = args.agent || args.chainName || "";
557
- return new Text(
558
- `${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`,
559
- 0,
560
- 0,
561
- );
562
- }
563
- const isParallel = (args.tasks?.length ?? 0) > 0;
564
- const parallelCount = effectiveParallelTaskCount(
565
- args.tasks as Array<{ count?: unknown }> | undefined,
566
- );
567
- const asyncLabel =
568
- args.async === true && args.clarify !== true && !isParallel
569
- ? theme.fg("warning", " [async]")
570
- : "";
571
- if (args.chain?.length)
572
- return new Text(
573
- `${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})${asyncLabel}`,
574
- 0,
575
- 0,
576
- );
577
- if (isParallel)
578
- return new Text(
579
- `${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})`,
580
- 0,
581
- 0,
582
- );
583
- return new Text(
584
- `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}${asyncLabel}`,
585
- 0,
586
- 0,
587
- );
588
- },
589
-
590
- renderResult(result, options, theme, context) {
591
- syncResultAnimation(result, context);
592
- return renderSubagentResult(result, options, theme);
593
- },
594
- };
595
-
596
- pi.registerTool(tool);
597
- registerSlashCommands(pi, state);
598
-
599
- const eventUnsubscribeStoreKey = "__piSubagentEventUnsubscribes";
600
- const controlNoticeSeenStoreKey = "__piSubagentVisibleControlNotices";
601
- const previousEventUnsubscribes = globalStore[eventUnsubscribeStoreKey];
602
- if (Array.isArray(previousEventUnsubscribes)) {
603
- for (const unsubscribe of previousEventUnsubscribes) {
604
- if (typeof unsubscribe !== "function") continue;
605
- try {
606
- unsubscribe();
607
- } catch {
608
- // Best effort cleanup for stale handlers from an older reload.
609
- }
610
- }
611
- }
612
- registerSubagentNotify(pi);
613
-
614
- const existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];
615
- const visibleControlNotices =
616
- existingVisibleControlNotices instanceof Set
617
- ? (existingVisibleControlNotices as Set<string>)
618
- : new Set<string>();
619
- globalStore[controlNoticeSeenStoreKey] = visibleControlNotices;
620
- const controlEventHandler = (payload: unknown) => {
621
- handleSubagentControlNotice({
622
- pi,
623
- state,
624
- visibleControlNotices,
625
- details: payload as SubagentControlMessageDetails,
626
- });
627
- };
628
- const eventUnsubscribes = [
629
- pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),
630
- pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),
631
- pi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),
632
- ];
633
- globalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;
634
-
635
- pi.on("tool_result", (event, ctx) => {
636
- if (event.toolName !== "subagent") return;
637
- if (!ctx.hasUI) return;
638
- state.lastUiContext = ctx;
639
- if (state.asyncJobs.size > 0) {
640
- renderWidget(ctx, Array.from(state.asyncJobs.values()));
641
- ctx.ui.requestRender?.();
642
- ensurePoller();
643
- }
644
- });
645
-
646
- const cleanupSessionArtifacts = (ctx: ExtensionContext) => {
647
- try {
648
- const sessionFile = ctx.sessionManager.getSessionFile();
649
- if (sessionFile) {
650
- cleanupOldArtifacts(
651
- getArtifactsDir(sessionFile),
652
- DEFAULT_ARTIFACT_CONFIG.cleanupDays,
653
- );
654
- }
655
- } catch {
656
- // Cleanup failures should not block session lifecycle events.
657
- }
658
- };
659
-
660
- const resetSessionState = (ctx: ExtensionContext) => {
661
- state.baseCwd = ctx.cwd;
662
- state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
663
- state.lastUiContext = ctx;
664
- cleanupSessionArtifacts(ctx);
665
- clearPendingForegroundControlNotices(state);
666
- resetJobs(ctx);
667
- restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
668
- primeExistingResults();
669
- };
670
-
671
- pi.on("session_start", (_event, ctx) => {
672
- resetSessionState(ctx);
673
- });
674
-
675
- pi.on("session_shutdown", () => {
676
- for (const unsubscribe of eventUnsubscribes) {
677
- try {
678
- unsubscribe();
679
- } catch {
680
- // Best effort cleanup during shutdown.
681
- }
682
- }
683
- if (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {
684
- delete globalStore[eventUnsubscribeStoreKey];
685
- }
686
- stopResultWatcher();
687
- if (state.poller) clearInterval(state.poller);
688
- state.poller = null;
689
- clearPendingForegroundControlNotices(state);
690
- for (const timer of state.cleanupTimers.values()) {
691
- clearTimeout(timer);
692
- }
693
- state.cleanupTimers.clear();
694
- state.asyncJobs.clear();
695
- clearSlashSnapshots();
696
- slashBridge.cancelAll();
697
- slashBridge.dispose();
698
- promptTemplateBridge.cancelAll();
699
- promptTemplateBridge.dispose();
700
- stopWidgetAnimation();
701
- stopResultAnimations();
702
- if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
703
- delete globalStore[runtimeCleanupStoreKey];
704
- }
705
- try {
706
- if (state.lastUiContext?.hasUI) {
707
- state.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);
708
- }
709
- } catch (error) {
710
- if (!isStaleExtensionContextError(error)) throw error;
711
- }
712
- });
424
+ parameters: SubagentParams,
425
+
426
+ execute(id, params, signal, onUpdate, ctx) {
427
+ return executeSubagentCollapsed(id, params, signal, onUpdate, ctx);
428
+ },
429
+
430
+ renderCall(args, theme) {
431
+ if (args.action) {
432
+ const target = args.agent || args.chainName || "";
433
+ return new Text(
434
+ `${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`,
435
+ 0, 0,
436
+ );
437
+ }
438
+ const isParallel = (args.tasks?.length ?? 0) > 0;
439
+ const parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);
440
+ const asyncLabel = args.async === true && args.clarify !== true && !isParallel ? theme.fg("warning", " [async]") : "";
441
+ if (args.chain?.length)
442
+ return new Text(
443
+ `${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})${asyncLabel}`,
444
+ 0,
445
+ 0,
446
+ );
447
+ if (isParallel)
448
+ return new Text(
449
+ `${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})`,
450
+ 0,
451
+ 0,
452
+ );
453
+ return new Text(
454
+ `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}${asyncLabel}`,
455
+ 0,
456
+ 0,
457
+ );
458
+ },
459
+
460
+ renderResult(result, options, theme, context) {
461
+ clearLegacyResultAnimationTimer(context);
462
+ return renderSubagentResult(result, options, theme);
463
+ },
464
+
465
+ };
466
+
467
+ pi.registerTool(tool);
468
+ registerSlashCommands(pi, state);
469
+
470
+ const eventUnsubscribeStoreKey = "__piSubagentEventUnsubscribes";
471
+ const controlNoticeSeenStoreKey = "__piSubagentVisibleControlNotices";
472
+ const previousEventUnsubscribes = globalStore[eventUnsubscribeStoreKey];
473
+ if (Array.isArray(previousEventUnsubscribes)) {
474
+ for (const unsubscribe of previousEventUnsubscribes) {
475
+ if (typeof unsubscribe !== "function") continue;
476
+ try {
477
+ unsubscribe();
478
+ } catch {
479
+ // Best effort cleanup for stale handlers from an older reload.
480
+ }
481
+ }
482
+ }
483
+ registerSubagentNotify(pi);
484
+
485
+ const existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];
486
+ const visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();
487
+ globalStore[controlNoticeSeenStoreKey] = visibleControlNotices;
488
+ const controlEventHandler = (payload: unknown) => {
489
+ handleSubagentControlNotice({
490
+ pi,
491
+ state,
492
+ visibleControlNotices,
493
+ details: payload as SubagentControlMessageDetails,
494
+ });
495
+ };
496
+ const eventUnsubscribes = [
497
+ pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),
498
+ pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),
499
+ pi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),
500
+ ];
501
+ globalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;
502
+
503
+ pi.on("tool_result", (event, ctx) => {
504
+ if (event.toolName !== "subagent") return;
505
+ if (!ctx.hasUI) return;
506
+ state.lastUiContext = ctx;
507
+ if (state.asyncJobs.size > 0) {
508
+ renderWidget(ctx, Array.from(state.asyncJobs.values()));
509
+ ctx.ui.requestRender?.();
510
+ ensurePoller();
511
+ }
512
+ });
513
+
514
+ const cleanupSessionArtifacts = (ctx: ExtensionContext) => {
515
+ try {
516
+ const sessionFile = ctx.sessionManager.getSessionFile();
517
+ if (sessionFile) {
518
+ cleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);
519
+ }
520
+ } catch {
521
+ // Cleanup failures should not block session lifecycle events.
522
+ }
523
+ };
524
+
525
+ const resetSessionState = (ctx: ExtensionContext) => {
526
+ state.baseCwd = ctx.cwd;
527
+ state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
528
+ state.lastUiContext = ctx;
529
+ cleanupSessionArtifacts(ctx);
530
+ clearPendingForegroundControlNotices(state);
531
+ resetJobs(ctx);
532
+ restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
533
+ primeExistingResults();
534
+ };
535
+
536
+ pi.on("session_start", (_event, ctx) => {
537
+ resetSessionState(ctx);
538
+ });
539
+
540
+ pi.on("session_shutdown", () => {
541
+ for (const unsubscribe of eventUnsubscribes) {
542
+ try {
543
+ unsubscribe();
544
+ } catch {
545
+ // Best effort cleanup during shutdown.
546
+ }
547
+ }
548
+ if (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {
549
+ delete globalStore[eventUnsubscribeStoreKey];
550
+ }
551
+ stopResultWatcher();
552
+ if (state.poller) clearInterval(state.poller);
553
+ state.poller = null;
554
+ clearPendingForegroundControlNotices(state);
555
+ for (const timer of state.cleanupTimers.values()) {
556
+ clearTimeout(timer);
557
+ }
558
+ state.cleanupTimers.clear();
559
+ state.asyncJobs.clear();
560
+ clearSlashSnapshots();
561
+ slashBridge.cancelAll();
562
+ slashBridge.dispose();
563
+ promptTemplateBridge.cancelAll();
564
+ promptTemplateBridge.dispose();
565
+ if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
566
+ delete globalStore[runtimeCleanupStoreKey];
567
+ }
568
+ try {
569
+ if (state.lastUiContext?.hasUI) {
570
+ state.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);
571
+ }
572
+ } catch (error) {
573
+ if (!isStaleExtensionContextError(error)) throw error;
574
+ }
575
+ });
713
576
  }