@bastani/atomic 0.9.5-alpha.7 → 0.9.5-alpha.9

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 (317) hide show
  1. package/CHANGELOG.md +33 -2
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/CHANGELOG.md +17 -0
  4. package/dist/builtin/intercom/README.md +27 -21
  5. package/dist/builtin/intercom/broker/broker.ts +3 -6
  6. package/dist/builtin/intercom/broker/paths.ts +42 -5
  7. package/dist/builtin/intercom/broker/spawn.ts +78 -10
  8. package/dist/builtin/intercom/config.ts +6 -3
  9. package/dist/builtin/intercom/contact-supervisor-tool.ts +6 -4
  10. package/dist/builtin/intercom/index-heavy.ts +1 -1
  11. package/dist/builtin/intercom/index.ts +162 -130
  12. package/dist/builtin/intercom/intercom-tool.ts +9 -3
  13. package/dist/builtin/intercom/package.json +10 -1
  14. package/dist/builtin/intercom/result-renderers.ts +1 -1
  15. package/dist/builtin/intercom/ui/compose.ts +2 -2
  16. package/dist/builtin/intercom/ui/inline-message.ts +2 -2
  17. package/dist/builtin/intercom/ui/session-list.ts +2 -2
  18. package/dist/builtin/mcp/CHANGELOG.md +11 -0
  19. package/dist/builtin/mcp/README.md +6 -6
  20. package/dist/builtin/mcp/direct-tools.ts +41 -23
  21. package/dist/builtin/mcp/index.ts +31 -4
  22. package/dist/builtin/mcp/init.ts +4 -43
  23. package/dist/builtin/mcp/metadata-hydration.ts +37 -0
  24. package/dist/builtin/mcp/package.json +1 -1
  25. package/dist/builtin/mcp/proxy-info-modes.ts +67 -14
  26. package/dist/builtin/mcp/startup-warmup.ts +98 -0
  27. package/dist/builtin/mcp/tool-result-renderer.ts +3 -3
  28. package/dist/builtin/subagents/CHANGELOG.md +18 -0
  29. package/dist/builtin/subagents/package.json +1 -1
  30. package/dist/builtin/subagents/src/extension/index.ts +14 -25
  31. package/dist/builtin/subagents/src/extension/startup-maintenance.ts +89 -0
  32. package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +23 -18
  33. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +20 -10
  34. package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +13 -3
  35. package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +10 -2
  36. package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +28 -1
  37. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +15 -48
  38. package/dist/builtin/subagents/src/tui/render-layout.ts +9 -0
  39. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +27 -25
  40. package/dist/builtin/subagents/src/tui/render-widget.ts +72 -66
  41. package/dist/builtin/subagents/src/tui/render.ts +1 -1
  42. package/dist/builtin/web-access/package.json +1 -1
  43. package/dist/builtin/workflows/CHANGELOG.md +29 -0
  44. package/dist/builtin/workflows/README.md +4 -2
  45. package/dist/builtin/workflows/builtin/goal-artifacts.ts +3 -1
  46. package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -2
  47. package/dist/builtin/workflows/builtin/goal-reducer.ts +21 -0
  48. package/dist/builtin/workflows/builtin/goal-review.ts +60 -6
  49. package/dist/builtin/workflows/builtin/goal-runner.ts +71 -22
  50. package/dist/builtin/workflows/builtin/goal-types.ts +7 -1
  51. package/dist/builtin/workflows/builtin/ralph-core.ts +52 -4
  52. package/dist/builtin/workflows/builtin/ralph-review-gate.ts +10 -3
  53. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +23 -2
  54. package/dist/builtin/workflows/builtin/ralph-runner.ts +38 -16
  55. package/dist/builtin/workflows/builtin/review-convergence.ts +111 -0
  56. package/dist/builtin/workflows/package.json +1 -1
  57. package/dist/builtin/workflows/skills/create-spec/SKILL.md +2 -0
  58. package/dist/builtin/workflows/skills/impeccable/SKILL.md +16 -13
  59. package/dist/builtin/workflows/skills/impeccable/reference/adapt.md +1 -0
  60. package/dist/builtin/workflows/skills/impeccable/reference/adapt.native.md +58 -0
  61. package/dist/builtin/workflows/skills/impeccable/reference/android.md +40 -0
  62. package/dist/builtin/workflows/skills/impeccable/reference/animate.md +2 -0
  63. package/dist/builtin/workflows/skills/impeccable/reference/audit.md +2 -0
  64. package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +139 -0
  65. package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +69 -62
  66. package/dist/builtin/workflows/skills/impeccable/reference/critique.md +21 -7
  67. package/dist/builtin/workflows/skills/impeccable/reference/document.md +1 -1
  68. package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +92 -0
  69. package/dist/builtin/workflows/skills/impeccable/reference/init.md +29 -7
  70. package/dist/builtin/workflows/skills/impeccable/reference/ios.md +45 -0
  71. package/dist/builtin/workflows/skills/impeccable/reference/layout.md +25 -1
  72. package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +23 -1
  73. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +2 -1
  74. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +75 -14
  75. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +1 -1
  76. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +31 -0
  77. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +64 -0
  78. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +118 -11
  79. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +16 -11
  80. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +66 -0
  81. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +42 -10
  82. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/fonts.mjs +30 -0
  83. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +4 -6
  84. package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +660 -0
  85. package/dist/builtin/workflows/skills/impeccable/scripts/hook-before-edit.mjs +516 -0
  86. package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +1764 -0
  87. package/dist/builtin/workflows/skills/impeccable/scripts/hook.mjs +61 -0
  88. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +4 -2
  89. package/dist/builtin/workflows/skills/impeccable/scripts/lib/is-generated.mjs +2 -2
  90. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +13 -16
  91. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +17 -11
  92. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +104 -37
  93. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +9 -0
  94. package/dist/builtin/workflows/src/durable/backend.ts +12 -0
  95. package/dist/builtin/workflows/src/durable/child-primitive.ts +12 -8
  96. package/dist/builtin/workflows/src/durable/dbos-backend.ts +63 -2
  97. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +28 -0
  98. package/dist/builtin/workflows/src/durable/resume-catalog.ts +12 -0
  99. package/dist/builtin/workflows/src/durable/resume-runtime.ts +5 -2
  100. package/dist/builtin/workflows/src/durable/stage-primitive.ts +165 -27
  101. package/dist/builtin/workflows/src/durable/types.ts +30 -1
  102. package/dist/builtin/workflows/src/engine/primitives/task.ts +1 -0
  103. package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +1 -2
  104. package/dist/builtin/workflows/src/engine/run-returned-status.ts +50 -16
  105. package/dist/builtin/workflows/src/engine/run.ts +32 -32
  106. package/dist/builtin/workflows/src/engine/runtime.ts +5 -0
  107. package/dist/builtin/workflows/src/extension/extension-factory.ts +3 -0
  108. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +9 -2
  109. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +137 -27
  110. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +22 -6
  111. package/dist/builtin/workflows/src/extension/public-types.ts +1 -1
  112. package/dist/builtin/workflows/src/extension/workflow-command-completions.ts +13 -1
  113. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +19 -4
  114. package/dist/builtin/workflows/src/extension/workflow-command-surfaces.ts +9 -0
  115. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +2 -0
  116. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +48 -23
  117. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +20 -8
  118. package/dist/builtin/workflows/src/extension/workflow-tool.ts +24 -8
  119. package/dist/builtin/workflows/src/runs/background/status.ts +4 -3
  120. package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +13 -3
  121. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +29 -8
  122. package/dist/builtin/workflows/src/runs/foreground/executor-hil.ts +2 -1
  123. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +4 -3
  124. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +18 -19
  125. package/dist/builtin/workflows/src/runs/foreground/stage-runner-unresolved-overflow.ts +38 -0
  126. package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +155 -20
  127. package/dist/builtin/workflows/src/runs/shared/worktree-types.ts +6 -0
  128. package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -1
  129. package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +18 -1
  130. package/dist/builtin/workflows/src/shared/persistence-restore.ts +23 -5
  131. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
  132. package/dist/builtin/workflows/src/shared/returned-run-status.ts +116 -0
  133. package/dist/builtin/workflows/src/shared/workflow-failures-decisions.ts +2 -0
  134. package/dist/builtin/workflows/src/tui/graph-view-render-helpers.ts +21 -3
  135. package/dist/builtin/workflows/src/tui/graph-view-state.ts +3 -0
  136. package/dist/builtin/workflows/src/tui/graph-view-types.ts +3 -0
  137. package/dist/builtin/workflows/src/tui/status-list.ts +22 -15
  138. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +4 -4
  139. package/dist/bun/internal-intercom-broker.d.ts +6 -0
  140. package/dist/bun/internal-intercom-broker.d.ts.map +1 -0
  141. package/dist/bun/internal-intercom-broker.js +39 -0
  142. package/dist/bun/internal-intercom-broker.js.map +1 -0
  143. package/dist/bun/split-loader.d.ts.map +1 -1
  144. package/dist/bun/split-loader.js +21 -8
  145. package/dist/bun/split-loader.js.map +1 -1
  146. package/dist/core/agent-session-auto-compaction.d.ts +21 -2
  147. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  148. package/dist/core/agent-session-auto-compaction.js +163 -11
  149. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  150. package/dist/core/agent-session-events.d.ts.map +1 -1
  151. package/dist/core/agent-session-events.js +10 -1
  152. package/dist/core/agent-session-events.js.map +1 -1
  153. package/dist/core/agent-session-methods.d.ts +12 -3
  154. package/dist/core/agent-session-methods.d.ts.map +1 -1
  155. package/dist/core/agent-session-methods.js.map +1 -1
  156. package/dist/core/agent-session-models.d.ts +2 -2
  157. package/dist/core/agent-session-models.d.ts.map +1 -1
  158. package/dist/core/agent-session-models.js.map +1 -1
  159. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  160. package/dist/core/agent-session-prompt.js +1 -0
  161. package/dist/core/agent-session-prompt.js.map +1 -1
  162. package/dist/core/agent-session-retry.d.ts +2 -0
  163. package/dist/core/agent-session-retry.d.ts.map +1 -1
  164. package/dist/core/agent-session-retry.js +152 -4
  165. package/dist/core/agent-session-retry.js.map +1 -1
  166. package/dist/core/agent-session-services.d.ts +1 -0
  167. package/dist/core/agent-session-services.d.ts.map +1 -1
  168. package/dist/core/agent-session-services.js +1 -0
  169. package/dist/core/agent-session-services.js.map +1 -1
  170. package/dist/core/agent-session-types.d.ts +15 -1
  171. package/dist/core/agent-session-types.d.ts.map +1 -1
  172. package/dist/core/agent-session-types.js.map +1 -1
  173. package/dist/core/agent-session.d.ts +6 -0
  174. package/dist/core/agent-session.d.ts.map +1 -1
  175. package/dist/core/agent-session.js +6 -0
  176. package/dist/core/agent-session.js.map +1 -1
  177. package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
  178. package/dist/core/compaction/context-compaction-runner.js +36 -20
  179. package/dist/core/compaction/context-compaction-runner.js.map +1 -1
  180. package/dist/core/copilot-anthropic-sse-repair.d.ts +23 -0
  181. package/dist/core/copilot-anthropic-sse-repair.d.ts.map +1 -0
  182. package/dist/core/copilot-anthropic-sse-repair.js +340 -0
  183. package/dist/core/copilot-anthropic-sse-repair.js.map +1 -0
  184. package/dist/core/copilot-gemini-reasoning.d.ts +16 -8
  185. package/dist/core/copilot-gemini-reasoning.d.ts.map +1 -1
  186. package/dist/core/copilot-gemini-reasoning.js +27 -22
  187. package/dist/core/copilot-gemini-reasoning.js.map +1 -1
  188. package/dist/core/copilot-hosts.d.ts +12 -0
  189. package/dist/core/copilot-hosts.d.ts.map +1 -0
  190. package/dist/core/copilot-hosts.js +33 -0
  191. package/dist/core/copilot-hosts.js.map +1 -0
  192. package/dist/core/extensions/agent-events.d.ts +1 -1
  193. package/dist/core/extensions/agent-events.d.ts.map +1 -1
  194. package/dist/core/extensions/agent-events.js.map +1 -1
  195. package/dist/core/extensions/loader-virtual-modules.d.ts +8 -1
  196. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  197. package/dist/core/extensions/loader-virtual-modules.js +2 -1
  198. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  199. package/dist/core/footer-data-provider.d.ts +6 -0
  200. package/dist/core/footer-data-provider.d.ts.map +1 -1
  201. package/dist/core/footer-data-provider.js +72 -29
  202. package/dist/core/footer-data-provider.js.map +1 -1
  203. package/dist/core/http-dispatcher.d.ts.map +1 -1
  204. package/dist/core/http-dispatcher.js +5 -5
  205. package/dist/core/http-dispatcher.js.map +1 -1
  206. package/dist/core/openai-responses-payload-sanitizer.d.ts +1 -0
  207. package/dist/core/openai-responses-payload-sanitizer.d.ts.map +1 -1
  208. package/dist/core/openai-responses-payload-sanitizer.js +24 -9
  209. package/dist/core/openai-responses-payload-sanitizer.js.map +1 -1
  210. package/dist/core/sdk-types.d.ts +2 -0
  211. package/dist/core/sdk-types.d.ts.map +1 -1
  212. package/dist/core/sdk-types.js.map +1 -1
  213. package/dist/core/sdk.d.ts.map +1 -1
  214. package/dist/core/sdk.js +1 -0
  215. package/dist/core/sdk.js.map +1 -1
  216. package/dist/core/settings-manager-basic-accessors.d.ts +1 -0
  217. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  218. package/dist/core/settings-manager-basic-accessors.js +6 -0
  219. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  220. package/dist/core/settings-types.d.ts +1 -0
  221. package/dist/core/settings-types.d.ts.map +1 -1
  222. package/dist/core/settings-types.js.map +1 -1
  223. package/dist/core/slash-commands.d.ts +2 -0
  224. package/dist/core/slash-commands.d.ts.map +1 -1
  225. package/dist/core/slash-commands.js +141 -0
  226. package/dist/core/slash-commands.js.map +1 -1
  227. package/dist/index-extensions.d.ts +1 -1
  228. package/dist/index-extensions.d.ts.map +1 -1
  229. package/dist/index-extensions.js.map +1 -1
  230. package/dist/index.d.ts +1 -0
  231. package/dist/index.d.ts.map +1 -1
  232. package/dist/index.js +1 -0
  233. package/dist/index.js.map +1 -1
  234. package/dist/main-deferred-startup.d.ts +12 -0
  235. package/dist/main-deferred-startup.d.ts.map +1 -1
  236. package/dist/main-deferred-startup.js +23 -2
  237. package/dist/main-deferred-startup.js.map +1 -1
  238. package/dist/main-early-input.d.ts +41 -0
  239. package/dist/main-early-input.d.ts.map +1 -0
  240. package/dist/main-early-input.js +154 -0
  241. package/dist/main-early-input.js.map +1 -0
  242. package/dist/main.d.ts +0 -6
  243. package/dist/main.d.ts.map +1 -1
  244. package/dist/main.js +20 -14
  245. package/dist/main.js.map +1 -1
  246. package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
  247. package/dist/modes/interactive/components/chat-session-host-actions.js +0 -1
  248. package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
  249. package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
  250. package/dist/modes/interactive/components/chat-session-host-events.js +15 -0
  251. package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
  252. package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
  253. package/dist/modes/interactive/interactive-agent-events.js +25 -0
  254. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  255. package/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
  256. package/dist/modes/interactive/interactive-autocomplete.js +21 -9
  257. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  258. package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
  259. package/dist/modes/interactive/interactive-deferred-startup.js +41 -28
  260. package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
  261. package/dist/modes/interactive/interactive-hotkeys-debug.d.ts.map +1 -1
  262. package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -0
  263. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  264. package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
  265. package/dist/modes/interactive/interactive-input-handling.js +318 -185
  266. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  267. package/dist/modes/interactive/interactive-mode-base.d.ts +12 -0
  268. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  269. package/dist/modes/interactive/interactive-mode-base.js +39 -0
  270. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  271. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
  272. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  273. package/dist/modes/interactive/interactive-mode-deps.js +1 -1
  274. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  275. package/dist/modes/interactive/interactive-mode-surface.d.ts +6 -2
  276. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  277. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  278. package/dist/modes/interactive/interactive-mode-types.d.ts +3 -0
  279. package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
  280. package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
  281. package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
  282. package/dist/modes/interactive/interactive-model-routing.js +3 -1
  283. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  284. package/dist/modes/interactive/interactive-process-lifecycle.d.ts.map +1 -1
  285. package/dist/modes/interactive/interactive-process-lifecycle.js +0 -3
  286. package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
  287. package/dist/modes/interactive/interactive-render-chat.d.ts.map +1 -1
  288. package/dist/modes/interactive/interactive-render-chat.js +36 -10
  289. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  290. package/dist/modes/interactive/interactive-session-routing.d.ts.map +1 -1
  291. package/dist/modes/interactive/interactive-session-routing.js +5 -1
  292. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  293. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  294. package/dist/modes/interactive/interactive-startup.js +26 -22
  295. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  296. package/dist/modes/interactive/theme/global-theme.d.ts.map +1 -1
  297. package/dist/modes/interactive/theme/global-theme.js +26 -2
  298. package/dist/modes/interactive/theme/global-theme.js.map +1 -1
  299. package/dist/utils/fs-watch.d.ts +24 -1
  300. package/dist/utils/fs-watch.d.ts.map +1 -1
  301. package/dist/utils/fs-watch.js +60 -5
  302. package/dist/utils/fs-watch.js.map +1 -1
  303. package/docs/compaction.md +8 -0
  304. package/docs/development.md +4 -0
  305. package/docs/extensions.md +5 -0
  306. package/docs/json.md +3 -1
  307. package/docs/providers.md +1 -0
  308. package/docs/quickstart.md +1 -1
  309. package/docs/rpc.md +3 -1
  310. package/docs/settings.md +27 -1
  311. package/docs/subagents.md +4 -0
  312. package/docs/terminal-setup.md +4 -0
  313. package/docs/usage.md +2 -2
  314. package/docs/windows.md +4 -0
  315. package/docs/workflows.md +21 -9
  316. package/npm-shrinkwrap.json +513 -23
  317. package/package.json +3 -2
