@bastani/atomic 0.9.5-alpha.9 → 0.9.5

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 (377) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +2 -2
  3. package/dist/builtin/cursor/CHANGELOG.md +12 -0
  4. package/dist/builtin/cursor/package.json +3 -3
  5. package/dist/builtin/cursor/src/model-mapper.ts +6 -3
  6. package/dist/builtin/intercom/CHANGELOG.md +37 -0
  7. package/dist/builtin/intercom/README.md +4 -2
  8. package/dist/builtin/intercom/broker/broker.ts +6 -114
  9. package/dist/builtin/intercom/broker/client.ts +29 -54
  10. package/dist/builtin/intercom/broker/delivered-message-cache.ts +44 -0
  11. package/dist/builtin/intercom/broker/pending-send-registry.ts +142 -0
  12. package/dist/builtin/intercom/broker/send-handler.ts +106 -0
  13. package/dist/builtin/intercom/broker/send-signature.ts +37 -0
  14. package/dist/builtin/intercom/foreground-detach-handoff.ts +136 -0
  15. package/dist/builtin/intercom/index-heavy.ts +49 -53
  16. package/dist/builtin/intercom/index.ts +278 -262
  17. package/dist/builtin/intercom/lazy-heavy-proxy.ts +114 -0
  18. package/dist/builtin/intercom/lazy-subagent-ack.ts +20 -0
  19. package/dist/builtin/intercom/lazy-tool-execution.ts +39 -0
  20. package/dist/builtin/intercom/lifecycle-lease.ts +51 -0
  21. package/dist/builtin/intercom/lifecycle.ts +37 -20
  22. package/dist/builtin/intercom/package.json +3 -6
  23. package/dist/builtin/intercom/reply-routing.ts +17 -0
  24. package/dist/builtin/intercom/subagent-relay.ts +58 -12
  25. package/dist/builtin/intercom/types.ts +3 -3
  26. package/dist/builtin/mcp/CHANGELOG.md +27 -0
  27. package/dist/builtin/mcp/OAUTH.md +1 -0
  28. package/dist/builtin/mcp/README.md +4 -0
  29. package/dist/builtin/mcp/apps-cancellation.ts +32 -0
  30. package/dist/builtin/mcp/call-tool-result.ts +9 -0
  31. package/dist/builtin/mcp/caller-wait.ts +50 -0
  32. package/dist/builtin/mcp/command-registration.ts +82 -0
  33. package/dist/builtin/mcp/direct-tool-executor.ts +279 -0
  34. package/dist/builtin/mcp/direct-tools.ts +6 -239
  35. package/dist/builtin/mcp/host-html-template.ts +4 -2
  36. package/dist/builtin/mcp/index.ts +258 -250
  37. package/dist/builtin/mcp/init.ts +96 -87
  38. package/dist/builtin/mcp/mcp-auth-flow.ts +247 -237
  39. package/dist/builtin/mcp/mcp-callback-server.ts +89 -68
  40. package/dist/builtin/mcp/mcp-oauth-provider.ts +23 -1
  41. package/dist/builtin/mcp/metadata-hydration.ts +21 -6
  42. package/dist/builtin/mcp/package.json +3 -3
  43. package/dist/builtin/mcp/proxy-auth.ts +4 -4
  44. package/dist/builtin/mcp/proxy-call.ts +111 -40
  45. package/dist/builtin/mcp/proxy-connect.ts +35 -15
  46. package/dist/builtin/mcp/proxy-info-modes.ts +49 -19
  47. package/dist/builtin/mcp/proxy-modes.ts +3 -3
  48. package/dist/builtin/mcp/session-cleanup-barrier.ts +43 -0
  49. package/dist/builtin/mcp/state-lease.ts +12 -0
  50. package/dist/builtin/mcp/ui-server.ts +8 -8
  51. package/dist/builtin/mcp/ui-session.ts +9 -18
  52. package/dist/builtin/subagents/CHANGELOG.md +66 -0
  53. package/dist/builtin/subagents/README.md +30 -29
  54. package/dist/builtin/subagents/agents/code-simplifier.md +2 -2
  55. package/dist/builtin/subagents/agents/codebase-analyzer.md +2 -2
  56. package/dist/builtin/subagents/agents/codebase-locator.md +2 -2
  57. package/dist/builtin/subagents/agents/codebase-online-researcher.md +2 -2
  58. package/dist/builtin/subagents/agents/codebase-pattern-finder.md +2 -2
  59. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +2 -2
  60. package/dist/builtin/subagents/agents/codebase-research-locator.md +2 -2
  61. package/dist/builtin/subagents/agents/debugger.md +2 -2
  62. package/dist/builtin/subagents/agents/worker.md +2 -2
  63. package/dist/builtin/subagents/package.json +5 -5
  64. package/dist/builtin/subagents/prompts/review-loop.md +1 -1
  65. package/dist/builtin/subagents/skills/subagent/SKILL.md +37 -36
  66. package/dist/builtin/subagents/src/extension/api-lifecycle.ts +64 -0
  67. package/dist/builtin/subagents/src/extension/fanout-child.ts +44 -51
  68. package/dist/builtin/subagents/src/extension/index.ts +287 -307
  69. package/dist/builtin/subagents/src/extension/prompt-guidance.ts +7 -10
  70. package/dist/builtin/subagents/src/extension/schemas.ts +22 -5
  71. package/dist/builtin/subagents/src/extension/tool-description.ts +29 -0
  72. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +13 -2
  73. package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
  74. package/dist/builtin/subagents/src/runs/background/async-event-journal.ts +106 -0
  75. package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +3 -2
  76. package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +13 -2
  77. package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +3 -0
  78. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +1 -1
  79. package/dist/builtin/subagents/src/runs/background/completion-claims.ts +189 -0
  80. package/dist/builtin/subagents/src/runs/background/completion-dedupe.ts +44 -7
  81. package/dist/builtin/subagents/src/runs/background/completion-notification.ts +34 -0
  82. package/dist/builtin/subagents/src/runs/background/notify.ts +72 -22
  83. package/dist/builtin/subagents/src/runs/background/result-delivery-processor.ts +232 -0
  84. package/dist/builtin/subagents/src/runs/background/result-file-claims.ts +151 -0
  85. package/dist/builtin/subagents/src/runs/background/result-quarantine.ts +72 -0
  86. package/dist/builtin/subagents/src/runs/background/result-retry-scheduler.ts +48 -0
  87. package/dist/builtin/subagents/src/runs/background/result-status.ts +81 -0
  88. package/dist/builtin/subagents/src/runs/background/result-watcher-data.ts +59 -0
  89. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +172 -238
  90. package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +3 -2
  91. package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +73 -14
  92. package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +17 -13
  93. package/dist/builtin/subagents/src/runs/background/top-level-async.ts +1 -2
  94. package/dist/builtin/subagents/src/runs/foreground/chain-execution-dynamic-step.ts +1 -0
  95. package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +1 -0
  96. package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-step.ts +1 -0
  97. package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +7 -9
  98. package/dist/builtin/subagents/src/runs/foreground/chain-execution-types.ts +3 -13
  99. package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +2 -15
  100. package/dist/builtin/subagents/src/runs/foreground/execution-attempt-finalize.ts +2 -2
  101. package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +53 -44
  102. package/dist/builtin/subagents/src/runs/foreground/execution-detach-reservations.ts +48 -0
  103. package/dist/builtin/subagents/src/runs/foreground/execution-detach-route.ts +17 -0
  104. package/dist/builtin/subagents/src/runs/foreground/execution-run-sync.ts +39 -26
  105. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +3 -1
  106. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-chain.ts +3 -51
  107. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +6 -11
  108. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +11 -2
  109. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +2 -0
  110. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +5 -112
  111. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +2 -0
  112. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +68 -118
  113. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +79 -7
  114. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +2 -2
  115. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +3 -0
  116. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +2 -2
  117. package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +27 -11
  118. package/dist/builtin/subagents/src/shared/artifacts.ts +22 -11
  119. package/dist/builtin/subagents/src/shared/event-jsonl-writer.ts +294 -0
  120. package/dist/builtin/subagents/src/shared/exclusive-file-publication.ts +44 -0
  121. package/dist/builtin/subagents/src/shared/jsonl-writer.ts +1 -0
  122. package/dist/builtin/subagents/src/shared/model-info.ts +2 -2
  123. package/dist/builtin/subagents/src/shared/settings.ts +20 -10
  124. package/dist/builtin/subagents/src/shared/types-async.ts +3 -1
  125. package/dist/builtin/subagents/src/shared/types-config.ts +2 -0
  126. package/dist/builtin/subagents/src/shared/types-runtime.ts +3 -2
  127. package/dist/builtin/subagents/src/slash/slash-commands.ts +9 -12
  128. package/dist/builtin/subagents/src/slash/slash-live-state.ts +63 -32
  129. package/dist/builtin/subagents/src/tui/render-result.ts +7 -0
  130. package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -0
  131. package/dist/builtin/subagents/src/tui/render-widget.ts +128 -89
  132. package/dist/builtin/web-access/CHANGELOG.md +28 -0
  133. package/dist/builtin/web-access/README.md +4 -0
  134. package/dist/builtin/web-access/content-tools.ts +8 -3
  135. package/dist/builtin/web-access/index-heavy.ts +1 -1
  136. package/dist/builtin/web-access/index.ts +190 -46
  137. package/dist/builtin/web-access/lifecycle-lease.ts +38 -0
  138. package/dist/builtin/web-access/package.json +2 -2
  139. package/dist/builtin/web-access/result-renderers.ts +1 -1
  140. package/dist/builtin/web-access/summary-review.ts +1 -1
  141. package/dist/builtin/web-access/web-search-activity.ts +1 -1
  142. package/dist/builtin/web-access/web-search-return.ts +7 -0
  143. package/dist/builtin/web-access/web-search-summary.ts +1 -1
  144. package/dist/builtin/web-access/web-search-tool.ts +4 -2
  145. package/dist/builtin/workflows/CHANGELOG.md +68 -0
  146. package/dist/builtin/workflows/README.md +4 -4
  147. package/dist/builtin/workflows/builtin/deep-research-codebase-utils.ts +27 -4
  148. package/dist/builtin/workflows/builtin/goal-runner.ts +47 -26
  149. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +10 -0
  150. package/dist/builtin/workflows/builtin/ralph-core.ts +9 -9
  151. package/dist/builtin/workflows/builtin/ralph-models.ts +71 -36
  152. package/dist/builtin/workflows/builtin/ralph-runner.ts +20 -31
  153. package/dist/builtin/workflows/package.json +2 -2
  154. package/dist/builtin/workflows/src/durable/stage-primitive.ts +2 -1
  155. package/dist/builtin/workflows/src/engine/primitives/task.ts +2 -1
  156. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +17 -22
  157. package/dist/builtin/workflows/src/extension/workflow-schema.ts +15 -2
  158. package/dist/builtin/workflows/src/runs/foreground/executor-hil.ts +1 -2
  159. package/dist/builtin/workflows/src/runs/shared/model-fallback-candidates.ts +1 -1
  160. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +2 -2
  161. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +2 -2
  162. package/dist/builtin/workflows/src/shared/resume-continuation.ts +2 -0
  163. package/dist/builtin/workflows/src/tui/stage-chat-view-archive-history.ts +1 -23
  164. package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +54 -9
  165. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +5 -2
  166. package/dist/cli/args.d.ts.map +1 -1
  167. package/dist/cli/args.js +2 -2
  168. package/dist/cli/args.js.map +1 -1
  169. package/dist/core/agent-session-events.d.ts.map +1 -1
  170. package/dist/core/agent-session-events.js +2 -1
  171. package/dist/core/agent-session-events.js.map +1 -1
  172. package/dist/core/agent-session-message-queue.d.ts.map +1 -1
  173. package/dist/core/agent-session-message-queue.js +1 -1
  174. package/dist/core/agent-session-message-queue.js.map +1 -1
  175. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  176. package/dist/core/agent-session-prompt.js +1 -1
  177. package/dist/core/agent-session-prompt.js.map +1 -1
  178. package/dist/core/agent-session-retry.d.ts.map +1 -1
  179. package/dist/core/agent-session-retry.js +1 -1
  180. package/dist/core/agent-session-retry.js.map +1 -1
  181. package/dist/core/agent-session-services.d.ts.map +1 -1
  182. package/dist/core/agent-session-services.js +3 -2
  183. package/dist/core/agent-session-services.js.map +1 -1
  184. package/dist/core/agent-session-types.d.ts.map +1 -1
  185. package/dist/core/agent-session-types.js +1 -1
  186. package/dist/core/agent-session-types.js.map +1 -1
  187. package/dist/core/anthropic-thinking-guard.d.ts +5 -5
  188. package/dist/core/anthropic-thinking-guard.d.ts.map +1 -1
  189. package/dist/core/anthropic-thinking-guard.js +42 -32
  190. package/dist/core/anthropic-thinking-guard.js.map +1 -1
  191. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  192. package/dist/core/atomic-guide-command.js +15 -15
  193. package/dist/core/atomic-guide-command.js.map +1 -1
  194. package/dist/core/auth-storage.d.ts.map +1 -1
  195. package/dist/core/auth-storage.js +14 -9
  196. package/dist/core/auth-storage.js.map +1 -1
  197. package/dist/core/compaction/compaction.d.ts +2 -0
  198. package/dist/core/compaction/compaction.d.ts.map +1 -1
  199. package/dist/core/compaction/compaction.js +79 -46
  200. package/dist/core/compaction/compaction.js.map +1 -1
  201. package/dist/core/compaction/context-assistant-turns.d.ts +42 -0
  202. package/dist/core/compaction/context-assistant-turns.d.ts.map +1 -0
  203. package/dist/core/compaction/context-assistant-turns.js +87 -0
  204. package/dist/core/compaction/context-assistant-turns.js.map +1 -0
  205. package/dist/core/compaction/context-compaction-critical.d.ts +1 -1
  206. package/dist/core/compaction/context-compaction-critical.d.ts.map +1 -1
  207. package/dist/core/compaction/context-compaction-critical.js +2 -2
  208. package/dist/core/compaction/context-compaction-critical.js.map +1 -1
  209. package/dist/core/compaction/context-compaction-eviction-alternates.d.ts +18 -0
  210. package/dist/core/compaction/context-compaction-eviction-alternates.d.ts.map +1 -0
  211. package/dist/core/compaction/context-compaction-eviction-alternates.js +186 -0
  212. package/dist/core/compaction/context-compaction-eviction-alternates.js.map +1 -0
  213. package/dist/core/compaction/context-compaction-eviction.d.ts +10 -2
  214. package/dist/core/compaction/context-compaction-eviction.d.ts.map +1 -1
  215. package/dist/core/compaction/context-compaction-eviction.js +196 -146
  216. package/dist/core/compaction/context-compaction-eviction.js.map +1 -1
  217. package/dist/core/compaction/context-compaction-prompt.d.ts.map +1 -1
  218. package/dist/core/compaction/context-compaction-prompt.js +1 -1
  219. package/dist/core/compaction/context-compaction-prompt.js.map +1 -1
  220. package/dist/core/compaction/context-compaction-types.d.ts +2 -0
  221. package/dist/core/compaction/context-compaction-types.d.ts.map +1 -1
  222. package/dist/core/compaction/context-compaction-types.js.map +1 -1
  223. package/dist/core/compaction/context-deletion-application.d.ts +5 -4
  224. package/dist/core/compaction/context-deletion-application.d.ts.map +1 -1
  225. package/dist/core/compaction/context-deletion-application.js +17 -15
  226. package/dist/core/compaction/context-deletion-application.js.map +1 -1
  227. package/dist/core/compaction/context-deletion-store.d.ts.map +1 -1
  228. package/dist/core/compaction/context-deletion-store.js +1 -1
  229. package/dist/core/compaction/context-deletion-store.js.map +1 -1
  230. package/dist/core/compaction/context-deletion-targets.d.ts +2 -1
  231. package/dist/core/compaction/context-deletion-targets.d.ts.map +1 -1
  232. package/dist/core/compaction/context-deletion-targets.js +36 -31
  233. package/dist/core/compaction/context-deletion-targets.js.map +1 -1
  234. package/dist/core/compaction/context-deletion-tool-helpers.d.ts.map +1 -1
  235. package/dist/core/compaction/context-deletion-tool-helpers.js +116 -43
  236. package/dist/core/compaction/context-deletion-tool-helpers.js.map +1 -1
  237. package/dist/core/compaction/context-transcript-analysis.d.ts +1 -2
  238. package/dist/core/compaction/context-transcript-analysis.d.ts.map +1 -1
  239. package/dist/core/compaction/context-transcript-analysis.js +33 -37
  240. package/dist/core/compaction/context-transcript-analysis.js.map +1 -1
  241. package/dist/core/copilot-model-synthesis.d.ts.map +1 -1
  242. package/dist/core/copilot-model-synthesis.js +3 -1
  243. package/dist/core/copilot-model-synthesis.js.map +1 -1
  244. package/dist/core/extensions/provider-types.d.ts +2 -7
  245. package/dist/core/extensions/provider-types.d.ts.map +1 -1
  246. package/dist/core/extensions/provider-types.js.map +1 -1
  247. package/dist/core/messages.d.ts +9 -0
  248. package/dist/core/messages.d.ts.map +1 -1
  249. package/dist/core/messages.js +100 -18
  250. package/dist/core/messages.js.map +1 -1
  251. package/dist/core/model-registry-builtins.d.ts.map +1 -1
  252. package/dist/core/model-registry-builtins.js +5 -0
  253. package/dist/core/model-registry-builtins.js.map +1 -1
  254. package/dist/core/model-registry-custom-loader.d.ts.map +1 -1
  255. package/dist/core/model-registry-custom-loader.js +49 -8
  256. package/dist/core/model-registry-custom-loader.js.map +1 -1
  257. package/dist/core/model-registry-dynamic.d.ts.map +1 -1
  258. package/dist/core/model-registry-dynamic.js +11 -5
  259. package/dist/core/model-registry-dynamic.js.map +1 -1
  260. package/dist/core/model-registry-loader.d.ts.map +1 -1
  261. package/dist/core/model-registry-loader.js +8 -0
  262. package/dist/core/model-registry-loader.js.map +1 -1
  263. package/dist/core/model-registry-schemas.d.ts +93 -8
  264. package/dist/core/model-registry-schemas.d.ts.map +1 -1
  265. package/dist/core/model-registry-schemas.js +24 -12
  266. package/dist/core/model-registry-schemas.js.map +1 -1
  267. package/dist/core/model-registry-types.d.ts +7 -6
  268. package/dist/core/model-registry-types.d.ts.map +1 -1
  269. package/dist/core/model-registry-types.js.map +1 -1
  270. package/dist/core/model-registry.d.ts +5 -0
  271. package/dist/core/model-registry.d.ts.map +1 -1
  272. package/dist/core/model-registry.js +23 -0
  273. package/dist/core/model-registry.js.map +1 -1
  274. package/dist/core/model-resolver-cli.d.ts.map +1 -1
  275. package/dist/core/model-resolver-cli.js +33 -5
  276. package/dist/core/model-resolver-cli.js.map +1 -1
  277. package/dist/core/model-resolver-initial.d.ts +6 -1
  278. package/dist/core/model-resolver-initial.d.ts.map +1 -1
  279. package/dist/core/model-resolver-initial.js +12 -7
  280. package/dist/core/model-resolver-initial.js.map +1 -1
  281. package/dist/core/model-resolver.d.ts +1 -1
  282. package/dist/core/model-resolver.d.ts.map +1 -1
  283. package/dist/core/model-resolver.js +1 -1
  284. package/dist/core/model-resolver.js.map +1 -1
  285. package/dist/core/resource-loader-context-files.d.ts +1 -0
  286. package/dist/core/resource-loader-context-files.d.ts.map +1 -1
  287. package/dist/core/resource-loader-context-files.js +13 -10
  288. package/dist/core/resource-loader-context-files.js.map +1 -1
  289. package/dist/core/sdk.d.ts.map +1 -1
  290. package/dist/core/sdk.js +2 -2
  291. package/dist/core/sdk.js.map +1 -1
  292. package/dist/core/session-entry-normalization.d.ts +7 -0
  293. package/dist/core/session-entry-normalization.d.ts.map +1 -0
  294. package/dist/core/session-entry-normalization.js +14 -0
  295. package/dist/core/session-entry-normalization.js.map +1 -0
  296. package/dist/core/session-manager-core.d.ts.map +1 -1
  297. package/dist/core/session-manager-core.js +1 -0
  298. package/dist/core/session-manager-core.js.map +1 -1
  299. package/dist/core/session-manager-history.d.ts +2 -2
  300. package/dist/core/session-manager-history.d.ts.map +1 -1
  301. package/dist/core/session-manager-history.js +127 -108
  302. package/dist/core/session-manager-history.js.map +1 -1
  303. package/dist/core/settings-manager-basic-accessors.d.ts +2 -2
  304. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  305. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  306. package/dist/core/settings-types.d.ts +1 -1
  307. package/dist/core/settings-types.d.ts.map +1 -1
  308. package/dist/core/settings-types.js.map +1 -1
  309. package/dist/core/tools/bash.d.ts.map +1 -1
  310. package/dist/core/tools/bash.js +11 -8
  311. package/dist/core/tools/bash.js.map +1 -1
  312. package/dist/main-session.d.ts.map +1 -1
  313. package/dist/main-session.js +1 -0
  314. package/dist/main-session.js.map +1 -1
  315. package/dist/modes/interactive/components/settings-selector-options.d.ts.map +1 -1
  316. package/dist/modes/interactive/components/settings-selector-options.js +3 -1
  317. package/dist/modes/interactive/components/settings-selector-options.js.map +1 -1
  318. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  319. package/dist/modes/interactive/components/thinking-selector.js +2 -1
  320. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  321. package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
  322. package/dist/modes/interactive/interactive-agent-events.js +2 -1
  323. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  324. package/dist/modes/interactive/interactive-child-ordering.d.ts +7 -0
  325. package/dist/modes/interactive/interactive-child-ordering.d.ts.map +1 -0
  326. package/dist/modes/interactive/interactive-child-ordering.js +26 -0
  327. package/dist/modes/interactive/interactive-child-ordering.js.map +1 -0
  328. package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
  329. package/dist/modes/interactive/interactive-deferred-startup.js +2 -2
  330. package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
  331. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
  332. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  333. package/dist/modes/interactive/interactive-mode-deps.js +1 -1
  334. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  335. package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
  336. package/dist/modes/interactive/interactive-model-routing.js +8 -3
  337. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  338. package/dist/modes/interactive/theme/theme-class.d.ts +1 -1
  339. package/dist/modes/interactive/theme/theme-class.d.ts.map +1 -1
  340. package/dist/modes/interactive/theme/theme-class.js +2 -0
  341. package/dist/modes/interactive/theme/theme-class.js.map +1 -1
  342. package/dist/utils/clipboard-image.d.ts.map +1 -1
  343. package/dist/utils/clipboard-image.js +3 -0
  344. package/dist/utils/clipboard-image.js.map +1 -1
  345. package/docs/changelog.mdx +19 -0
  346. package/docs/compaction.md +55 -48
  347. package/docs/custom-provider.md +18 -5
  348. package/docs/extensions.md +7 -3
  349. package/docs/models.md +69 -10
  350. package/docs/providers.md +1 -1
  351. package/docs/quickstart.md +11 -7
  352. package/docs/rpc.md +2 -2
  353. package/docs/sdk.md +1 -1
  354. package/docs/settings.md +3 -3
  355. package/docs/subagents.md +36 -5
  356. package/docs/tools.md +1 -1
  357. package/docs/usage.md +4 -2
  358. package/docs/workflows.md +26 -43
  359. package/examples/extensions/preset.ts +2 -2
  360. package/examples/extensions/subagent/README.md +2 -2
  361. package/examples/extensions/subagent/index.ts +1 -2
  362. package/examples/extensions/subagent/schemas.ts +4 -1
  363. package/examples/sdk/12-full-control.ts +1 -1
  364. package/npm-shrinkwrap.json +48 -51
  365. package/package.json +7 -7
  366. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-behavior.ts +0 -75
  367. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-component.ts +0 -202
  368. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-edit.ts +0 -97
  369. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-editor.ts +0 -160
  370. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-frame.ts +0 -72
  371. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-render-modes.ts +0 -161
  372. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-render-selectors.ts +0 -203
  373. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-selectors.ts +0 -234
  374. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-state.ts +0 -103
  375. package/dist/builtin/subagents/src/runs/foreground/chain-clarify-types.ts +0 -29
  376. package/dist/builtin/subagents/src/runs/foreground/chain-clarify.ts +0 -9
  377. package/dist/builtin/subagents/src/runs/foreground/chain-execution-clarify.ts +0 -117
