@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,4 +1,13 @@
1
+ export interface CompletionSeenRecord {
2
+ seenAt: number;
3
+ signature?: string;
4
+ }
5
+
6
+ export type CompletionSeenMap = Map<string, number | CompletionSeenRecord>;
7
+ export type CompletionSeenMatch = "miss" | "match" | "conflict";
8
+
1
9
  interface CompletionDataLike {
10
+ runId?: unknown;
2
11
  id?: unknown;
3
12
  agent?: unknown;
4
13
  timestamp?: unknown;
@@ -20,6 +29,8 @@ function asFiniteNumber(value: unknown): number | undefined {
20
29
  }
21
30
 
22
31
  export function buildCompletionKey(data: CompletionDataLike, fallback: string): string {
32
+ const runId = asNonEmptyString(data.runId);
33
+ if (runId) return `run:${runId}`;
23
34
  const id = asNonEmptyString(data.id);
24
35
  if (id) return `id:${id}`;
25
36
  const sessionId = asNonEmptyString(data.sessionId) ?? "no-session";
@@ -40,24 +51,50 @@ export function buildCompletionKey(data: CompletionDataLike, fallback: string):
40
51
  ].join(":");
41
52
  }
42
53
 
43
- function pruneSeenMap(seen: Map<string, number>, now: number, ttlMs: number): void {
44
- for (const [key, ts] of seen.entries()) {
45
- if (now - ts > ttlMs) seen.delete(key);
54
+ function recordTimestamp(record: number | CompletionSeenRecord): number {
55
+ return typeof record === "number" ? record : record.seenAt;
56
+ }
57
+
58
+ function pruneSeenMap(seen: CompletionSeenMap, now: number, ttlMs: number): void {
59
+ for (const [key, record] of seen.entries()) {
60
+ if (now - recordTimestamp(record) > ttlMs) seen.delete(key);
46
61
  }
47
62
  }
48
63
 
49
- export function markSeenWithTtl(seen: Map<string, number>, key: string, now: number, ttlMs: number): boolean {
64
+ export function lookupSeenWithTtl(
65
+ seen: CompletionSeenMap,
66
+ key: string,
67
+ signature: string | undefined,
68
+ now: number,
69
+ ttlMs: number,
70
+ ): CompletionSeenMatch {
71
+ pruneSeenMap(seen, now, ttlMs);
72
+ const record = seen.get(key);
73
+ if (record === undefined) return "miss";
74
+ const recordedSignature = typeof record === "number" ? undefined : record.signature;
75
+ return recordedSignature === undefined || signature === undefined || recordedSignature === signature ? "match" : "conflict";
76
+ }
77
+
78
+ export function hasSeenWithTtl(seen: CompletionSeenMap, key: string, now: number, ttlMs: number): boolean {
79
+ return lookupSeenWithTtl(seen, key, undefined, now, ttlMs) !== "miss";
80
+ }
81
+
82
+ export function recordSeen(seen: CompletionSeenMap, key: string, now: number, signature?: string): void {
83
+ seen.set(key, signature === undefined ? now : { seenAt: now, signature });
84
+ }
85
+
86
+ export function markSeenWithTtl(seen: CompletionSeenMap, key: string, now: number, ttlMs: number): boolean {
50
87
  pruneSeenMap(seen, now, ttlMs);
51
88
  if (seen.has(key)) return true;
52
89
  seen.set(key, now);
53
90
  return false;
54
91
  }
55
92
 
56
- export function getGlobalSeenMap(storeKey: string): Map<string, number> {
93
+ export function getGlobalSeenMap(storeKey: string): CompletionSeenMap {
57
94
  const globalStore = globalThis as Record<string, unknown>;
58
95
  const existing = globalStore[storeKey];
59
- if (existing instanceof Map) return existing as Map<string, number>;
60
- const map = new Map<string, number>();
96
+ if (existing instanceof Map) return existing as CompletionSeenMap;
97
+ const map: CompletionSeenMap = new Map();
61
98
  globalStore[storeKey] = map;
62
99
  return map;
63
100
  }
@@ -0,0 +1,34 @@
1
+ import type { IntercomEventBus } from "../../shared/types.ts";
2
+ import { SUBAGENT_ASYNC_COMPLETE_EVENT } from "../../shared/types.ts";
3
+
4
+ export interface CompletionNotificationEnvelope extends Record<string, unknown> {
5
+ notificationId: string;
6
+ acknowledge: (delivered: boolean) => void;
7
+ }
8
+
9
+ export function deliverLocalCompletionNotification(
10
+ events: IntercomEventBus,
11
+ payload: Record<string, unknown>,
12
+ notificationId: string,
13
+ ): Promise<boolean> {
14
+ return new Promise((resolve) => {
15
+ let settled = false;
16
+ const finish = (delivered: boolean) => {
17
+ if (settled) return;
18
+ settled = true;
19
+ resolve(delivered);
20
+ };
21
+ try {
22
+ events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
23
+ ...payload,
24
+ notificationId,
25
+ acknowledge: finish,
26
+ } satisfies CompletionNotificationEnvelope);
27
+ // The established event contract treats successful synchronous emission as
28
+ // delivery. Production listeners can synchronously reject via acknowledge(false).
29
+ finish(true);
30
+ } catch {
31
+ finish(false);
32
+ }
33
+ });
34
+ }
@@ -3,7 +3,8 @@
3
3
  */
4
4
 
5
5
  import type { ExtensionAPI } from "@bastani/atomic";
6
- import { buildCompletionKey, getGlobalSeenMap, markSeenWithTtl } from "./completion-dedupe.ts";
6
+ import { buildCompletionKey, hasSeenWithTtl, recordSeen } from "./completion-dedupe.ts";
7
+ import type { CompletionNotificationEnvelope } from "./completion-notification.ts";
7
8
  import { SUBAGENT_ASYNC_COMPLETE_EVENT } from "../../shared/types.ts";
8
9
 
9
10
  interface ChainStepResult {
@@ -40,27 +41,62 @@ interface SubagentResult {
40
41
  totalTasks?: number;
41
42
  }
42
43
 
43
- export default function registerSubagentNotify(pi: ExtensionAPI): void {
44
- const unsubscribeStoreKey = "__pi_subagents_notify_unsubscribe__";
45
- const globalStore = globalThis as Record<string, unknown>;
46
- const previousUnsubscribe = globalStore[unsubscribeStoreKey];
47
- if (typeof previousUnsubscribe === "function") {
44
+ interface NotifyRegistration {
45
+ unsubscribe: () => void;
46
+ }
47
+
48
+ function getNotifyRegistry(): WeakMap<ExtensionAPI, NotifyRegistration> {
49
+ const key = "__piSubagentsNotifyRegistrations";
50
+ const store = globalThis as Record<string, unknown>;
51
+ const existing = store[key];
52
+ if (existing instanceof WeakMap) return existing as WeakMap<ExtensionAPI, NotifyRegistration>;
53
+ const registry = new WeakMap<ExtensionAPI, NotifyRegistration>();
54
+ store[key] = registry;
55
+ return registry;
56
+ }
57
+
58
+ function getNotifySeenRegistry(): WeakMap<ExtensionAPI, Map<string, number>> {
59
+ const key = "__piSubagentsNotifySeenByApi";
60
+ const store = globalThis as Record<string, unknown>;
61
+ const existing = store[key];
62
+ if (existing instanceof WeakMap) return existing as WeakMap<ExtensionAPI, Map<string, number>>;
63
+ const registry = new WeakMap<ExtensionAPI, Map<string, number>>();
64
+ store[key] = registry;
65
+ return registry;
66
+ }
67
+
68
+ function getNotifySeen(pi: ExtensionAPI): Map<string, number> {
69
+ const registry = getNotifySeenRegistry();
70
+ const existing = registry.get(pi);
71
+ if (existing) return existing;
72
+ const seen = new Map<string, number>();
73
+ registry.set(pi, seen);
74
+ return seen;
75
+ }
76
+
77
+ export default function registerSubagentNotify(pi: ExtensionAPI): () => void {
78
+ const registry = getNotifyRegistry();
79
+ const previous = registry.get(pi);
80
+ if (previous) {
48
81
  try {
49
- previousUnsubscribe();
82
+ previous.unsubscribe();
50
83
  } catch {
51
84
  // Best effort cleanup for stale handlers from an older reload.
52
85
  }
53
86
  }
54
-
55
- const seen = getGlobalSeenMap("__pi_subagents_notify_seen__");
87
+ const seen = getNotifySeen(pi);
56
88
  const ttlMs = 10 * 60 * 1000;
57
89
 
90
+ let registration: NotifyRegistration;
58
91
  const handleComplete = (data: unknown) => {
59
- const result = data as SubagentResult;
92
+ if (registry.get(pi) !== registration) return;
93
+ const result = data as SubagentResult & Partial<CompletionNotificationEnvelope>;
60
94
  const now = Date.now();
61
- const key = buildCompletionKey(result, "notify");
62
- if (markSeenWithTtl(seen, key, now, ttlMs)) return;
63
-
95
+ const key = result.notificationId ? `notification:${result.notificationId}` : buildCompletionKey(result, "notify");
96
+ if (hasSeenWithTtl(seen, key, now, ttlMs)) {
97
+ result.acknowledge?.(true);
98
+ return;
99
+ }
64
100
  const agent = result.agent ?? "unknown";
65
101
  const summary = typeof result.summary === "string" ? result.summary : "";
66
102
  const paused = !result.success && (
@@ -94,15 +130,29 @@ export default function registerSubagentNotify(pi: ExtensionAPI): void {
94
130
  .filter((line) => line !== undefined)
95
131
  .join("\n");
96
132
 
97
- pi.sendMessage(
98
- {
99
- customType: "subagent-notify",
100
- content,
101
- display: true,
102
- },
103
- { triggerTurn: true },
104
- );
133
+ try {
134
+ pi.sendMessage(
135
+ {
136
+ customType: "subagent-notify",
137
+ content,
138
+ display: true,
139
+ },
140
+ { triggerTurn: true },
141
+ );
142
+ recordSeen(seen, key, Date.now());
143
+ result.acknowledge?.(true);
144
+ } catch (error) {
145
+ result.acknowledge?.(false);
146
+ console.error("Failed to deliver async subagent completion notification:", error);
147
+ }
105
148
  };
106
149
 
107
- globalStore[unsubscribeStoreKey] = pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete);
150
+ const unsubscribe = pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete);
151
+ registration = { unsubscribe };
152
+ registry.set(pi, registration);
153
+ return () => {
154
+ if (registry.get(pi) !== registration) return;
155
+ registry.delete(pi);
156
+ unsubscribe();
157
+ };
108
158
  }
@@ -0,0 +1,232 @@
1
+ import * as fs from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import * as path from "node:path";
4
+ import { buildCompletionKey, lookupSeenWithTtl, recordSeen } from "./completion-dedupe.js";
5
+ import { deliverClaimedCompletion, type CompletionClaimSnapshot } from "./completion-claims.js";
6
+ import { deliverLocalCompletionNotification } from "./completion-notification.js";
7
+ import { quarantineResultFile } from "./result-quarantine.js";
8
+ import {
9
+ claimIdFromScheduleKey,
10
+ claimPublicResult,
11
+ claimScheduleKey,
12
+ loadResultClaim,
13
+ readClaimData,
14
+ removeResultClaim,
15
+ updateResultClaim,
16
+ type FrozenCompletionEnvelope,
17
+ type ResultClaimFs,
18
+ type ResultFileClaim,
19
+ } from "./result-file-claims.js";
20
+ import { modernResultHasTerminalStatus, type ResultStatusFs } from "./result-status.js";
21
+ import {
22
+ attachNestedChildrenToResultChildren,
23
+ buildSubagentResultIntercomPayload,
24
+ compactNestedResultChildren,
25
+ deliverSubagentResultIntercomEvent,
26
+ resolveSubagentResultStatus,
27
+ } from "../../intercom/result-intercom.js";
28
+ import { projectNestedRegistryForRoot } from "../shared/nested-events.js";
29
+ import { buildCompletionSignature, sanitizeNestedResultChildren, type ResultFileData } from "./result-watcher-data.js";
30
+ import type { IntercomEventBus, SubagentResultIntercomChild, SubagentState } from "../../shared/types.js";
31
+
32
+ export interface ResultProcessOutcome {
33
+ status: "done" | "status-pending" | "delivery-retry" | "inactive";
34
+ entry?: string;
35
+ }
36
+
37
+ export interface ResultProcessorFs extends ResultClaimFs, Partial<ResultStatusFs> {
38
+ existsSync?: typeof fs.existsSync;
39
+ unlinkSync?: typeof fs.unlinkSync;
40
+ linkSync?: typeof fs.linkSync;
41
+ copyFileSync?: typeof fs.copyFileSync;
42
+ openSync?: typeof fs.openSync;
43
+ readSync?: typeof fs.readSync;
44
+ closeSync?: typeof fs.closeSync;
45
+ }
46
+
47
+ export interface ResultProcessorContext {
48
+ pi: { events: IntercomEventBus };
49
+ state: SubagentState;
50
+ resultsDir: string;
51
+ completionTtlMs: number;
52
+ fsApi?: ResultProcessorFs;
53
+ allowedStatusRoots?: string[];
54
+ maxStatusBytes?: number;
55
+ maxNoProgressFailures?: number;
56
+ intercomTimeoutMs?: number | false;
57
+ isActive: () => boolean;
58
+ ownsResult: (data: ResultFileData) => boolean;
59
+ }
60
+
61
+ function statusFs(fsApi: ResultProcessorFs): ResultStatusFs {
62
+ return {
63
+ realpathSync: fsApi.realpathSync ?? fs.realpathSync,
64
+ lstatSync: fsApi.lstatSync ?? fs.lstatSync,
65
+ openSync: fsApi.openSync ?? fs.openSync,
66
+ fstatSync: fsApi.fstatSync ?? fs.fstatSync,
67
+ readSync: fsApi.readSync ?? fs.readSync,
68
+ closeSync: fsApi.closeSync ?? fs.closeSync,
69
+ };
70
+ }
71
+
72
+ function readPublic(resultsDir: string, file: string, fsApi: ResultProcessorFs): ResultFileData {
73
+ return JSON.parse((fsApi.readFileSync ?? fs.readFileSync)(path.join(resultsDir, file), "utf-8")) as ResultFileData;
74
+ }
75
+
76
+ function markClaimState(claim: ResultFileClaim, value: CompletionClaimSnapshot, fsApi: ResultProcessorFs): void {
77
+ updateResultClaim(claim, {
78
+ intercomDelivered: value.intercomDelivered,
79
+ localDelivered: value.localDelivered,
80
+ noProgressFailures: value.noProgressFailures,
81
+ envelope: value.envelope,
82
+ ...(value.terminalStatus === "delivered" ? { state: "delivered" as const } : {}),
83
+ ...(value.terminalStatus === "exhausted" ? { state: "undelivered" as const } : {}),
84
+ }, fsApi);
85
+ }
86
+
87
+ function buildEnvelope(data: ResultFileData, resultPath: string, fsApi: ResultProcessorFs): FrozenCompletionEnvelope {
88
+ const runId = data.runId ?? data.id ?? path.basename(resultPath, ".json");
89
+ const explicitNested = data.nestedChildren !== undefined;
90
+ let nestedChildren = compactNestedResultChildren(sanitizeNestedResultChildren(data.nestedChildren, resultPath, "nestedChildren"));
91
+ if (!nestedChildren?.length && !explicitNested) nestedChildren = compactNestedResultChildren(projectNestedRegistryForRoot(runId)?.children);
92
+ const hasResults = Array.isArray(data.results) && data.results.length > 0;
93
+ const resultChildren = hasResults ? data.results! : [{ agent: data.agent, output: data.summary, success: data.success }];
94
+ const normalized = attachNestedChildrenToResultChildren(runId, resultChildren.map((result = {}, index): SubagentResultIntercomChild => {
95
+ const baseOutput = result.output ?? data.summary;
96
+ const hasOutput = typeof baseOutput === "string" && baseOutput.trim().length > 0;
97
+ const output = hasOutput ? baseOutput : "(no output)";
98
+ const summary = result.success === false && result.error
99
+ ? `${result.error}${hasOutput ? `\n\nOutput:\n${baseOutput}` : ""}`
100
+ : output;
101
+ const sessionPath = result.sessionFile ?? (resultChildren.length === 1 ? data.sessionFile : undefined);
102
+ const children = sanitizeNestedResultChildren(result.children, resultPath, `results[${index}].children`);
103
+ return {
104
+ agent: result.agent ?? data.agent ?? `step-${index + 1}`,
105
+ status: resolveSubagentResultStatus({
106
+ success: result.success,
107
+ state: data.state === "paused" || typeof result.success !== "boolean" ? data.state : undefined,
108
+ }),
109
+ summary,
110
+ index,
111
+ artifactPath: result.artifactPaths?.outputPath,
112
+ ...(typeof sessionPath === "string" && (fsApi.existsSync ?? fs.existsSync)(sessionPath) ? { sessionPath } : {}),
113
+ ...(result.intercomTarget ? { intercomTarget: result.intercomTarget } : {}),
114
+ ...(children ? { children } : {}),
115
+ };
116
+ }), nestedChildren);
117
+ const local: Record<string, unknown> = {
118
+ ...data,
119
+ runId,
120
+ ...(nestedChildren?.length ? { nestedChildren } : {}),
121
+ ...(Array.isArray(data.results) ? {
122
+ results: hasResults ? normalized.map((child, index) => ({
123
+ ...data.results![index], agent: child.agent, status: child.status, summary: child.summary,
124
+ index: child.index, artifactPath: child.artifactPath, sessionPath: child.sessionPath, children: child.children,
125
+ })) : [],
126
+ } : {}),
127
+ };
128
+ const target = data.intercomTarget?.trim();
129
+ const mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain"
130
+ ? data.mode : resultChildren.length > 1 ? "chain" : "single";
131
+ return {
132
+ local,
133
+ ...(target ? { intercom: { to: target, runId, mode, source: "async", children: normalized, asyncId: data.id, asyncDir: data.asyncDir } } : {}),
134
+ };
135
+ }
136
+
137
+ function quarantineClaim(context: ResultProcessorContext, claim: ResultFileClaim): void {
138
+ const fsApi = context.fsApi ?? fs;
139
+ updateResultClaim(claim, { state: "undelivered" }, fsApi);
140
+ quarantineResultFile(context.resultsDir, claim.meta.originalFile, claim.payloadPath, fsApi as typeof fs);
141
+ removeResultClaim(claim, fsApi);
142
+ }
143
+
144
+ export async function processResultEntry(entry: string, context: ResultProcessorContext): Promise<ResultProcessOutcome> {
145
+ const fsApi = context.fsApi ?? fs;
146
+ let claim: ResultFileClaim | undefined;
147
+ const existingClaimId = claimIdFromScheduleKey(entry);
148
+ if (existingClaimId) {
149
+ claim = loadResultClaim(context.resultsDir, existingClaimId, fsApi);
150
+ if (!claim) return { status: "done" };
151
+ if (claim.meta.state === "delivered") { removeResultClaim(claim, fsApi); return { status: "done" }; }
152
+ if (claim.meta.state === "undelivered") { quarantineClaim(context, claim); return { status: "done" }; }
153
+ } else {
154
+ const publicPath = path.join(context.resultsDir, entry);
155
+ if (!(fsApi.existsSync ?? fs.existsSync)(publicPath)) return { status: "done" };
156
+ const preliminary = readPublic(context.resultsDir, entry, fsApi);
157
+ if (!context.ownsResult(preliminary)) return { status: "inactive" };
158
+ if (!modernResultHasTerminalStatus(preliminary, statusFs(fsApi), {
159
+ allowedRoots: context.allowedStatusRoots,
160
+ maxBytes: context.maxStatusBytes,
161
+ })) return { status: "status-pending", entry };
162
+ claim = claimPublicResult(context.resultsDir, entry, fsApi);
163
+ }
164
+
165
+ const { data } = readClaimData(claim, fsApi);
166
+ if (!context.ownsResult(data)) return { status: "inactive" };
167
+ if (!modernResultHasTerminalStatus(data, statusFs(fsApi), {
168
+ allowedRoots: context.allowedStatusRoots,
169
+ maxBytes: context.maxStatusBytes,
170
+ })) return { status: "status-pending", entry: claim ? claimScheduleKey(claim.id) : entry };
171
+ const sourceSignature = buildCompletionSignature({ source: data });
172
+ if (claim.meta.sourceSignature && claim.meta.sourceSignature !== sourceSignature) {
173
+ quarantineClaim(context, claim);
174
+ return { status: "done" };
175
+ }
176
+ let envelope = claim.meta.envelope;
177
+ if (!envelope) {
178
+ envelope = buildEnvelope(data, claim.payloadPath, fsApi);
179
+ updateResultClaim(claim, { sourceSignature, envelope }, fsApi);
180
+ }
181
+ const completionKey = buildCompletionKey(data, `result:${claim.meta.originalFile}`);
182
+ const claimKey = `${path.resolve(context.resultsDir)}:${completionKey}`;
183
+ const stableHash = createHash("sha256").update(`${claimKey}:${sourceSignature}`).digest("hex");
184
+ const seen = lookupSeenWithTtl(context.state.completionSeen, completionKey, sourceSignature, Date.now(), context.completionTtlMs);
185
+ if (seen === "match") {
186
+ updateResultClaim(claim, { state: "delivered" }, fsApi);
187
+ removeResultClaim(claim, fsApi);
188
+ return { status: "done" };
189
+ }
190
+ if (seen === "conflict") {
191
+ quarantineClaim(context, claim);
192
+ return { status: "done" };
193
+ }
194
+ const claimResult = await deliverClaimedCompletion(
195
+ claimKey,
196
+ sourceSignature,
197
+ context.completionTtlMs,
198
+ {
199
+ isOwned: () => context.isActive() && context.ownsResult(data),
200
+ onState: (value) => markClaimState(claim!, value, fsApi),
201
+ intercom: envelope.intercom ? async (frozen) => {
202
+ const payload = buildSubagentResultIntercomPayload(frozen.intercom!);
203
+ payload.requestId = `completion-${stableHash}`;
204
+ return deliverSubagentResultIntercomEvent(context.pi.events, payload, context.intercomTimeoutMs ?? 500);
205
+ } : undefined,
206
+ local: async (frozen) => deliverLocalCompletionNotification(
207
+ context.pi.events, frozen.local, `completion-notify-${stableHash}`,
208
+ ),
209
+ },
210
+ context.maxNoProgressFailures,
211
+ {
212
+ envelope: claim.meta.envelope,
213
+ intercomDelivered: claim.meta.intercomDelivered,
214
+ localDelivered: claim.meta.localDelivered,
215
+ noProgressFailures: claim.meta.noProgressFailures,
216
+ },
217
+ envelope,
218
+ );
219
+ if (claimResult.status === "retry" || claimResult.status === "released") {
220
+ return context.isActive() && context.ownsResult(data)
221
+ ? { status: "delivery-retry", entry: claimScheduleKey(claim.id) }
222
+ : { status: "inactive" };
223
+ }
224
+ if (claimResult.status === "conflict" || claimResult.status === "exhausted") {
225
+ quarantineClaim(context, claim);
226
+ return { status: "done" };
227
+ }
228
+ recordSeen(context.state.completionSeen, completionKey, Date.now(), sourceSignature);
229
+ updateResultClaim(claim, { state: "delivered" }, fsApi);
230
+ removeResultClaim(claim, fsApi);
231
+ return { status: "done" };
232
+ }
@@ -0,0 +1,151 @@
1
+ import * as fs from "node:fs";
2
+ import { randomUUID } from "node:crypto";
3
+ import * as path from "node:path";
4
+ import type { GroupedResultIntercomMessageInput } from "../../intercom/result-intercom.js";
5
+ import type { ResultFileData } from "./result-watcher-data.js";
6
+
7
+ const CLAIMS_DIR = ".claims";
8
+ const CLAIM_SCHEDULE_PREFIX = "@claim/";
9
+
10
+ export interface FrozenCompletionEnvelope {
11
+ local: Record<string, unknown>;
12
+ intercom?: GroupedResultIntercomMessageInput;
13
+ }
14
+ export interface ResultFileClaimMeta {
15
+ version: 1;
16
+ id: string;
17
+ originalFile: string;
18
+ state: "active" | "delivered" | "undelivered";
19
+ createdAt: number;
20
+ sourceSignature?: string;
21
+ envelope?: FrozenCompletionEnvelope;
22
+ intercomDelivered?: boolean;
23
+ localDelivered?: boolean;
24
+ noProgressFailures?: number;
25
+ }
26
+
27
+ export interface ResultFileClaim {
28
+ id: string;
29
+ dir: string;
30
+ payloadPath: string;
31
+ metaPath: string;
32
+ meta: ResultFileClaimMeta;
33
+ }
34
+
35
+ export interface ResultClaimFs {
36
+ mkdirSync?: typeof fs.mkdirSync;
37
+ renameSync?: typeof fs.renameSync;
38
+ readFileSync?: typeof fs.readFileSync;
39
+ writeFileSync?: typeof fs.writeFileSync;
40
+ readdirSync?: typeof fs.readdirSync;
41
+ rmSync?: typeof fs.rmSync;
42
+ rmdirSync?: typeof fs.rmdirSync;
43
+ }
44
+
45
+ function api(fsApi: ResultClaimFs) {
46
+ return {
47
+ mkdirSync: fsApi.mkdirSync ?? fs.mkdirSync,
48
+ renameSync: fsApi.renameSync ?? fs.renameSync,
49
+ readFileSync: fsApi.readFileSync ?? fs.readFileSync,
50
+ writeFileSync: fsApi.writeFileSync ?? fs.writeFileSync,
51
+ rmSync: fsApi.rmSync ?? fs.rmSync,
52
+ rmdirSync: fsApi.rmdirSync ?? fs.rmdirSync,
53
+ };
54
+ }
55
+
56
+ export function claimScheduleKey(id: string): string {
57
+ return `${CLAIM_SCHEDULE_PREFIX}${id}`;
58
+ }
59
+
60
+ export function claimIdFromScheduleKey(key: string): string | undefined {
61
+ return key.startsWith(CLAIM_SCHEDULE_PREFIX) ? key.slice(CLAIM_SCHEDULE_PREFIX.length) : undefined;
62
+ }
63
+
64
+ export function loadResultClaim(resultsDir: string, id: string, fsApi: ResultClaimFs = fs): ResultFileClaim | undefined {
65
+ try {
66
+ const dir = path.join(resultsDir, CLAIMS_DIR, id);
67
+ const metaPath = path.join(dir, "claim.json");
68
+ const payloadPath = path.join(dir, "result.json");
69
+ const meta = JSON.parse((fsApi.readFileSync ?? fs.readFileSync)(metaPath, "utf-8")) as ResultFileClaimMeta;
70
+ if (meta.version !== 1 || meta.id !== id || !path.basename(meta.originalFile)) return undefined;
71
+ return { id, dir, payloadPath, metaPath, meta };
72
+ } catch {
73
+ return undefined;
74
+ }
75
+ }
76
+
77
+ export function listResultClaims(resultsDir: string, fsApi: ResultClaimFs = fs): ResultFileClaim[] {
78
+ const claimsDir = path.join(resultsDir, CLAIMS_DIR);
79
+ try {
80
+ return (fsApi.readdirSync ?? fs.readdirSync)(claimsDir, { withFileTypes: true })
81
+ .filter((entry) => entry.isDirectory())
82
+ .map((entry) => loadResultClaim(resultsDir, entry.name, fsApi))
83
+ .filter((claim): claim is ResultFileClaim => Boolean(claim));
84
+ } catch {
85
+ return [];
86
+ }
87
+ }
88
+
89
+ export function claimPublicResult(
90
+ resultsDir: string,
91
+ file: string,
92
+ fsApi: ResultClaimFs = fs,
93
+ createId: () => string = randomUUID,
94
+ ): ResultFileClaim {
95
+ const f = api(fsApi);
96
+ const claimsDir = path.join(resultsDir, CLAIMS_DIR);
97
+ f.mkdirSync(claimsDir, { recursive: true });
98
+ for (let attempt = 0; attempt < 16; attempt += 1) {
99
+ const id = createId();
100
+ const dir = path.join(claimsDir, id);
101
+ try {
102
+ f.mkdirSync(dir);
103
+ } catch (error) {
104
+ if ((error as NodeJS.ErrnoException).code === "EEXIST") continue;
105
+ throw error;
106
+ }
107
+ const metaPath = path.join(dir, "claim.json");
108
+ const payloadPath = path.join(dir, "result.json");
109
+ const meta: ResultFileClaimMeta = {
110
+ version: 1, id, originalFile: path.basename(file), state: "active", createdAt: Date.now(),
111
+ };
112
+ try {
113
+ f.writeFileSync(metaPath, `${JSON.stringify(meta)}\n`, "utf-8");
114
+ f.renameSync(path.join(resultsDir, file), payloadPath);
115
+ return { id, dir, payloadPath, metaPath, meta };
116
+ } catch (error) {
117
+ f.rmSync(dir, { recursive: true, force: true });
118
+ throw error;
119
+ }
120
+ }
121
+ throw new Error(`Unable to allocate a result claim for '${file}'`);
122
+ }
123
+
124
+ export function readClaimData(claim: ResultFileClaim, fsApi: ResultClaimFs = fs): { raw: string; data: ResultFileData } {
125
+ const raw = (fsApi.readFileSync ?? fs.readFileSync)(claim.payloadPath, "utf-8");
126
+ return { raw, data: JSON.parse(raw) as ResultFileData };
127
+ }
128
+
129
+ export function updateResultClaim(
130
+ claim: ResultFileClaim,
131
+ patch: Partial<Omit<ResultFileClaimMeta, "version" | "id" | "originalFile" | "createdAt">>,
132
+ fsApi: ResultClaimFs = fs,
133
+ ): void {
134
+ const f = api(fsApi);
135
+ claim.meta = { ...claim.meta, ...patch };
136
+ const temporary = path.join(claim.dir, `.claim-${randomUUID()}.tmp`);
137
+ f.writeFileSync(temporary, `${JSON.stringify(claim.meta)}\n`, "utf-8");
138
+ f.renameSync(temporary, claim.metaPath);
139
+ }
140
+
141
+ export function removeResultClaim(claim: ResultFileClaim, fsApi: ResultClaimFs = fs): void {
142
+ const f = api(fsApi);
143
+ f.rmSync(claim.dir, { recursive: true, force: true });
144
+ try { f.rmdirSync(path.dirname(claim.dir)); } catch { /* another active claim keeps the shared directory */ }
145
+ }
146
+
147
+ export function recoverTerminalClaim(claim: ResultFileClaim, fsApi: ResultClaimFs = fs): "active" | "removed" {
148
+ if (claim.meta.state === "active") return "active";
149
+ removeResultClaim(claim, fsApi);
150
+ return "removed";
151
+ }