@@ -7,21 +7,18 @@ import { APP_NAME, getEnvValue } from "@bastani/atomic";
7
7
  import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@bastani/atomic";
8
8
  import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
9
9
  import { discoverAgents } from "../agents/agents.ts";
10
- import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
10
+ import { getArtifactsDir } from "../shared/artifacts.ts";
11
11
  import { resolveCurrentSessionId } from "../shared/session-identity.ts";
12
- import { cleanupOldChainDirs } from "../shared/settings.ts";
13
12
  import { advanceResultPulseFrame, renderLiveSubagentResult, renderSubagentResult, stopResultAnimations, stopWidgetAnimation, type SubagentResultRenderState } from "../tui/render.ts";
14
13
  import { SubagentParams } from "./schemas.ts";
15
14
  import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
16
15
  import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
17
- import { createResultWatcher } from "../runs/background/result-watcher.ts";
18
16
  import { registerSlashCommands } from "../slash/slash-commands.ts";
19
17
  import { registerPromptTemplateDelegationBridge } from "../slash/prompt-template-bridge.ts";
20
18
  import { registerSlashSubagentBridge } from "../slash/slash-bridge.ts";
21
19
  import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "../slash/slash-live-state.ts";
22
20
  import { inspectSubagentStatus } from "../runs/background/run-status.ts";