@@ -1,91 +1,47 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { isSafeFsWatchPathError, watchWithErrorHandler } from "@bastani/atomic";
4
- import { buildCompletionKey, markSeenWithTtl } from "./completion-dedupe.ts";
5
- import { createFileCoalescer } from "../../shared/file-coalescer.ts";
6
- import {
7
- SUBAGENT_ASYNC_COMPLETE_EVENT,
8
- type IntercomEventBus,
9
- type NestedRunSummary,
10
- type SubagentResultIntercomChild,
11
- type SubagentState,
12
- } from "../../shared/types.ts";
13
- import {
14
- attachNestedChildrenToResultChildren,
15
- buildSubagentResultIntercomPayload,
16
- compactNestedResultChildren,
17
- deliverSubagentResultIntercomEvent,
18
- resolveSubagentResultStatus,
19
- } from "../../intercom/result-intercom.ts";
20
- import { projectNestedRegistryForRoot, sanitizeSummary } from "../shared/nested-events.ts";
4
+ import { createFileCoalescer } from "../../shared/file-coalescer.js";
5
+ import type { IntercomEventBus, SubagentState } from "../../shared/types.js";
6
+ import { processResultEntry, type ResultProcessorFs } from "./result-delivery-processor.js";
7
+ import { claimIdFromScheduleKey, claimScheduleKey, listResultClaims } from "./result-file-claims.js";
8
+ import { createRetryScheduler } from "./result-retry-scheduler.js";
9
+ import type { ResultFileData } from "./result-watcher-data.js";
21
10
 