23
21
  import registerSubagentNotify, { type SubagentNotifyDetails } from "../runs/background/notify.ts";
24
- import { cleanupOldNestedRuntimeDirs } from "../runs/shared/nested-events.ts";
25
22
  import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
26
23
  import registerFanoutChildSubagentExtension from "./fanout-child.ts";
27
24
  import { formatDuration, shortenPath } from "../shared/formatters.ts";
@@ -29,6 +26,7 @@ import { loadConfig } from "./config.ts";
29
26
  import { DEFAULT_PROMPT_GUIDANCE } from "./prompt-guidance.ts";
30
27
  import { type Details, type SubagentState, ASYNC_DIR, DEFAULT_ARTIFACT_CONFIG, RESULTS_DIR, SLASH_RESULT_TYPE, SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CONTROL_EVENT, WIDGET_KEY } from "../shared/types.ts";
31
28
  import { clearPendingForegroundControlNotices, formatSubagentControlNotice, handleSubagentControlNotice, SUBAGENT_CONTROL_MESSAGE_TYPE, type SubagentControlMessageDetails } from "./control-notices.ts";
29
+ import { createSubagentStartupMaintenance } from "./startup-maintenance.ts";
32
30
  export { loadConfig } from "./config.ts";
33
31
  function getSubagentSessionRoot(parentSessionFile: string | null): string {
34
32
  if (parentSessionFile) {
@@ -171,12 +169,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
171
169
  }
172
170
  ensureAccessibleDir(RESULTS_DIR);
173
171
  ensureAccessibleDir(ASYNC_DIR);
174
- cleanupOldChainDirs();
175
172
  const config = loadConfig();
176
173
  const asyncByDefault = config.asyncByDefault === true;
177
174
  const tempArtifactsDir = getArtifactsDir(null);
178
- cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
179
- cleanupOldNestedRuntimeDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
180
175
  const state: SubagentState = {
181
176
  baseCwd: "",
182
177
  currentSessionId: null,
@@ -197,16 +192,16 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
197
192
  clear: () => {},
198
193
  },
199
194
  };