22
11
  const WATCHER_RESTART_DELAY_MS = 3000;
23
12
  const POLL_INTERVAL_MS = 3000;
13
+ const DIRECTORY_RESCAN_DELAY_MS = 50;
14
+ const STATUS_RECHECK_BASE_MS = 250;
15
+ const STATUS_RECHECK_MAX_MS = 30_000;
16
+ const DELIVERY_RETRY_BASE_MS = 1000;
17
+ const DELIVERY_RETRY_MAX_MS = 30_000;
24
18
 
25
- type ResultWatcherFs = Pick<typeof fs, "existsSync" | "readFileSync" | "unlinkSync" | "readdirSync" | "mkdirSync" | "watch"> & {
26
- realpathSync?: typeof fs.realpathSync;
27
- };
28
-
19
+ type ResultWatcherFs = ResultProcessorFs & Pick<typeof fs, "existsSync" | "readdirSync" | "mkdirSync" | "watch">;
29
20
  type ResultWatcherTimers = {
30
21
  setTimeout: typeof setTimeout;
31
22
  clearTimeout: typeof clearTimeout;
32
23
  setInterval: typeof setInterval;
33
24
  clearInterval: typeof clearInterval;
34
25
  };
35
-
36
26
  type ResultWatcherSafeWatch = typeof watchWithErrorHandler;
37
27
 
38
28
  type ResultWatcherDeps = {
39
29
  fs?: ResultWatcherFs;
40
30
  timers?: ResultWatcherTimers;
41
31
  safeWatch?: ResultWatcherSafeWatch;
32
+ statusRecheckBaseMs?: number;
33
+ statusRecheckMaxMs?: number;
34
+ deliveryRetryBaseMs?: number;
35
+ deliveryRetryMaxMs?: number;
36
+ maxNoProgressFailures?: number;
37
+ intercomTimeoutMs?: number | false;
38
+ allowedStatusRoots?: string[];
39
+ maxStatusBytes?: number;
42
40
  };
43
41
 
44
- type ResultFileChild = {
45
- agent?: string;
46
- output?: string;
47
- error?: string;
48
- success?: boolean;
49
- sessionFile?: string;
50
- artifactPaths?: { outputPath?: string };
51
- intercomTarget?: string;
52
- children?: unknown;
53
- };
54
-
55
- type ResultFileData = {
56
- id?: string;
57
- runId?: string;
58
- agent?: string;
59
- success?: boolean;
60
- state?: string;
61
- mode?: string;
62
- summary?: string;
63
- results?: ResultFileChild[];
64
- nestedChildren?: unknown;
65
- sessionId?: string;
66
- cwd?: string;
67
- sessionFile?: string;
68
- asyncDir?: string;
69
- intercomTarget?: string;
70
- };
71
-
72
- function sanitizeNestedResultChildren(value: unknown, resultPath: string, label: string): NestedRunSummary[] | undefined {
73
- if (value === undefined) return undefined;
74
- if (!Array.isArray(value)) {
75
- console.error(`Ignoring invalid nested children in subagent result file '${resultPath}' at ${label}: expected an array.`);
76
- return undefined;
77
- }
78
- const children = value.map((child) => sanitizeSummary(child)).filter((child): child is NestedRunSummary => Boolean(child));
79
- if (children.length !== value.length) {
80
- console.error(`Ignoring ${value.length - children.length} invalid nested child record(s) in subagent result file '${resultPath}' at ${label}.`);
81
- }
82
- return children.length ? children : undefined;
83
- }
84
-
85
42
  function getErrorCode(error: unknown): string | undefined {
86
43
  return typeof error === "object" && error !== null && "code" in error
87
- ? (error as NodeJS.ErrnoException).code
88
- : undefined;
44
+ ? (error as NodeJS.ErrnoException).code : undefined;
89
45
  }