200
- const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
201
- pi,
202
- state,
203
- RESULTS_DIR,
204
- 10 * 60 * 1000,
205
- );
206
- startResultWatcher();
207
- primeExistingResults();
195
+ const maintenance = createSubagentStartupMaintenance(pi, state, {
196
+ resultsDir: RESULTS_DIR,
197
+ artifactCleanupDays: DEFAULT_ARTIFACT_CONFIG.cleanupDays,
198
+ resultTtlMs: 10 * 60 * 1000,
199
+ });
200
+ maintenance.scheduleStartupCleanup();
201
+ maintenance.startResultWatcherDeferred();
202
+ maintenance.primeExistingResultsDeferred();
208
203
  const runtimeCleanup = () => {
209
- stopResultWatcher();
204
+ maintenance.stop();
210
205
  stopWidgetAnimation();
211
206
  stopResultAnimations();
212
207
  clearPendingForegroundControlNotices(state);
@@ -430,13 +425,7 @@ DIAGNOSTICS:
430
425
  if (state.asyncJobs.size > 0) ensurePoller();
431
426
  });
432
427
  const cleanupSessionArtifacts = (ctx: ExtensionContext) => {
433
- try {
434
- const sessionFile = ctx.sessionManager.getSessionFile();
435
- if (sessionFile) {
436
- cleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);
437
- }
438
- } catch {
439
- }
428
+ maintenance.cleanupSessionArtifactsDeferred(ctx);
440
429
  };
441
430
  const resetSessionState = (ctx: ExtensionContext) => {
442
431
  state.baseCwd = ctx.cwd;
@@ -447,7 +436,7 @@ DIAGNOSTICS:
447
436
  resetJobs(ctx);
448
437
  hydrateActiveJobs(ctx);
449
438
  restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
450
- primeExistingResults();
439
+ maintenance.primeExistingResultsDeferred();
451
440
  };