90
46
 
91
47
  function isNotFoundError(error: unknown): boolean {
@@ -103,215 +59,193 @@ export function createResultWatcher(
103
59
  resultsDir: string,
104
60
  completionTtlMs: number,
105
61
  deps: ResultWatcherDeps = {},
106
- ): {
107
- startResultWatcher: () => void;
108
- primeExistingResults: () => void;
109
- stopResultWatcher: () => void;
110
- } {
62
+ ): { startResultWatcher: () => void; primeExistingResults: () => void; stopResultWatcher: () => void } {
111
63
  const fsApi = deps.fs ?? fs;
112
64
  const timers = deps.timers ?? { setTimeout, clearTimeout, setInterval, clearInterval };
113
65
  const safeWatch = deps.safeWatch ?? watchWithErrorHandler;
66
+ let directoryRescanTimer: ReturnType<typeof setTimeout> | null = null;
67
+ let stopped = false;
68
+ let processingEpoch = 0;
69
+ let watcherInstallation = 0;
70
+ let activeWatcherInstallation = 0;
71
+ const inFlight = new Set<string>();
72
+ const rerunAfterFlight = new Set<string>();
73
+ const statusRetries = createRetryScheduler(timers, deps.statusRecheckBaseMs ?? STATUS_RECHECK_BASE_MS, deps.statusRecheckMaxMs ?? STATUS_RECHECK_MAX_MS);
74
+ const deliveryRetries = createRetryScheduler(timers, deps.deliveryRetryBaseMs ?? DELIVERY_RETRY_BASE_MS, deps.deliveryRetryMaxMs ?? DELIVERY_RETRY_MAX_MS);
75
+ const isEpochActive = (epoch: number): boolean => !stopped && epoch === processingEpoch;
76
+ const entryExists = (entry: string): boolean => {
77
+ const claimId = claimIdFromScheduleKey(entry);
78
+ return claimId ? Boolean(listResultClaims(resultsDir, fsApi).some((claim) => claim.id === claimId))
79
+ : fsApi.existsSync(path.join(resultsDir, entry));
80
+ };
81
+ const ownsResult = (data: ResultFileData): boolean => !stopped
82
+ && (!data.sessionId || data.sessionId === state.currentSessionId)
83
+ && Boolean(data.sessionId || !data.cwd || (state.baseCwd && data.cwd === state.baseCwd));
84
+
85
+ const coalescerKey = (entry: string, epoch: number): string => `${epoch}\0${entry}`;
86
+ const parseCoalescerKey = (key: string): { epoch: number; entry: string } => {
87
+ const split = key.indexOf("\0");
88
+ return { epoch: Number(key.slice(0, split)), entry: key.slice(split + 1) };
89
+ };
90
+ const scheduleResultEntry = (entry: string, delay = 0, epoch = processingEpoch): boolean => {
91
+ if (!isEpochActive(epoch) || statusRetries.has(entry) || deliveryRetries.has(entry)) return false;
92
+ return state.resultFileCoalescer.schedule(coalescerKey(entry, epoch), delay);
93
+ };
94
+ const scheduleStatusRecheck = (entry: string, epoch: number) => {
95
+ if (!isEpochActive(epoch)) return;
96
+ statusRetries.schedule(entry, () => {
97
+ if (!isEpochActive(epoch) || !entryExists(entry)) { statusRetries.clear(entry); return; }
98
+ scheduleResultEntry(entry, 0, epoch);
99
+ });
100
+ };
101
+ const scheduleDeliveryRetry = (entry: string, epoch: number) => {
102
+ if (!isEpochActive(epoch)) return;
103
+ deliveryRetries.schedule(entry, () => {
104
+ if (!isEpochActive(epoch) || !entryExists(entry)) { deliveryRetries.clear(entry); return; }
105
+ scheduleResultEntry(entry, 0, epoch);
106
+ });
107
+ };
114
108
 
115
- const handleResult = async (file: string) => {
116
- const resultPath = path.join(resultsDir, file);
117
- if (!fsApi.existsSync(resultPath)) return;
109
+ const handleResult = async (entry: string, epoch: number) => {
110
+ if (!isEpochActive(epoch)) return;
111
+ const flightKey = coalescerKey(entry, epoch);
112
+ if (inFlight.has(flightKey)) { rerunAfterFlight.add(flightKey); return; }
113
+ inFlight.add(flightKey);
118
114
  try {
119
- const data = JSON.parse(fsApi.readFileSync(resultPath, "utf-8")) as ResultFileData;
120
- if (data.sessionId && data.sessionId !== state.currentSessionId) return;
121
- if (!data.sessionId && data.cwd && (!state.baseCwd || data.cwd !== state.baseCwd)) return;
122
-
123
- const runId = data.runId ?? data.id ?? file.replace(/\.json$/i, "");
124
- const hasExplicitNestedChildren = data.nestedChildren !== undefined;
125
- let nestedChildren = compactNestedResultChildren(sanitizeNestedResultChildren(data.nestedChildren, resultPath, "nestedChildren"));
126
- if (!nestedChildren?.length && !hasExplicitNestedChildren) {
127
- try {
128
- nestedChildren = compactNestedResultChildren(projectNestedRegistryForRoot(runId)?.children);
129
- } catch (error) {
130
- console.error(`Failed to enrich subagent result file '${resultPath}' with nested registry children; will retry later:`, error);
131
- return;
132
- }
133
- }
134
- const now = Date.now();
135
- const completionKey = buildCompletionKey(data, `result:${file}`);
136
- if (markSeenWithTtl(state.completionSeen, completionKey, now, completionTtlMs)) {
137
- fsApi.unlinkSync(resultPath);
138
- return;
139
- }
140
-
141
- const hasResultChildren = Array.isArray(data.results) && data.results.length > 0;
142
- const resultChildren = hasResultChildren
143
- ? data.results!
144
- : [{
145
- agent: data.agent,
146
- output: data.summary,
147
- success: data.success,
148
- }];
149
- const normalizedChildren = attachNestedChildrenToResultChildren(runId, resultChildren.map((result = {}, index): SubagentResultIntercomChild => {
150
- const baseOutput = result.output ?? data.summary;
151
- const hasRealOutput = typeof baseOutput === "string" && baseOutput.trim().length > 0;
152
- const output = hasRealOutput ? baseOutput : "(no output)";
153
- const summary = result.success === false && result.error
154
- ? `${result.error}${hasRealOutput ? `\n\nOutput:\n${baseOutput}` : ""}`
155
- : output;
156
- const sessionPath = result.sessionFile ?? (resultChildren.length === 1 ? data.sessionFile : undefined);
157
- const childNestedChildren = sanitizeNestedResultChildren(result.children, resultPath, `results[${index}].children`);
158
- return {
159
- agent: result.agent ?? data.agent ?? `step-${index + 1}`,
160
- status: resolveSubagentResultStatus({
161
- success: result.success,
162
- state: data.state === "paused" || typeof result.success !== "boolean" ? data.state : undefined,
163
- }),
164
- summary,
165
- index,
166
- artifactPath: result.artifactPaths?.outputPath,
167
- ...(typeof sessionPath === "string" && fsApi.existsSync(sessionPath) ? { sessionPath } : {}),
168
- ...(result.intercomTarget ? { intercomTarget: result.intercomTarget } : {}),
169
- ...(childNestedChildren ? { children: childNestedChildren } : {}),
170
- };
171
- }), nestedChildren);
172
-
173
- const intercomTarget = data.intercomTarget?.trim();
174
- if (intercomTarget) {
175
- const mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain"
176
- ? data.mode
177
- : resultChildren.length > 1 ? "chain" : "single";
178
- const payload = buildSubagentResultIntercomPayload({
179
- to: intercomTarget,
180
- runId,
181
- mode,
182
- source: "async",
183
- children: normalizedChildren,
184
- asyncId: data.id,
185
- asyncDir: data.asyncDir,
186
- });
187
- const delivered = await deliverSubagentResultIntercomEvent(pi.events, payload);
188
- if (!delivered) {
189
- console.error(`Subagent async grouped result intercom delivery was not acknowledged for '${resultPath}'.`);
190
- }
191
- }
192
-
193
- pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
194
- ...data,
195
- runId,
196
- ...(nestedChildren?.length ? { nestedChildren } : {}),
197
- ...(Array.isArray(data.results) ? {
198
- results: hasResultChildren
199
- ? normalizedChildren.map((child, index) => ({
200
- ...data.results![index],
201
- agent: child.agent,
202
- status: child.status,
203
- summary: child.summary,
204
- index: child.index,
205
- artifactPath: child.artifactPath,
206
- sessionPath: child.sessionPath,
207
- children: child.children,
208
- }))
209
- : [],
210
- } : {}),
115
+ const outcome = await processResultEntry(entry, {
116
+ pi, state, resultsDir, completionTtlMs, fsApi,
117
+ allowedStatusRoots: deps.allowedStatusRoots,
118
+ maxStatusBytes: deps.maxStatusBytes,
119
+ maxNoProgressFailures: deps.maxNoProgressFailures,
120
+ intercomTimeoutMs: deps.intercomTimeoutMs,
121
+ isActive: () => isEpochActive(epoch),
122
+ ownsResult: (data) => isEpochActive(epoch) && ownsResult(data),
211
123
  });
212
- fsApi.unlinkSync(resultPath);
124
+ const nextEntry = outcome.entry ?? entry;
125
+ if (outcome.status === "status-pending") scheduleStatusRecheck(nextEntry, epoch);
126
+ else if (outcome.status === "delivery-retry") scheduleDeliveryRetry(nextEntry, epoch);
127
+ else { statusRetries.clear(entry); deliveryRetries.clear(entry); }
213
128
  } catch (error) {
214
- if (isNotFoundError(error)) return;
215
- console.error(`Failed to process subagent result file '${resultPath}':`, error);
129
+ if (!isNotFoundError(error)) {
130
+ console.error(`Failed to process subagent result entry '${entry}':`, error);
131
+ if (isEpochActive(epoch) && entryExists(entry)) scheduleDeliveryRetry(entry, epoch);
132
+ }
133
+ } finally {
134
+ inFlight.delete(flightKey);
135
+ if (rerunAfterFlight.delete(flightKey) && entryExists(entry)) scheduleResultEntry(entry, 0, epoch);
216
136
  }
217
137
  };
218
138
 
219
- state.resultFileCoalescer = createFileCoalescer((file) => {
220
- void handleResult(file);
221
- }, 50);
139
+ state.resultFileCoalescer = createFileCoalescer((key) => {
140
+ const { epoch, entry } = parseCoalescerKey(key);
141
+ void handleResult(entry, epoch);
142
+ }, 50, { setTimeout: timers.setTimeout, clearTimeout: timers.clearTimeout });
222
143
 
223
144
  const primeExistingResults = () => {
145
+ if (stopped) return;
146
+ const epoch = processingEpoch;
224
147
  try {
225
- fsApi.readdirSync(resultsDir)
226
- .filter((f) => f.endsWith(".json"))
227
- .forEach((file) => state.resultFileCoalescer.schedule(file, 0));
148
+ for (const file of fsApi.readdirSync(resultsDir)) if (file.endsWith(".json")) scheduleResultEntry(file, 0, epoch);
149
+ for (const claim of listResultClaims(resultsDir, fsApi)) scheduleResultEntry(claimScheduleKey(claim.id), 0, epoch);
228
150
  } catch (error) {
229
- if (isNotFoundError(error)) return;
230
- console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
151
+ if (!isNotFoundError(error)) console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
231
152
  }
232
153
  };
154
+ const scheduleDirectoryRescan = (epoch: number) => {
155
+ if (!isEpochActive(epoch)) return;
156
+ if (directoryRescanTimer) timers.clearTimeout(directoryRescanTimer);
157
+ directoryRescanTimer = timers.setTimeout(() => {
158
+ directoryRescanTimer = null;
159
+ if (isEpochActive(epoch)) primeExistingResults();
160
+ }, DIRECTORY_RESCAN_DELAY_MS);
161
+ directoryRescanTimer.unref?.();
162
+ };
233
163
 
234
- const startPollingFallback = (reason: unknown) => {
164
+ const startPollingFallback = (reason: unknown, epoch: number, installation?: number) => {
165
+ if (!isEpochActive(epoch) || (installation !== undefined && installation !== activeWatcherInstallation)) return;
235
166
  state.watcher?.close();
236
167
  state.watcher = null;
168
+ activeWatcherInstallation = 0;
237
169
  if (state.watcherRestartTimer) return;
238
-
239
- console.error(
240
- `Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).`,
241
- );
170
+ console.error(`Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).`);
242
171
  primeExistingResults();
243
- state.watcherRestartTimer = timers.setInterval(primeExistingResults, POLL_INTERVAL_MS);
172
+ state.watcherRestartTimer = timers.setInterval(() => { if (isEpochActive(epoch)) primeExistingResults(); }, POLL_INTERVAL_MS);
244
173
  state.watcherRestartTimer.unref?.();
245
174
  };
246
175
 
247
- const scheduleRestart = () => {
248
- if (state.watcherRestartTimer) return;
249
- state.watcherRestartTimer = timers.setTimeout(() => {
176
+ function scheduleRestart(epoch: number): void {
177
+ if (!isEpochActive(epoch) || state.watcherRestartTimer) return;
178
+ let timer!: ReturnType<typeof setTimeout>;
179
+ timer = timers.setTimeout(() => {
180
+ if (state.watcherRestartTimer !== timer || !isEpochActive(epoch)) return;
250
181
  state.watcherRestartTimer = null;
251
- try {
252
- fsApi.mkdirSync(resultsDir, { recursive: true });
253
- startResultWatcher();
254
- } catch (error) {
255
- if (shouldFallBackToPolling(error)) {
256
- startPollingFallback(error);
257
- return;
258
- }
259
- console.error(`Failed to restart subagent result watcher for '${resultsDir}':`, error);
260
- scheduleRestart();
182
+ try { fsApi.mkdirSync(resultsDir, { recursive: true }); openResultWatcher(epoch); }
183
+ catch (error) {
184
+ if (!isEpochActive(epoch)) return;
185
+ if (shouldFallBackToPolling(error)) startPollingFallback(error, epoch);
186
+ else { console.error(`Failed to restart subagent result watcher for '${resultsDir}':`, error); scheduleRestart(epoch); }
261
187
  }
262
188
  }, WATCHER_RESTART_DELAY_MS);
263
- state.watcherRestartTimer.unref?.();
264
- };
189
+ state.watcherRestartTimer = timer;
190
+ timer.unref?.();
191
+ }
265
192
 
266
- const startResultWatcher = () => {
267
- if (state.watcher) return;
268
- if (state.watcherRestartTimer) {
269
- timers.clearTimeout(state.watcherRestartTimer);
270
- timers.clearInterval(state.watcherRestartTimer);
271
- state.watcherRestartTimer = null;
272
- }
193
+ function openResultWatcher(epoch: number): void {
194
+ if (!isEpochActive(epoch) || state.watcher) return;
195
+ const installation = ++watcherInstallation;
196
+ let handle: fs.FSWatcher | null = null;
197
+ let pendingSynchronousError: Error | undefined;
198
+ const handleWatcherError = (error: Error) => {
199
+ if (!handle) { pendingSynchronousError = error; return; }
200
+ if (!isEpochActive(epoch) || installation !== activeWatcherInstallation || state.watcher !== handle) return;
201
+ if (shouldFallBackToPolling(error)) { startPollingFallback(error, epoch, installation); return; }
202
+ console.error(`Subagent result watcher failed for '${resultsDir}':`, error);
203
+ handle.close();
204
+ if (state.watcher === handle) state.watcher = null;
205
+ activeWatcherInstallation = 0;
206
+ scheduleRestart(epoch);
207
+ };
273
208
  try {
274
- const handleWatcherError = (error: Error) => {
275
- if (shouldFallBackToPolling(error)) {
276
- startPollingFallback(error);
277
- return;
278
- }
279
- console.error(`Subagent result watcher failed for '${resultsDir}':`, error);
280
- state.watcher?.close();
281
- state.watcher = null;
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,
292
- });
293
- state.watcher?.unref?.();
209
+ handle = safeWatch(resultsDir, (_event, file) => {
210
+ if (!isEpochActive(epoch) || installation !== activeWatcherInstallation) return;
211
+ if (file) { const name = file.toString(); if (name.endsWith(".json")) scheduleResultEntry(name, 50, epoch); }
212
+ scheduleDirectoryRescan(epoch);
213
+ }, handleWatcherError, { watch: fsApi.watch, realpathSyncNative: fsApi.realpathSync?.native });
214
+ if (!handle) throw pendingSynchronousError ?? new Error("Result watcher installation returned no handle");
215
+ if (!isEpochActive(epoch)) { handle.close(); return; }
216
+ state.watcher = handle;
217
+ activeWatcherInstallation = installation;
218
+ handle.unref?.();
219
+ if (pendingSynchronousError) handleWatcherError(pendingSynchronousError);
294
220
  } catch (error) {
295
- if (shouldFallBackToPolling(error)) {
296
- startPollingFallback(error);
297
- return;
298
- }
299
- console.error(`Failed to start subagent result watcher for '${resultsDir}':`, error);
300
- state.watcher = null;
301
- scheduleRestart();
221
+ handle?.close();
222
+ if (state.watcher === handle) state.watcher = null;
223
+ activeWatcherInstallation = 0;
224
+ if (!isEpochActive(epoch)) return;
225
+ if (shouldFallBackToPolling(error)) startPollingFallback(error, epoch);
226
+ else { console.error(`Failed to start subagent result watcher for '${resultsDir}':`, error); scheduleRestart(epoch); }
302
227
  }
303
- };
228
+ }
304
229
 
230
+ const startResultWatcher = () => {
231
+ stopped = false;
232
+ processingEpoch += 1;
233
+ if (state.watcherRestartTimer) {
234
+ timers.clearTimeout(state.watcherRestartTimer); timers.clearInterval(state.watcherRestartTimer); state.watcherRestartTimer = null;
235
+ }
236
+ openResultWatcher(processingEpoch);
237
+ };
305
238
  const stopResultWatcher = () => {
239
+ stopped = true;
240
+ processingEpoch += 1;
306
241
  state.watcher?.close();
307
242
  state.watcher = null;
308
- if (state.watcherRestartTimer) {
309
- timers.clearTimeout(state.watcherRestartTimer);
310
- timers.clearInterval(state.watcherRestartTimer);
311
- }
243
+ activeWatcherInstallation = 0;
244
+ if (state.watcherRestartTimer) { timers.clearTimeout(state.watcherRestartTimer); timers.clearInterval(state.watcherRestartTimer); }
312
245
  state.watcherRestartTimer = null;
313
- state.resultFileCoalescer.clear();
246
+ if (directoryRescanTimer) timers.clearTimeout(directoryRescanTimer);
247
+ directoryRescanTimer = null;
248
+ statusRetries.clearAll(); deliveryRetries.clearAll(); state.resultFileCoalescer.clear();
314
249
  };
315
-
316
250
  return { startResultWatcher, primeExistingResults, stopResultWatcher };
317
251
  }
@@ -28,7 +28,8 @@ function exactAsyncLocation(id: string, asyncDirRoot: string, resultsDir: string
28
28
  }
29
29
 
30
30
  function foregroundIds(state: SubagentState | undefined): string[] {
31
- return state ? [...state.foregroundControls.keys()] : [];
31
+ if (!state) return [];
32
+ return [...new Set([...state.foregroundControls.keys(), ...(state.foregroundRuns?.keys() ?? [])])];
32
33
  }
33
34
 
34
35
  export function nestedScopeFromState(state: SubagentState | undefined): NestedRunResolutionScope | undefined {
@@ -57,7 +58,7 @@ export function resolveSubagentRunId(id: string, deps: ResolveSubagentRunIdDeps
57
58
  const resultsDir = deps.resultsDir ?? RESULTS_DIR;
58
59
 
59
60
  const nestedScope = deps.nested ?? nestedScopeFromState(deps.state);
60
- if (deps.state?.foregroundControls.has(id)) return { kind: "foreground", id };
61
+ if (deps.state?.foregroundControls.has(id) || deps.state?.foregroundRuns?.has(id)) return { kind: "foreground", id };
61
62
  const exactAsync = exactAsyncLocation(id, asyncDirRoot, resultsDir);
62
63
  if (exactAsync) return { kind: "async", id, location: exactAsync };
63
64
  const exactNested = findNestedRunMatchesById(id, nestedScope ? { scope: nestedScope } : {});
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
4
+ import { publishFileExclusive, unlinkIfPresent } from "../../shared/exclusive-file-publication.js";
4
5
  import { RESULTS_DIR, type AsyncParallelGroupStatus, type AsyncStatus, type NestedRunSummary, type SubagentRunMode } from "../../shared/types.ts";
5
6
  import { normalizeParallelGroups } from "./parallel-groups.ts";
6
7
  import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent, type NestedRoute } from "../shared/nested-events.ts";
@@ -28,6 +29,7 @@ interface ReconcileAsyncRunOptions {
28
29
  startedRun?: StartedRunMetadata;
29
30
  missingStatusGraceMs?: number;
30
31
  staleAlivePidMs?: number;
32
+ publish?: (source: string, destination: string) => "published" | "exists";
31
33
  }
32
34
 
33
35
  interface ReconcileAsyncRunResult {
@@ -53,6 +55,52 @@ function appendJsonl(filePath: string, payload: object): void {
53
55
  fs.appendFileSync(filePath, `${JSON.stringify(payload)}\n`, "utf-8");
54
56
  }
55
57
 
58
+ function repairStagePath(resultPath: string): string {
59
+ return path.join(path.dirname(resultPath), `.${path.basename(resultPath)}.stale-repair-stage`);
60
+ }
61
+
62
+ function readStagedRepair(resultPath: string): object | undefined {
63
+ try {
64
+ return JSON.parse(fs.readFileSync(repairStagePath(resultPath), "utf-8")) as object;
65
+ } catch (error) {
66
+ if (isNotFoundError(error)) return undefined;
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ function stagedRepairMessage(staged: object): string | undefined {
72
+ const summary = (staged as { summary?: unknown }).summary;
73
+ return typeof summary === "string" ? summary : undefined;
74
+ }
75
+
76
+ function retireObsoleteStage(resultPath: string): void {
77
+ const stagePath = repairStagePath(resultPath);
78
+ const retiredPath = `${stagePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.retired`;
79
+ try {
80
+ fs.renameSync(stagePath, retiredPath);
81
+ try { fs.unlinkSync(retiredPath); } catch (error) { if (!isNotFoundError(error)) console.error(error); }
82
+ } catch (error) {
83
+ if (!isNotFoundError(error)) console.error(error);
84
+ }
85
+ }
86
+
87
+ function publishStagedRepair(
88
+ resultPath: string,
89
+ status: AsyncStatus,
90
+ staged: object,
91
+ publish: (source: string, destination: string) => "published" | "exists" = publishFileExclusive,
92
+ ): ReconcileAsyncRunResult {
93
+ const stagePath = repairStagePath(resultPath);
94
+ const outcome = publish(stagePath, resultPath);
95
+ unlinkIfPresent(stagePath);
96
+ return {
97
+ status,
98
+ repaired: outcome === "published",
99
+ resultPath,
100
+ message: outcome === "published" ? stagedRepairMessage(staged) : "A concurrently published result was preserved.",
101
+ };
102
+ }
103
+
56
104
  function readStatusFile(asyncDir: string): AsyncStatus | null {
57
105
  const statusPath = path.join(asyncDir, "status.json");
58
106
  let content: string;
@@ -222,19 +270,25 @@ function buildFailedRepair(status: AsyncStatus, asyncDir: string, now: number, r
222
270
  };
223
271
  }
224
272
 
225
- function writeFailedRepair(asyncDir: string, status: AsyncStatus, resultPath: string, now: number, reason?: string): ReconcileAsyncRunResult {
273
+ function writeFailedRepair(asyncDir: string, status: AsyncStatus, resultPath: string, now: number, reason?: string, publish?: ReconcileAsyncRunOptions["publish"]): ReconcileAsyncRunResult {
226
274
  const repair = buildFailedRepair(status, asyncDir, now, reason);
227
- writeAtomicJson(resultPath, repair.result);
275
+ // Stage the exact public payload beside its destination. Publication consumes
276
+ // the hidden stage with one atomic rename after terminal status is durable.
277
+ writeAtomicJson(repairStagePath(resultPath), repair.result);
228
278
  writeAtomicJson(path.join(asyncDir, "status.json"), repair.status);
229
- appendJsonl(path.join(asyncDir, "events.jsonl"), {
230
- type: "subagent.run.repaired_stale",
231
- ts: now,
232
- runId: repair.status.runId,
233
- pid: status.pid,
234
- resultPath,
235
- message: repair.message,
236
- });
237
- return { status: repair.status, repaired: true, resultPath, message: repair.message };
279
+ try {
280
+ appendJsonl(path.join(asyncDir, "events.jsonl"), {
281
+ type: "subagent.run.repaired_stale",
282
+ ts: now,
283
+ runId: repair.status.runId,
284
+ pid: status.pid,
285
+ resultPath,
286
+ message: repair.message,
287
+ });
288
+ } catch (error) {
289
+ console.error(`Failed to append stale-repair event for '${asyncDir}'; publishing the recoverable result:`, error);
290
+ }
291
+ return publishStagedRepair(resultPath, repair.status, repair.result, publish);
238
292
  }
239
293
 
240
294
  function terminal(state: AsyncStatus["state"]): boolean {
@@ -305,6 +359,7 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
305
359
  const runId = effectiveStatus.runId || path.basename(asyncDir);
306
360
  const resultPath = path.join(options.resultsDir ?? RESULTS_DIR, `${runId}.json`);
307
361
  if (fs.existsSync(resultPath)) {
362
+ retireObsoleteStage(resultPath);
308
363
  const terminalStatus = effectiveStatus.state === "running" || effectiveStatus.state === "queued"
309
364
  ? terminalStatusFromResult(effectiveStatus, resultPath, now)
310
365
  : undefined;
@@ -315,6 +370,11 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
315
370
  return { status: effectiveStatus, repaired: false, resultPath };
316
371
  }
317
372
 
373
+ const stagedRepair = readStagedRepair(resultPath);
374
+ if (stagedRepair && terminal(effectiveStatus.state)) {
375
+ return publishStagedRepair(resultPath, effectiveStatus, stagedRepair, options.publish);
376
+ }
377
+
318
378
  if (effectiveStatus.state !== "running" || typeof effectiveStatus.pid !== "number") {
319
379
  return { status: status ?? null, repaired: false, resultPath };
320
380
  }
@@ -332,8 +392,7 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
332
392
  const lastUpdate = effectiveStatus.lastUpdate ?? effectiveStatus.startedAt;
333
393
  if (now - lastUpdate <= staleAfterMs) return { status: status ?? null, repaired: false, resultPath };
334
394
  const message = `Async runner process ${effectiveStatus.pid} still has a live PID, but status has not updated for ${now - lastUpdate}ms. Marked run failed by stale-run reconciliation because PID ownership cannot be verified.`;
335
- return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now, message);
395
+ return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now, message, options.publish);
336
396
  }
337
-
338
- return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now);
397
+ return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now, undefined, options.publish);
339
398
  }