452
441
  pi.on("session_start", (_event, ctx) => {
453
442
  resetSessionState(ctx);
@@ -462,7 +451,7 @@ DIAGNOSTICS:
462
451
  if (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {
463
452
  delete globalStore[eventUnsubscribeStoreKey];
464
453
  }
465
- stopResultWatcher();
454
+ maintenance.stop();
466
455
  stopWidgetAnimation();
467
456
  stopResultAnimations();
468
457
  if (state.poller) clearInterval(state.poller);
@@ -0,0 +1,89 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@bastani/atomic";
2
+ import type { SubagentState } from "../shared/types.ts";
3
+ import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
4
+ import { cleanupOldChainDirs } from "../shared/settings.ts";
5
+ import { cleanupOldNestedRuntimeDirs } from "../runs/shared/nested-events.ts";
6
+ import { createResultWatcher } from "../runs/background/result-watcher.ts";
7
+
8
+ export interface SubagentStartupMaintenance {
9
+ scheduleStartupCleanup(): void;
10
+ startResultWatcherDeferred(): void;
11
+ primeExistingResultsDeferred(): void;
12
+ cleanupSessionArtifactsDeferred(ctx: ExtensionContext): void;
13
+ stop(): void;
14
+ }
15
+
16
+ function scheduleMacrotask(task: () => void): () => void {
17
+ let cancelled = false;
18
+ const handle = setImmediate(() => {
19
+ if (!cancelled) task();
20
+ });
21
+ handle.unref?.();
22
+ return () => {
23
+ cancelled = true;
24
+ clearImmediate(handle);
25
+ };
26
+ }
27
+
28
+ function swallowCleanup(task: () => void): void {
29
+ try {
30
+ task();
31
+ } catch {
32
+ }
33
+ }
34
+
35
+ export function createSubagentStartupMaintenance(
36
+ pi: ExtensionAPI,
37
+ state: SubagentState,
38
+ options: {
39
+ resultsDir: string;
40
+ artifactCleanupDays: number;
41
+ resultTtlMs: number;
42
+ },
43
+ ): SubagentStartupMaintenance {
44
+ const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
45
+ pi,
46
+ state,
47
+ options.resultsDir,
48
+ options.resultTtlMs,
49
+ );
50
+ const cancelTasks = new Set<() => void>();
51
+ const schedule = (task: () => void): void => {
52
+ const cancel = scheduleMacrotask(() => {
53
+ cancelTasks.delete(cancel);
54
+ task();
55
+ });
56
+ cancelTasks.add(cancel);
57
+ };
58
+
59
+ return {
60
+ scheduleStartupCleanup() {
61
+ schedule(() => {
62
+ swallowCleanup(cleanupOldChainDirs);
63
+ swallowCleanup(() => cleanupAllArtifactDirs(options.artifactCleanupDays));
64
+ swallowCleanup(() => cleanupOldNestedRuntimeDirs(options.artifactCleanupDays));
65
+ });
66
+ },
67
+ startResultWatcherDeferred() {
68
+ schedule(startResultWatcher);
69
+ },
70
+ primeExistingResultsDeferred() {
71
+ schedule(primeExistingResults);
72
+ },
73
+ cleanupSessionArtifactsDeferred(ctx) {
74
+ let sessionFile: string | null | undefined;
75
+ try {
76
+ sessionFile = ctx.sessionManager.getSessionFile();
77
+ } catch {
78
+ return;
79
+ }
80
+ if (!sessionFile) return;
81
+ schedule(() => swallowCleanup(() => cleanupOldArtifacts(getArtifactsDir(sessionFile), options.artifactCleanupDays)));
82
+ },
83
+ stop() {
84
+ for (const cancel of cancelTasks) cancel();
85
+ cancelTasks.clear();
86
+ stopResultWatcher();
87
+ },
88
+ };
89
+ }
@@ -4,7 +4,7 @@ import * as path from "node:path";
4
4
  import { createRequire } from "node:module";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { APP_NAME } from "@bastani/atomic";
7
- import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
7
+ import { formatPiSpawnError, resolvePiPackageRoot, validatePiSpawnCwd } from "../shared/pi-spawn.ts";
8
8
  import { getAsyncConfigPath, TEMP_ROOT_DIR, type SubagentRunMode } from "../../shared/types.ts";
9
9
  import type { AsyncExecutionResult, AsyncSpawnResult } from "./async-execution-types.ts";
10
10
 
@@ -85,33 +85,38 @@ export function writeAsyncRunnerConfig(cfg: object, suffix: string): string {
85
85
  }
86
86
 
87
87
  export function spawnRunner(cfg: object, suffix: string, cwd: string): AsyncSpawnResult {
88
+ const cwdValidation = validatePiSpawnCwd(cwd);
89
+ if (!cwdValidation.ok) return { error: cwdValidation.error };
90
+
88
91
  if (!jitiCliPath) {
89
92
  return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
90
93
  }
91
94
 
92
- try {
93
- const cwdStats = fs.statSync(cwd);
94
- if (!cwdStats.isDirectory()) {
95
- return { error: `cwd is not a directory: ${cwd}` };
96
- }
97
- } catch {
98
- return { error: `cwd does not exist: ${cwd}` };
99
- }
100
-
101
95
  const cfgPath = writeAsyncRunnerConfig(cfg, suffix);
102
96
  const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
103
97
 
104
- const proc = spawn(process.execPath, [jitiCliPath, runner, cfgPath], {
105
- cwd,
106
- detached: true,
107
- stdio: "ignore",
108
- windowsHide: true,
109
- });
98
+ const spawnSpec = { command: process.execPath, args: [jitiCliPath, runner, cfgPath] };
99
+ let proc: ReturnType<typeof spawn>;
100
+ try {
101
+ proc = spawn(spawnSpec.command, spawnSpec.args, {
102
+ cwd,
103
+ detached: true,
104
+ stdio: "ignore",
105
+ windowsHide: true,
106
+ });
107
+ } catch (error) {
108
+ const spawnError = error instanceof Error ? error : new Error(String(error));
109
+ return { error: formatPiSpawnError(spawnError, spawnSpec, cwd) };
110
+ }
110
111
  proc.on("error", (error) => {
111
- console.error(`[${APP_NAME}-subagents] async spawn failed: ${error.message}`);
112
+ console.error(`[${APP_NAME}-subagents] async spawn failed: ${formatPiSpawnError(error, spawnSpec, cwd)}`);
112
113
  });
113
114
  if (typeof proc.pid !== "number") {
114
- return { error: `async runner did not produce a pid for cwd: ${cwd}` };
115
+ const noPidError = Object.assign(
116
+ new Error(`spawn ${spawnSpec.command} failed before assigning a pid`),
117
+ fs.existsSync(spawnSpec.command) ? {} : { code: "ENOENT" },
118
+ );
119
+ return { error: formatPiSpawnError(noPidError, spawnSpec, cwd) };
115
120
  }
116
121
  proc.unref();
117
122
  return { pid: proc.pid };
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import { isSafeFsWatchPathError, watchWithErrorHandler } from "@bastani/atomic";
3
4
  import { buildCompletionKey, markSeenWithTtl } from "./completion-dedupe.ts";
4
5
  import { createFileCoalescer } from "../../shared/file-coalescer.ts";
5
6
  import {
@@ -21,7 +22,9 @@ import { projectNestedRegistryForRoot, sanitizeSummary } from "../shared/nested-
21
22
  const WATCHER_RESTART_DELAY_MS = 3000;
22
23
  const POLL_INTERVAL_MS = 3000;
23
24
 
24
- type ResultWatcherFs = Pick<typeof fs, "existsSync" | "readFileSync" | "unlinkSync" | "readdirSync" | "mkdirSync" | "watch">;
25
+ type ResultWatcherFs = Pick<typeof fs, "existsSync" | "readFileSync" | "unlinkSync" | "readdirSync" | "mkdirSync" | "watch"> & {
26
+ realpathSync?: typeof fs.realpathSync;
27
+ };
25
28
 
26
29
  type ResultWatcherTimers = {
27
30
  setTimeout: typeof setTimeout;
@@ -30,9 +33,12 @@ type ResultWatcherTimers = {
30
33
  clearInterval: typeof clearInterval;
31
34
  };
32
35
 
36
+ type ResultWatcherSafeWatch = typeof watchWithErrorHandler;
37
+
33
38
  type ResultWatcherDeps = {
34
39
  fs?: ResultWatcherFs;
35
40
  timers?: ResultWatcherTimers;
41
+ safeWatch?: ResultWatcherSafeWatch;
36
42
  };
37
43
 
38
44
  type ResultFileChild = {
@@ -88,7 +94,7 @@ function isNotFoundError(error: unknown): boolean {
88
94
 
89
95
  function shouldFallBackToPolling(error: unknown): boolean {
90
96
  const code = getErrorCode(error);
91
- return code === "EMFILE" || code === "ENOSPC";
97
+ return code === "EMFILE" || code === "ENOSPC" || isSafeFsWatchPathError(error);
92
98
  }
93
99
 
94
100
  export function createResultWatcher(
@@ -104,6 +110,7 @@ export function createResultWatcher(
104
110
  } {
105
111
  const fsApi = deps.fs ?? fs;
106
112
  const timers = deps.timers ?? { setTimeout, clearTimeout, setInterval, clearInterval };
113
+ const safeWatch = deps.safeWatch ?? watchWithErrorHandler;
107
114
 
108
115
  const handleResult = async (file: string) => {
109
116
  const resultPath = path.join(resultsDir, file);
@@ -264,13 +271,7 @@ export function createResultWatcher(
264
271
  state.watcherRestartTimer = null;
265
272
  }
266
273
  try {
267
- state.watcher = fsApi.watch(resultsDir, (ev, file) => {
268
- if (ev !== "rename" || !file) return;
269
- const fileName = file.toString();
270
- if (!fileName.endsWith(".json")) return;
271
- state.resultFileCoalescer.schedule(fileName);
272
- });
273
- state.watcher.on("error", (error) => {
274
+ const handleWatcherError = (error: Error) => {
274
275
  if (shouldFallBackToPolling(error)) {
275
276
  startPollingFallback(error);
276
277
  return;
@@ -279,8 +280,17 @@ export function createResultWatcher(
279
280
  state.watcher?.close();
280
281
  state.watcher = null;
281
282
  scheduleRestart();
283
+ };
284
+ state.watcher = safeWatch(resultsDir, (ev, file) => {
285
+ if (ev !== "rename" || !file) return;
286
+ const fileName = file.toString();
287
+ if (!fileName.endsWith(".json")) return;
288
+ state.resultFileCoalescer.schedule(fileName);
289
+ }, handleWatcherError, {
290
+ watch: fsApi.watch,
291
+ realpathSyncNative: fsApi.realpathSync?.native,
282
292
  });
283
- state.watcher.unref?.();
293
+ state.watcher?.unref?.();
284
294
  } catch (error) {
285
295
  if (shouldFallBackToPolling(error)) {
286
296
  startPollingFallback(error);
@@ -5,7 +5,7 @@ import { appendJsonl } from "../../shared/artifacts.ts";
5
5
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
6
6
  import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "../../shared/utils.ts";
7
7
  import { getSubagentDepthEnv } from "../../shared/types.ts";
8
- import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
8
+ import { formatPiSpawnError, getPiSpawnCommand, validatePiSpawnCwd } from "../shared/pi-spawn.ts";
9
9
  import {
10
10
  assistantStopReason,
11
11
  isAssistantFailureStopReason,
@@ -29,6 +29,17 @@ export function runPiStreaming(
29
29
  registerInterrupt?: (interrupt: (() => void) | undefined) => void,
30
30
  onChildEvent?: (event: ChildEvent) => void,
31
31
  ): Promise<RunPiStreamingResult> {
32
+ const cwdValidation = validatePiSpawnCwd(cwd);
33
+ if (!cwdValidation.ok) {
34
+ return Promise.resolve({
35
+ stderr: "",
36
+ exitCode: 1,
37
+ messages: [],
38
+ usage: emptyUsage(),
39
+ error: cwdValidation.error,
40
+ finalOutput: "",
41
+ });
42
+ }
32
43
  return new Promise((resolve) => {
33
44
  const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
34
45
  const spawnEnv = {
@@ -273,8 +284,7 @@ export function runPiStreaming(
273
284
  attemptWatchdog.clear();
274
285
  outputStream.end();
275
286
  const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
276
- const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
277
- const finalError = error ?? assistantError ?? spawnErrorMessage;
287
+ const finalError = error ?? assistantError ?? formatPiSpawnError(spawnError, spawnSpec, cwd);
278
288
  resolve({
279
289
  stderr,
280
290
  exitCode: 1,
@@ -14,7 +14,7 @@ import { extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "
14
14
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
15
15
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
16
16
  import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
17
- import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
17
+ import { formatPiSpawnError, getPiSpawnCommand, validatePiSpawnCwd } from "../shared/pi-spawn.ts";
18
18
  import { assistantStopReason, isAssistantFailureStopReason, shouldStartSubagentFinalDrain } from "../shared/final-drain.ts";
19
19
  import { modelFailureMessage } from "../shared/model-fallback.ts";
20
20
  import { createAttemptWatchdog } from "../shared/attempt-watchdog.ts";
@@ -124,6 +124,14 @@ export async function runSingleAttempt(
124
124
  workflowStageSubagentGuard: options.workflowStageSubagentGuard,
125
125
  }),
126
126
  };
127
+ const cwdValidation = validatePiSpawnCwd(runCwd);
128
+ if (!cwdValidation.ok) {
129
+ cleanupTempDir(tempDir);
130
+ result.error = cwdValidation.error;
131
+ return finalizeSingleAttempt({
132
+ result, progress, exitCode: 1, interruptedByControl, allControlEvents: controlRuntime.allControlEvents, options, shared, startTime,
133
+ });
134
+ }
127
135
  const exitCode = await new Promise<number>((resolve) => {
128
136
  const spawnSpec = getPiSpawnCommand(args);
129
137
  const proc = spawn(spawnSpec.command, spawnSpec.args, {
@@ -418,7 +426,7 @@ export async function runSingleAttempt(
418
426
  attemptWatchdog.clear();
419
427
  void jsonlWriter.close().catch(() => undefined);
420
428
  cleanupTempDir(tempDir);
421
- if (!result.error) result.error = error instanceof Error ? error.message : String(error);
429
+ if (!result.error) result.error = formatPiSpawnError(error, spawnSpec, runCwd);
422
430
  finish(1);
423
431
  });
424
432
 
@@ -42,9 +42,36 @@ export interface PiSpawnDeps {
42
42
  resolvePackageJson?: () => string;
43
43
  resolvePackageEntry?: () => string;
44
44
  piPackageRoot?: string;
45
+ statSync?: (filePath: string) => fs.Stats;
45
46
  }
46
47
 
47
- interface PiSpawnCommand {
48
+ export type PiSpawnCwdValidationResult = { ok: true } | { ok: false; error: string };
49
+
50
+ export function validatePiSpawnCwd(cwd: string, deps: PiSpawnDeps = {}): PiSpawnCwdValidationResult {
51
+ const statSync = deps.statSync ?? fs.statSync;
52
+ try {
53
+ const cwdStats = statSync(cwd);
54
+ if (!cwdStats.isDirectory()) return { ok: false, error: `cwd is not a directory: ${cwd}` };
55
+ return { ok: true };
56
+ } catch (error) {
57
+ const fsError = error as NodeJS.ErrnoException;
58
+ if (fsError.code === "ENOENT") return { ok: false, error: `cwd does not exist: ${cwd}` };
59
+ if (fsError.code === "ENOTDIR") return { ok: false, error: `cwd path contains a non-directory component: ${cwd}` };
60
+ const details = fsError.message ? ` (${fsError.message})` : "";
61
+ return { ok: false, error: `cwd is not accessible: ${cwd}${details}` };
62
+ }
63
+ }
64
+
65
+ export function formatPiSpawnError(error: Error, spawnSpec: PiSpawnCommand, cwd: string): string {
66
+ const spawnError = error as NodeJS.ErrnoException;
67
+ const message = error.message || String(error);
68
+ if (spawnError.code === "ENOENT") {
69
+ return `failed to spawn subagent runtime '${spawnSpec.command}' from cwd '${cwd}': runtime executable was not found or could not be launched (${message})`;
70
+ }
71
+ return `failed to spawn subagent runtime '${spawnSpec.command}' from cwd '${cwd}': ${message}`;
72
+ }
73
+
74
+ export interface PiSpawnCommand {
48
75
  command: string;
49
76
  args: string[];
50
77
  }
@@ -5,7 +5,7 @@ import { formatDuration, formatModelThinking, shortenPath } from "../shared/form
5
5
  import { formatNestedAggregate } from "../runs/shared/nested-render.ts";
6
6
  import { formatAgentRunningLabel } from "../shared/status-format.ts";
7
7
  import { buildLiveStatusLine, formatCurrentToolLine, formatTokenStat, formatToolUseStat, statJoin } from "./render-status-progress.ts";
8
- import { runningGlyph, runningSeed, truncLine, type Theme } from "./render-layout.ts";
8
+ import { runningGlyph, runningPulseGlyph, truncLine, type Theme } from "./render-layout.ts";
9
9
 
10
10
  export function formatWidgetAgents(agents: string[]): string {
11
11
  const distinct = [...new Set(agents)];
@@ -40,47 +40,9 @@ export function widgetActivity(job: AsyncJobState): string {
40
40
  return "Done";
41
41
  }
42
42
 
43
- export function widgetStepRunningSeed(step: NonNullable<AsyncJobState["steps"]>[number], fallbackIndex?: number): number | undefined {
44
- return runningSeed(
45
- fallbackIndex,
46
- step.index,
47
- step.toolCount,
48
- step.turnCount,
49
- step.tokens?.total,
50
- step.lastActivityAt,
51
- step.currentToolStartedAt,
52
- step.durationMs,
53
- );
54
- }
55
-
56
- export function widgetStepsRunningSeed(steps: Array<NonNullable<AsyncJobState["steps"]>[number]> | undefined): number | undefined {
57
- let seed: number | undefined;
58
- for (const [index, step] of (steps ?? []).entries()) seed = runningSeed(seed, widgetStepRunningSeed(step, index));
59
- return seed;
60
- }
61
-
62
- export function widgetJobRunningSeed(job: AsyncJobState): number | undefined {
63
- return runningSeed(
64
- job.updatedAt,
65
- job.lastActivityAt,
66
- job.toolCount,
67
- job.turnCount,
68
- job.totalTokens?.total,
69
- job.currentStep,
70
- job.runningSteps,
71
- job.completedSteps,
72
- widgetStepsRunningSeed(job.steps),
73
- );
74
- }
75
-
76
- export function widgetJobsRunningSeed(jobs: AsyncJobState[]): number | undefined {
77
- let seed: number | undefined;
78
- for (const job of jobs) seed = runningSeed(seed, widgetJobRunningSeed(job));
79
- return seed;
80
- }
81
43
 
82
- export function widgetStatusGlyph(job: AsyncJobState, theme: Theme, now?: number): string {
83
- if (job.status === "running") return theme.fg("accent", runningGlyph(widgetJobRunningSeed(job), now));
44
+ export function widgetStatusGlyph(job: AsyncJobState, theme: Theme, pulseFrame?: number): string {
45
+ if (job.status === "running") return theme.fg("accent", runningPulseGlyph(pulseFrame));
84
46
  if (job.status === "queued") return theme.fg("muted", "◦");
85
47
  if (job.status === "complete") return theme.fg("success", "✓");
86
48
  if (job.status === "paused") return theme.fg("warning", "■");
@@ -95,6 +57,14 @@ export function widgetStepGlyph(status: AsyncJobStep["status"], theme: Theme, se
95
57
  return theme.fg("muted", "◦");
96
58
  }
97
59
 
60
+ export function widgetStepPulseGlyph(status: AsyncJobStep["status"], theme: Theme, pulseFrame?: number): string {
61
+ if (status === "running") return theme.fg("accent", runningPulseGlyph(pulseFrame));
62
+ if (status === "complete" || status === "completed") return theme.fg("success", "✓");
63
+ if (status === "failed") return theme.fg("error", "✗");
64
+ if (status === "paused") return theme.fg("warning", "■");
65
+ return theme.fg("muted", "◦");
66
+ }
67
+
98
68
  export function widgetStepStatus(status: AsyncJobStep["status"], theme: Theme): string {
99
69
  if (status === "running") return theme.fg("accent", "running");
100
70
  if (status === "complete" || status === "completed") return theme.fg("success", "complete");
@@ -186,17 +156,14 @@ export function nestedRunName(run: NestedRunSummary): string {
186
156
  return run.id;
187
157
  }
188
158
 
189
- export function nestedStatusGlyph(state: NestedRunSummary["state"] | NestedStepSummary["status"], theme: Theme, seed?: number, now?: number): string {
190
- if (state === "running") return theme.fg("accent", runningGlyph(seed, now));
159
+ export function nestedStatusGlyph(state: NestedRunSummary["state"] | NestedStepSummary["status"], theme: Theme, pulseFrame?: number): string {
160
+ if (state === "running") return theme.fg("accent", runningPulseGlyph(pulseFrame));
191
161
  if (state === "complete" || state === "completed") return theme.fg("success", "✓");
192
162
  if (state === "failed") return theme.fg("error", "✗");
193
163
  if (state === "paused") return theme.fg("warning", "■");
194
164
  return theme.fg("muted", "◦");
195
165
  }
196
166
 
197
- export function nestedRunSeed(run: NestedRunSummary): number | undefined {
198
- return runningSeed(run.lastUpdate, run.lastActivityAt, run.currentStep, run.toolCount, run.turnCount, run.totalTokens?.total, run.currentToolStartedAt);
199
- }
200
167
 
201
168
  export function nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount">, state: NestedRunSummary["state"] | NestedStepSummary["status"], snapshotNow?: number): string {
202
169
  const facts: string[] = [];
@@ -240,7 +207,7 @@ export function formatNestedWidgetLines(children: NestedRunSummary[] | undefined
240
207
  }
241
208
  const activity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);
242
209
  const error = child.error ? ` · ${child.error}` : "";
243
- lines.push(theme.fg("dim", `${prefix}↳ ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child), now)} ${nestedRunName(child)} · ${child.state} · ${activity}${error}`));
210
+ lines.push(theme.fg("dim", `${prefix}↳ ${nestedStatusGlyph(child.state, theme, now)} ${nestedRunName(child)} · ${child.state} · ${activity}${error}`));
244
211
  if (depth === maxDepth) {
245
212
  const aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);
246
213
  if (aggregate && lines.length < lineBudget) lines.push(theme.fg("dim", `${prefix} ↳ ${aggregate}`));
@@ -248,7 +215,7 @@ export function formatNestedWidgetLines(children: NestedRunSummary[] | undefined
248
215
  }
249
216
  for (const step of child.steps ?? []) {
250
217
  if (lines.length >= lineBudget) return;
251
- lines.push(theme.fg("dim", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme, undefined, now)} ${step.agent} · ${step.status} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
218
+ lines.push(theme.fg("dim", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme, now)} ${step.agent} · ${step.status} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
252
219
  append(step.children, depth + 1, `${prefix} `);
253
220
  }
254
221
  append(child.children, depth + 1, `${prefix} `);
@@ -129,6 +129,15 @@ export function pulseGlyph(frame?: number): string {
129
129
  return PULSE_FRAMES[index % PULSE_FRAMES.length]!;
130
130
  }
131
131
 
132
+ /**
133
+ * Running async/background subagent pulse. It reuses the exact foreground pulse
134
+ * glyph frames while accepting an explicit frame advanced by the widget only on
135
+ * real async progress/status updates, matching foreground result semantics.
136
+ */
137
+ export function runningPulseGlyph(frame?: number): string {
138
+ return pulseGlyph(frame);
139
+ }
140
+
132
141
  export function progressRunningSeed(progress: ProgressSeedSource | undefined): number | undefined {
133
142
  if (!progress) return undefined;
134
143
  return runningSeed(