@gajae-code/coding-agent 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (610) hide show
  1. package/CHANGELOG.md +215 -8
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +10 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +73 -9
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +2 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/dynamic-theme-text.d.ts +9 -0
  87. package/dist/types/modes/components/gajae-pet-widget.d.ts +6 -28
  88. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  89. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  90. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  91. package/dist/types/modes/components/pet-capability.d.ts +7 -21
  92. package/dist/types/modes/components/settings-selector.d.ts +5 -0
  93. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  94. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  95. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  96. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  97. package/dist/types/modes/interactive-mode.d.ts +1 -0
  98. package/dist/types/modes/theme/defaults/index.d.ts +118 -0
  99. package/dist/types/modes/types.d.ts +1 -0
  100. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  101. package/dist/types/runtime-mcp/config.d.ts +9 -0
  102. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  103. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  104. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  105. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  106. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  107. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  108. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  109. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  110. package/dist/types/runtime-mcp/types.d.ts +102 -10
  111. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  112. package/dist/types/sdk/broker/broker.d.ts +3 -55
  113. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  114. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  115. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  116. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  117. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  118. package/dist/types/sdk/bus/config.d.ts +11 -0
  119. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  120. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  121. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  122. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  123. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  124. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  125. package/dist/types/sdk/bus/index.d.ts +85 -99
  126. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  127. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  128. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  129. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  130. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  131. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  132. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  133. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  135. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  136. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  137. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  138. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  139. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  140. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  141. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  142. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  143. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  144. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  145. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  146. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  147. package/dist/types/sdk/cli/rows.d.ts +2 -11
  148. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  149. package/dist/types/sdk/client/client.d.ts +97 -1
  150. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  151. package/dist/types/sdk/host/control/index.d.ts +1 -1
  152. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  153. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  154. package/dist/types/sdk/host/host.d.ts +15 -1
  155. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  156. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  157. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  158. package/dist/types/sdk/host/types.d.ts +1 -1
  159. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  160. package/dist/types/sdk/index.d.ts +2 -1
  161. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  162. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  163. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  164. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  165. package/dist/types/sdk/mcp/server.d.ts +9 -6
  166. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  167. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  168. package/dist/types/sdk/prompt-status.d.ts +10 -3
  169. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  170. package/dist/types/sdk/receipt-state.d.ts +17 -0
  171. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  172. package/dist/types/sdk/router/index.d.ts +1 -0
  173. package/dist/types/sdk/router/session-router.d.ts +152 -0
  174. package/dist/types/sdk/session-list.d.ts +22 -0
  175. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  176. package/dist/types/sdk/session.d.ts +34 -1
  177. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  178. package/dist/types/session/account-inventory.d.ts +86 -0
  179. package/dist/types/session/agent-session.d.ts +145 -6
  180. package/dist/types/session/agent-storage.d.ts +10 -0
  181. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  182. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  183. package/dist/types/session/messages.d.ts +1 -1
  184. package/dist/types/session/secure-token-file.d.ts +3 -0
  185. package/dist/types/session/session-manager.d.ts +47 -1
  186. package/dist/types/session/startup-auth-config.d.ts +44 -0
  187. package/dist/types/session/terminal-abort.d.ts +378 -0
  188. package/dist/types/session/yield-queue.d.ts +7 -0
  189. package/dist/types/session-import/claude.d.ts +26 -0
  190. package/dist/types/session-import/codex.d.ts +88 -0
  191. package/dist/types/session-import/command.d.ts +35 -0
  192. package/dist/types/session-import/detect.d.ts +22 -0
  193. package/dist/types/session-import/index.d.ts +7 -0
  194. package/dist/types/session-import/provider-codex.d.ts +22 -0
  195. package/dist/types/session-import/provider-service.d.ts +38 -0
  196. package/dist/types/session-import/redact.d.ts +23 -0
  197. package/dist/types/session-import/service.d.ts +47 -0
  198. package/dist/types/session-import/types.d.ts +120 -0
  199. package/dist/types/setup/paseo/check.d.ts +16 -0
  200. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  201. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  202. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  203. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  204. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  205. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  206. package/dist/types/setup/paseo/remove.d.ts +14 -0
  207. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  208. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  209. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  210. package/dist/types/skill-state/active-state.d.ts +5 -1
  211. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  212. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  213. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  214. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  215. package/dist/types/slash-commands/types.d.ts +7 -1
  216. package/dist/types/task/executor.d.ts +10 -0
  217. package/dist/types/task/index.d.ts +3 -1
  218. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  219. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  220. package/dist/types/tools/bash.d.ts +2 -6
  221. package/dist/types/tools/browser/attach.d.ts +11 -0
  222. package/dist/types/tools/browser/launch.d.ts +20 -0
  223. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  224. package/dist/types/tools/browser/render.d.ts +1 -0
  225. package/dist/types/tools/browser.d.ts +3 -1
  226. package/dist/types/tools/image-gen.d.ts +10 -2
  227. package/dist/types/tools/index.d.ts +8 -0
  228. package/dist/types/tools/monitor.d.ts +1 -1
  229. package/dist/types/tools/skill-discovery.d.ts +8 -1
  230. package/dist/types/tools/subagent.d.ts +2 -0
  231. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  232. package/dist/types/utils/herdr-pane.d.ts +76 -0
  233. package/package.json +19 -9
  234. package/scripts/build-binary.ts +2 -1
  235. package/scripts/build-sdk-package-smoke.ts +2 -13
  236. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  237. package/scripts/compile-args.ts +2 -0
  238. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  239. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  240. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  241. package/scripts/generate-tool-catalog.ts +6 -4
  242. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  243. package/src/async/job-manager.ts +352 -17
  244. package/src/async/support.ts +25 -0
  245. package/src/autoresearch/dashboard.ts +28 -13
  246. package/src/capability/index.ts +3 -1
  247. package/src/capability/mcp.ts +6 -0
  248. package/src/capability/types.ts +6 -0
  249. package/src/cli/accounts-cli.ts +395 -0
  250. package/src/cli/args.ts +23 -0
  251. package/src/cli/auth-broker-cli.ts +136 -66
  252. package/src/cli/auth-gateway-cli.ts +94 -70
  253. package/src/cli/config-cli.ts +27 -2
  254. package/src/cli/crash-cli.ts +78 -0
  255. package/src/cli/customize-doctor.ts +1605 -0
  256. package/src/cli/daemon-cli.ts +12 -3
  257. package/src/cli/fast-help.ts +1 -0
  258. package/src/cli/mcp-cli.ts +128 -60
  259. package/src/cli/notify-cli.ts +8 -14
  260. package/src/cli/root-flags.ts +8 -0
  261. package/src/cli/setup-cli.ts +145 -0
  262. package/src/cli/skills-cli.ts +38 -2
  263. package/src/cli/update-cli.ts +167 -12
  264. package/src/cli.ts +4 -0
  265. package/src/commands/accounts.ts +71 -0
  266. package/src/commands/crash.ts +35 -0
  267. package/src/commands/customize.ts +84 -0
  268. package/src/commands/daemon.ts +2 -2
  269. package/src/commands/deep-interview.ts +2 -0
  270. package/src/commands/launch.ts +1 -0
  271. package/src/commands/mcp.ts +12 -7
  272. package/src/commands/ralplan.ts +2 -0
  273. package/src/commands/sdk.ts +65 -32
  274. package/src/commands/setup.ts +5 -0
  275. package/src/commands/skills.ts +14 -4
  276. package/src/commands/ultragoal.ts +10 -1
  277. package/src/commands/update.ts +26 -15
  278. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  279. package/src/config/atomic-yaml-patch.ts +769 -29
  280. package/src/config/config-schema-version.ts +10 -0
  281. package/src/config/file-lock.ts +70 -1
  282. package/src/config/mcp-schema.json +52 -14
  283. package/src/config/model-profile-activation.ts +23 -2
  284. package/src/config/model-profiles.ts +88 -21
  285. package/src/config/model-registry.ts +147 -21
  286. package/src/config/models-config-schema.ts +1 -1
  287. package/src/config/settings-schema.ts +77 -7
  288. package/src/config/settings.ts +4038 -199
  289. package/src/config/skill-settings-defaults.ts +41 -3
  290. package/src/config/task-delegation.ts +87 -0
  291. package/src/config/update-channel.ts +23 -0
  292. package/src/coordinator-mcp/server.ts +377 -308
  293. package/src/crash/index-store.ts +723 -0
  294. package/src/crash/nudge.ts +89 -0
  295. package/src/crash/record-loader.ts +149 -0
  296. package/src/crash/report.ts +551 -0
  297. package/src/crash/sanitize.ts +129 -0
  298. package/src/cursor.ts +36 -10
  299. package/src/customization/import.ts +946 -0
  300. package/src/customization/index.ts +4 -0
  301. package/src/customization/inventory.ts +335 -0
  302. package/src/customization/mutations.ts +141 -0
  303. package/src/customization/types.ts +223 -0
  304. package/src/daemon/control-types.ts +8 -1
  305. package/src/daemon/operator-contract.ts +6 -6
  306. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
  307. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  308. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  309. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  310. package/src/discovery/claude-hooks.ts +38 -0
  311. package/src/discovery/claude.ts +142 -209
  312. package/src/discovery/codex-hooks.ts +38 -0
  313. package/src/discovery/codex.ts +132 -198
  314. package/src/discovery/helpers.ts +8 -1
  315. package/src/discovery/index.ts +2 -0
  316. package/src/discovery/mcp-compat.ts +234 -0
  317. package/src/discovery/mcp-json.ts +6 -0
  318. package/src/edit/renderer.ts +40 -0
  319. package/src/edit/streaming.ts +4 -2
  320. package/src/exec/bash-executor.ts +24 -20
  321. package/src/export/html/index.ts +51 -2
  322. package/src/extensibility/extensions/loader.ts +9 -1
  323. package/src/extensibility/extensions/runner.ts +17 -4
  324. package/src/extensibility/extensions/types.ts +31 -6
  325. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  326. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  327. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  328. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  329. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  330. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  331. package/src/extensibility/hooks/loader.ts +154 -10
  332. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  333. package/src/extensibility/skill-management.ts +368 -0
  334. package/src/extensibility/skills.ts +52 -16
  335. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  336. package/src/gjc-runtime/launch-worktree.ts +56 -2
  337. package/src/gjc-runtime/linux-proc.ts +7 -1
  338. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  339. package/src/gjc-runtime/session-state-lock.ts +992 -0
  340. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  341. package/src/gjc-runtime/state-writer.ts +39 -14
  342. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  343. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  344. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  345. package/src/gjc-runtime/workflow-settings.ts +596 -0
  346. package/src/goals/tools/goal-tool.ts +6 -1
  347. package/src/hooks/events.ts +291 -0
  348. package/src/hooks/native-skill-hook.ts +4 -0
  349. package/src/hooks/normalize.ts +369 -0
  350. package/src/hooks/skill-state.ts +165 -46
  351. package/src/internal-urls/docs-index.generated.ts +41 -32
  352. package/src/main.ts +36 -5
  353. package/src/migrate/mcp-mapper.ts +25 -2
  354. package/src/modes/acp/abort-scope.ts +29 -0
  355. package/src/modes/acp/acp-agent.ts +423 -98
  356. package/src/modes/components/assistant-message.ts +6 -1
  357. package/src/modes/components/chrome.ts +4 -3
  358. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  359. package/src/modes/components/customization/import-wizard.ts +372 -0
  360. package/src/modes/components/customization/index.ts +1 -0
  361. package/src/modes/components/dynamic-theme-text.ts +22 -0
  362. package/src/modes/components/gajae-pet-widget.ts +607 -193
  363. package/src/modes/components/irc-sidebar.ts +11 -0
  364. package/src/modes/components/iterm-pet-transport.ts +585 -0
  365. package/src/modes/components/model-selector.ts +1 -6
  366. package/src/modes/components/oauth-selector.ts +277 -86
  367. package/src/modes/components/pet-capability.ts +35 -31
  368. package/src/modes/components/settings-selector.ts +29 -5
  369. package/src/modes/components/status-line/gh.ts +45 -1
  370. package/src/modes/components/status-line/segments.ts +19 -4
  371. package/src/modes/components/tool-status-header.ts +13 -3
  372. package/src/modes/controllers/command-controller.ts +20 -8
  373. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  374. package/src/modes/controllers/input-controller.ts +10 -0
  375. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  376. package/src/modes/controllers/selector-controller.ts +238 -77
  377. package/src/modes/controllers/stt-controller.ts +3 -3
  378. package/src/modes/interactive-mode.ts +88 -16
  379. package/src/modes/print-mode.ts +35 -6
  380. package/src/modes/runtime-init.ts +21 -68
  381. package/src/modes/theme/defaults/index.ts +2 -0
  382. package/src/modes/theme/defaults/ouroboros.json +118 -0
  383. package/src/modes/types.ts +1 -0
  384. package/src/modes/utils/injected-user-submission.ts +25 -0
  385. package/src/modes/utils/terminal-bell.ts +15 -1
  386. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  387. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  388. package/src/prompts/agents/critic.md +1 -1
  389. package/src/prompts/system/rlm-research.md +1 -1
  390. package/src/prompts/system/system-prompt.md +0 -2
  391. package/src/prompts/tools/bash.md +7 -1
  392. package/src/prompts/tools/browser.md +1 -1
  393. package/src/prompts/tools/cron.md +6 -6
  394. package/src/prompts/tools/read.md +0 -2
  395. package/src/prompts/tools/skill-discovery.md +4 -2
  396. package/src/runtime-mcp/client.ts +427 -23
  397. package/src/runtime-mcp/config.ts +37 -5
  398. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  399. package/src/runtime-mcp/manager.ts +52 -2
  400. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  401. package/src/runtime-mcp/oauth-flow.ts +46 -1
  402. package/src/runtime-mcp/pool-key.ts +5 -1
  403. package/src/runtime-mcp/pool.test.ts +9 -0
  404. package/src/runtime-mcp/protocol.test.ts +353 -0
  405. package/src/runtime-mcp/protocol.ts +624 -0
  406. package/src/runtime-mcp/scope-config.ts +78 -0
  407. package/src/runtime-mcp/tool-bridge.ts +44 -10
  408. package/src/runtime-mcp/transports/http.ts +84 -13
  409. package/src/runtime-mcp/types.ts +123 -12
  410. package/src/sdk/acp/adapter.ts +328 -97
  411. package/src/sdk/broker/broker.ts +160 -741
  412. package/src/sdk/broker/ensure.ts +9 -1
  413. package/src/sdk/broker/lifecycle.ts +510 -116
  414. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  415. package/src/sdk/broker/process-incarnation.ts +19 -0
  416. package/src/sdk/broker/session-index.ts +516 -117
  417. package/src/sdk/broker/startup-failure.ts +28 -5
  418. package/src/sdk/broker/transport.ts +1 -13
  419. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  420. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  421. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  422. package/src/sdk/bus/config.ts +13 -0
  423. package/src/sdk/bus/conversation-store.ts +59 -171
  424. package/src/sdk/bus/daemon-paths.ts +20 -0
  425. package/src/sdk/bus/discord-conversation.ts +2 -0
  426. package/src/sdk/bus/discord-daemon.ts +524 -126
  427. package/src/sdk/bus/discord-live-provider.ts +206 -79
  428. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  429. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  430. package/src/sdk/bus/index.ts +2480 -456
  431. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  432. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  433. package/src/sdk/bus/managed-daemon.ts +2 -145
  434. package/src/sdk/bus/notification-service.ts +196 -0
  435. package/src/sdk/bus/operator-runtime.ts +1 -1
  436. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  437. package/src/sdk/bus/reconciliation-store.ts +553 -24
  438. package/src/sdk/bus/session-control.ts +49 -3
  439. package/src/sdk/bus/slack-conversation.ts +4 -0
  440. package/src/sdk/bus/slack-daemon.ts +733 -134
  441. package/src/sdk/bus/slack-live-provider.ts +65 -15
  442. package/src/sdk/bus/slack-provider.ts +4 -0
  443. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  444. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  445. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  446. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  447. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  448. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  449. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  450. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  451. package/src/sdk/bus/telegram-reference.ts +1 -226
  452. package/src/sdk/bus/telegram-setup.ts +2 -2
  453. package/src/sdk/bus/threaded-render.ts +1 -0
  454. package/src/sdk/bus/topic-registry.ts +279 -146
  455. package/src/sdk/cli/rows.ts +2 -35
  456. package/src/sdk/cli/session-cli.ts +776 -700
  457. package/src/sdk/client/client.ts +892 -1
  458. package/src/sdk/host/control/dispatch.ts +93 -58
  459. package/src/sdk/host/control/index.ts +1 -1
  460. package/src/sdk/host/control/operations.ts +18 -10
  461. package/src/sdk/host/control/runtime-gate.ts +40 -0
  462. package/src/sdk/host/host.ts +63 -12
  463. package/src/sdk/host/query/handlers.ts +66 -7
  464. package/src/sdk/host/reverse-leases.ts +2 -2
  465. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  466. package/src/sdk/host/session-runtime.test.ts +2787 -34
  467. package/src/sdk/host/session-runtime.ts +1489 -101
  468. package/src/sdk/host/surface-policy.ts +12 -8
  469. package/src/sdk/host/types.ts +1 -1
  470. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  471. package/src/sdk/host/websocket-transport.ts +85 -50
  472. package/src/sdk/index.ts +2 -1
  473. package/src/sdk/lifecycle/broker-client.ts +53 -0
  474. package/src/sdk/lifecycle/client.ts +234 -0
  475. package/src/sdk/lifecycle/index.ts +3 -0
  476. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  477. package/src/sdk/lifecycle/service.ts +720 -0
  478. package/src/sdk/mcp/server.ts +226 -188
  479. package/src/sdk/prompt-deadline-lease.ts +55 -0
  480. package/src/sdk/prompt-deadline-manager.ts +148 -0
  481. package/src/sdk/prompt-status.ts +11 -3
  482. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  483. package/src/sdk/protocol/operation-registry.ts +71 -8
  484. package/src/sdk/receipt-state.ts +43 -0
  485. package/src/sdk/reconciliation-extensions.ts +14 -0
  486. package/src/sdk/router/index.ts +1 -0
  487. package/src/sdk/router/session-router.ts +1658 -0
  488. package/src/sdk/session-list.ts +93 -0
  489. package/src/sdk/session-reconnect.ts +45 -1
  490. package/src/sdk/session.ts +716 -120
  491. package/src/sdk/transport/serve-cli.ts +18 -34
  492. package/src/session/account-inventory.ts +603 -0
  493. package/src/session/agent-session.ts +2873 -559
  494. package/src/session/agent-storage.ts +35 -0
  495. package/src/session/blob-store.ts +32 -1
  496. package/src/session/coordinator-tool-label.ts +40 -0
  497. package/src/session/internal/managed-session-scope.ts +4 -0
  498. package/src/session/internal/managed-session-storage.ts +199 -31
  499. package/src/session/messages.ts +1 -1
  500. package/src/session/secure-token-file.ts +80 -0
  501. package/src/session/session-manager.ts +757 -57
  502. package/src/session/session-storage.ts +25 -16
  503. package/src/session/startup-auth-config.ts +297 -0
  504. package/src/session/terminal-abort.ts +1253 -0
  505. package/src/session/yield-queue.ts +57 -15
  506. package/src/session-import/claude.ts +334 -0
  507. package/src/session-import/codex.ts +1033 -0
  508. package/src/session-import/command.ts +77 -0
  509. package/src/session-import/detect.ts +133 -0
  510. package/src/session-import/index.ts +7 -0
  511. package/src/session-import/provider-codex.ts +327 -0
  512. package/src/session-import/provider-service.ts +457 -0
  513. package/src/session-import/redact.ts +128 -0
  514. package/src/session-import/service.ts +1264 -0
  515. package/src/session-import/types.ts +148 -0
  516. package/src/setup/paseo/check.ts +213 -0
  517. package/src/setup/paseo/install-saga.ts +293 -0
  518. package/src/setup/paseo/json-publisher.ts +263 -0
  519. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  520. package/src/setup/paseo/paseo-ownership.ts +224 -0
  521. package/src/setup/paseo/paseo-setup.ts +253 -0
  522. package/src/setup/paseo/provider-config.ts +139 -0
  523. package/src/setup/paseo/remove.ts +183 -0
  524. package/src/setup/paseo/result-types.ts +79 -0
  525. package/src/setup/paseo/setup-deps.ts +175 -0
  526. package/src/setup/paseo/skills-bridge.ts +260 -0
  527. package/src/skill-state/active-state.ts +21 -11
  528. package/src/slash-commands/acp-builtins.ts +9 -1
  529. package/src/slash-commands/builtin-registry.ts +117 -19
  530. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  531. package/src/slash-commands/helpers/mcp.ts +9 -3
  532. package/src/slash-commands/helpers/usage-report.ts +61 -56
  533. package/src/slash-commands/types.ts +7 -2
  534. package/src/task/executor.ts +30 -11
  535. package/src/task/index.ts +66 -7
  536. package/src/task/render.ts +23 -9
  537. package/src/tools/ask-answer-registry.ts +23 -7
  538. package/src/tools/ask.ts +8 -4
  539. package/src/tools/bash-sleep-advisory.ts +115 -0
  540. package/src/tools/bash.ts +98 -18
  541. package/src/tools/browser/attach.ts +71 -3
  542. package/src/tools/browser/launch.ts +86 -13
  543. package/src/tools/browser/profile-discovery.ts +57 -7
  544. package/src/tools/browser/render.ts +8 -1
  545. package/src/tools/browser.ts +130 -21
  546. package/src/tools/computer.enforcement.test.ts +1 -1
  547. package/src/tools/computer.ts +3 -2
  548. package/src/tools/cron.ts +12 -1
  549. package/src/tools/image-gen.ts +349 -142
  550. package/src/tools/index.ts +8 -0
  551. package/src/tools/job.ts +33 -6
  552. package/src/tools/monitor.ts +41 -2
  553. package/src/tools/skill-discovery.ts +21 -29
  554. package/src/tools/subagent.ts +142 -24
  555. package/src/tools/todo-contract.ts +55 -3
  556. package/src/tools/todo-write.ts +4 -3
  557. package/src/tools/tool-catalog.generated.ts +6 -6
  558. package/src/tools/ultragoal-ask-guard.ts +10 -3
  559. package/src/utils/herdr-pane.ts +345 -0
  560. package/src/utils/title-generator.ts +2 -0
  561. package/src/web/scrapers/index.ts +6 -4
  562. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  563. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  564. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  565. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  566. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  567. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  568. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  569. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  570. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  571. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  572. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  573. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  574. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  575. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  576. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  577. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  578. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  579. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  580. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  581. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  582. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  583. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  584. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  585. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  586. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  587. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  588. package/dist/types/session/auth-broker-config.d.ts +0 -13
  589. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  590. package/dist/types/skills/skills.test.d.ts +0 -1
  591. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  592. package/dist/types/task/token-log.test.d.ts +0 -1
  593. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  594. package/dist/types/tools/descriptors.test.d.ts +0 -1
  595. package/scripts/g005-daemon-smoke.ts +0 -180
  596. package/scripts/g011-daemon-path-smoke.ts +0 -153
  597. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  598. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  599. package/src/sdk/bus/telegram-cli.ts +0 -212
  600. package/src/sdk/elevation/allowlist.ts +0 -54
  601. package/src/sdk/elevation/audit.ts +0 -208
  602. package/src/sdk/elevation/capability.ts +0 -41
  603. package/src/sdk/elevation/digest.ts +0 -129
  604. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  605. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  606. package/src/sdk/elevation/owner.ts +0 -109
  607. package/src/session/auth-broker-config.ts +0 -109
  608. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  609. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  610. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -6,23 +6,37 @@
6
6
  *
7
7
  * Safe session ids only: /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
8
8
  * Atomic write: temp + fsync + rename + 0600. Corrupt → quarantine + empty.
9
- * Non-terminal skill records settle to failed/process_restart on bootstrap; prompt
10
- * records finalize their pending outcome or a prompt_failed fallback.
9
+ * Non-terminal records with a pending outcome finalize that exact claim on bootstrap.
10
+ * Outcome-less skills retain the existing failed/process_restart settlement.
11
11
  */
12
+ import { createHash, randomUUID } from "node:crypto";
12
13
  import * as fs from "node:fs/promises";
13
14
  import * as path from "node:path";
14
15
  import type { PromptReconciliationStatus, SdkPromptTerminalOutcome } from "../prompt-status";
16
+ import type { ReceiptState } from "../receipt-state";
15
17
  import { TURN_RESULT_CONTENT_MAX_BYTES, type TurnResultContent } from "../turn-result";
16
-
17
18
  import type { PromptCorrelation } from "./prompt-reconciliation";
18
19
 
19
- export const RECONCILIATION_STORE_VERSION = 1;
20
+ export const RECONCILIATION_STORE_VERSION = 2;
21
+ export const RECONCILIATION_STORE_VERSION_V1 = 1;
20
22
  export const RECONCILIATION_SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
21
23
  export const RECONCILIATION_DIR_NAME = ".sdk-reconciliation";
24
+ // Replay-authority hashes are canonical SHA-256 hex digests; anything else in a
25
+ // durable document can never match a real retry and would make the reserved key
26
+ // invisible (and reusable for an unrelated later abort), so reload rejects it.
27
+ const SHA256_RE = /^[0-9a-f]{64}$/;
28
+
29
+ export function resolveReconciliationSessionFile(
30
+ sessionFile: string | undefined | null,
31
+ stateRoot: string,
32
+ sessionId: string,
33
+ ): string {
34
+ return sessionFile ?? path.join(stateRoot, `${sessionId}.jsonl`);
35
+ }
22
36
 
23
- export type ReconciliationKind = "prompt" | "skill";
37
+ export type ReconciliationKind = "prompt" | "skill" | "terminal";
24
38
 
25
- export interface DurableReconciliationRecord extends PromptCorrelation {
39
+ export interface DurableExecutionReconciliationRecord extends PromptCorrelation {
26
40
  kind: ReconciliationKind;
27
41
  clientRef?: string;
28
42
  status: PromptReconciliationStatus;
@@ -31,16 +45,105 @@ export interface DurableReconciliationRecord extends PromptCorrelation {
31
45
  startedAt?: number;
32
46
  terminalAt?: number;
33
47
  outcome?: SdkPromptTerminalOutcome;
48
+ receiptState?: Exclude<ReceiptState, "absent">;
34
49
  pendingOutcome?: SdkPromptTerminalOutcome;
50
+ pendingReceiptState?: Extract<ReceiptState, "present" | "missing">;
35
51
  /** Skill-only safe token; never skill args bodies. */
36
52
  skillName?: string;
37
53
  content?: TurnResultContent;
38
54
  }
39
55
 
56
+ /**
57
+ * Durable terminal scope record (approved abort-SDK plan, v2 document).
58
+ * Bounded origin/fence and owned-settlement fields only; no prompt text and no
59
+ * suppressed/deferred receipts for left-running turn work.
60
+ */
61
+ export interface DurableTerminalScopeRecord {
62
+ selection: "turn" | "owned";
63
+ /** SHA-256 of the bounded idempotency key; the raw key is never persisted. */
64
+ idempotencyKeyHash?: string;
65
+ /** SHA-256 of the canonicalized normalized input; raw input is never persisted. */
66
+ idempotencyInputHash?: string;
67
+ turnDisposition:
68
+ | "pending"
69
+ | "stopped"
70
+ | "uncertain"
71
+ | "no_effect"
72
+ | "no_effect_reserved"
73
+ | "no_effect_marker_failure";
74
+ /** Whether the correlated agent_end event was published (AC 19). */
75
+ terminalPublished?: boolean;
76
+ ownedWorkDisposition: "not_requested" | "left_running" | "stopped" | "uncertain";
77
+ automaticDeliveryDisposition: "enabled" | "none";
78
+ resumeOnOwnedCompletion: boolean;
79
+ turnContinuationFence: {
80
+ state: "retained" | "released";
81
+ abortedAttemptEpoch: number;
82
+ blockedContinuationIds: string[];
83
+ predecessorTombstones: string[];
84
+ ownedCompletionPolicy: "enabled" | "disabled";
85
+ };
86
+ ownedDeliverySettlements?: Array<{
87
+ keyHash: string;
88
+ entryIdHash: string;
89
+ status: "settled" | "absent" | "uncertain";
90
+ observedAt: number;
91
+ }>;
92
+ responseState: "pending" | "sent" | "delivered" | "failed";
93
+ responsePayloadHash: string;
94
+ /** Hash of the replay-shaped public result a same-key retry delivers, when
95
+ * it differs from the original response (the replay appends metadata). */
96
+ replayPayloadHash?: string;
97
+ acceptedAt: number;
98
+ terminalAt?: number;
99
+ }
100
+
101
+ /** Compact evicted-key tombstone with enough disposition metadata to
102
+ * reconstruct the original terminal replay result (review thread P2). */
103
+ export interface EvictedTerminalKeyEntry {
104
+ keyHash: string;
105
+ inputHash: string;
106
+ turnDisposition?: "stopped" | "uncertain" | "no_effect" | "no_effect_reserved" | "no_effect_marker_failure";
107
+ ownedWorkDisposition?: "not_requested" | "left_running" | "stopped" | "uncertain";
108
+ responseState?: "pending" | "sent" | "delivered" | "failed";
109
+ responsePayloadHash?: string;
110
+ replayPayloadHash?: string;
111
+ terminalPublished?: boolean;
112
+ }
113
+ export interface DurableSteerReconciliationRecord {
114
+ kind: "steer";
115
+ clientRef: string;
116
+ textDigest: string;
117
+ createdAt: number;
118
+ status: "dispatching" | "accepted" | "rejected" | "uncertain";
119
+ settledAt?: number;
120
+ error?: { code: string; message: string };
121
+ commandId: string;
122
+ turnId: string;
123
+ acceptedAt: number;
124
+ startedAt?: never;
125
+ terminalAt?: never;
126
+ outcome?: never;
127
+ receiptState?: never;
128
+ pendingOutcome?: never;
129
+ pendingReceiptState?: never;
130
+ skillName?: never;
131
+ }
132
+
133
+ export type DurableReconciliationRecord = DurableExecutionReconciliationRecord | DurableSteerReconciliationRecord;
134
+
40
135
  export interface ReconciliationStoreDocument {
41
136
  version: typeof RECONCILIATION_STORE_VERSION;
42
137
  sessionId: string;
43
138
  records: DurableReconciliationRecord[];
139
+ terminalScopes?: DurableTerminalScopeRecord[];
140
+ /**
141
+ * Compact key tombstones for completed terminal rows evicted by the
142
+ * retention cap: the key hash is retained durably so a same-key retry
143
+ * after dispatch-cache expiry/restart still replays instead of aborting an
144
+ * unrelated later prompt (review thread P2).
145
+ */
146
+ evictedTerminalKeys?: EvictedTerminalKeyEntry[];
44
147
  }
45
148
 
46
149
  export interface ReconciliationStoreFs {
@@ -63,7 +166,7 @@ const nodeFs: ReconciliationStoreFs = {
63
166
  mkdir: fs.mkdir,
64
167
  readFile: fs.readFile,
65
168
  writeFile: fs.writeFile,
66
- rename: fs.rename,
169
+ rename: (from, to) => fs.rename(from, to),
67
170
  unlink: fs.unlink,
68
171
  open: fs.open as ReconciliationStoreFs["open"],
69
172
  };
@@ -88,18 +191,54 @@ function isRecord(value: unknown): value is Record<string, unknown> {
88
191
  /** Record-level validation: JSON-valid but malformed entries must be quarantined too. */
89
192
  function isValidRecord(value: unknown): boolean {
90
193
  if (!isRecord(value)) return false;
91
- const { kind, commandId, turnId, status, acceptedAt, terminalAt, outcome, pendingOutcome } = value;
194
+ const { kind } = value;
195
+ if (kind === "steer") {
196
+ if (typeof value.clientRef !== "string" || !value.clientRef) return false;
197
+ if (typeof value.textDigest !== "string" || !/^[0-9a-f]{64}$/.test(value.textDigest)) return false;
198
+ if (typeof value.createdAt !== "number" || !Number.isFinite(value.createdAt)) return false;
199
+ if (typeof value.commandId !== "string" || !value.commandId || typeof value.turnId !== "string" || !value.turnId)
200
+ return false;
201
+ if (typeof value.acceptedAt !== "number" || !Number.isFinite(value.acceptedAt)) return false;
202
+ if (!["dispatching", "accepted", "rejected", "uncertain"].includes(value.status as string)) return false;
203
+ const settled = value.status !== "dispatching";
204
+ if (settled !== (typeof value.settledAt === "number" && Number.isFinite(value.settledAt))) return false;
205
+ if ((value.status === "rejected" || value.status === "uncertain") !== (value.error !== undefined)) return false;
206
+ if (
207
+ value.error !== undefined &&
208
+ (!isRecord(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string")
209
+ )
210
+ return false;
211
+ return value.receiptState === undefined && value.outcome === undefined;
212
+ }
92
213
  if (kind !== "prompt" && kind !== "skill") return false;
214
+ const {
215
+ commandId,
216
+ turnId,
217
+ status,
218
+ acceptedAt,
219
+ terminalAt,
220
+ outcome,
221
+ pendingOutcome,
222
+ receiptState,
223
+ pendingReceiptState,
224
+ } = value;
93
225
  if (typeof commandId !== "string" || !commandId || typeof turnId !== "string" || !turnId) return false;
94
226
  if (status !== "accepted" && status !== "in_flight" && status !== "terminal_ok" && status !== "failed") return false;
95
227
  if (typeof acceptedAt !== "number" || !Number.isFinite(acceptedAt)) return false;
96
228
  if (terminalAt !== undefined && (typeof terminalAt !== "number" || !Number.isFinite(terminalAt))) return false;
97
- // Durable invariants: only prompts carry a pending claim, a finalized record has no
98
- // pending claim left, and terminal/active status must agree with `terminalAt`.
99
- if (pendingOutcome !== undefined && kind !== "prompt") return false;
229
+ if (receiptState !== undefined && !["present", "missing", "unknown"].includes(receiptState as string)) return false;
230
+ if (pendingReceiptState !== undefined && pendingReceiptState !== "present" && pendingReceiptState !== "missing")
231
+ return false;
232
+ // Durable invariants: only prompts carry a pending receipt claim, a finalized
233
+ // record has no pending claim left, and terminal/active status must agree with
234
+ // `terminalAt`. Prompts and skills may both carry a pending outcome claim.
235
+ if (pendingReceiptState !== undefined && kind !== "prompt") return false;
236
+ if (pendingReceiptState !== undefined && pendingOutcome === undefined) return false;
100
237
  if (pendingOutcome !== undefined && terminalAt !== undefined) return false;
101
238
  const isTerminalStatus = status === "terminal_ok" || status === "failed";
102
239
  if (isTerminalStatus !== (terminalAt !== undefined)) return false;
240
+ if (!isTerminalStatus && receiptState !== undefined) return false;
241
+ if (isTerminalStatus && pendingReceiptState !== undefined) return false;
103
242
  if (outcome !== undefined && !isTerminalStatus) return false;
104
243
  if (
105
244
  outcome !== undefined &&
@@ -111,10 +250,6 @@ function isValidRecord(value: unknown): boolean {
111
250
  return false;
112
251
  if (value.clientRef !== undefined && typeof value.clientRef !== "string") return false;
113
252
  if (value.skillName !== undefined && typeof value.skillName !== "string") return false;
114
- if (value.error !== undefined) {
115
- if (!isRecord(value.error)) return false;
116
- if (typeof value.error.code !== "string" || typeof value.error.message !== "string") return false;
117
- }
118
253
  if (value.content !== undefined) {
119
254
  if (
120
255
  !isRecord(value.content) ||
@@ -128,6 +263,10 @@ function isValidRecord(value: unknown): boolean {
128
263
  )
129
264
  return false;
130
265
  }
266
+ if (value.error !== undefined) {
267
+ if (!isRecord(value.error)) return false;
268
+ if (typeof value.error.code !== "string" || typeof value.error.message !== "string") return false;
269
+ }
131
270
  return [outcome, pendingOutcome].every(candidate => {
132
271
  if (candidate === undefined) return true;
133
272
  if (!isRecord(candidate)) return false;
@@ -145,29 +284,274 @@ function isValidRecord(value: unknown): boolean {
145
284
  );
146
285
  });
147
286
  }
287
+ /** Terminal scope validation: bounded origin/fence/settlement fields only. */
288
+ function isValidTerminalScope(value: unknown): boolean {
289
+ if (!isRecord(value)) return false;
290
+ const {
291
+ selection,
292
+ turnDisposition,
293
+ ownedWorkDisposition,
294
+ automaticDeliveryDisposition,
295
+ resumeOnOwnedCompletion,
296
+ turnContinuationFence,
297
+ ownedDeliverySettlements,
298
+ responseState,
299
+ responsePayloadHash,
300
+ replayPayloadHash,
301
+ acceptedAt,
302
+ terminalAt,
303
+ idempotencyKeyHash,
304
+ idempotencyInputHash,
305
+ terminalPublished,
306
+ } = value;
307
+ if (selection !== "turn" && selection !== "owned") return false;
308
+ if (
309
+ turnDisposition !== "pending" &&
310
+ turnDisposition !== "stopped" &&
311
+ turnDisposition !== "uncertain" &&
312
+ turnDisposition !== "no_effect" &&
313
+ turnDisposition !== "no_effect_reserved" &&
314
+ turnDisposition !== "no_effect_marker_failure"
315
+ )
316
+ return false;
317
+ if (
318
+ ownedWorkDisposition !== "not_requested" &&
319
+ ownedWorkDisposition !== "left_running" &&
320
+ ownedWorkDisposition !== "stopped" &&
321
+ ownedWorkDisposition !== "uncertain"
322
+ )
323
+ return false;
324
+ if (automaticDeliveryDisposition !== "enabled" && automaticDeliveryDisposition !== "none") return false;
325
+ if (typeof resumeOnOwnedCompletion !== "boolean") return false;
326
+ if (!isRecord(turnContinuationFence)) return false;
327
+ const { state, abortedAttemptEpoch, blockedContinuationIds, predecessorTombstones, ownedCompletionPolicy } =
328
+ turnContinuationFence;
329
+ if (state !== "retained" && state !== "released") return false;
330
+ if (typeof abortedAttemptEpoch !== "number" || !Number.isFinite(abortedAttemptEpoch)) return false;
331
+ if (!Array.isArray(blockedContinuationIds) || !blockedContinuationIds.every(id => typeof id === "string"))
332
+ return false;
333
+ if (!Array.isArray(predecessorTombstones) || !predecessorTombstones.every(id => typeof id === "string"))
334
+ return false;
335
+ if (ownedCompletionPolicy !== "enabled" && ownedCompletionPolicy !== "disabled") return false;
336
+ if (ownedDeliverySettlements !== undefined) {
337
+ if (!Array.isArray(ownedDeliverySettlements) || ownedDeliverySettlements.length > 256) return false;
338
+ for (const settlement of ownedDeliverySettlements) {
339
+ if (!isRecord(settlement)) return false;
340
+ if (typeof settlement.keyHash !== "string" || !settlement.keyHash) return false;
341
+ if (typeof settlement.entryIdHash !== "string" || !settlement.entryIdHash) return false;
342
+ if (settlement.status !== "settled" && settlement.status !== "absent" && settlement.status !== "uncertain")
343
+ return false;
344
+ if (typeof settlement.observedAt !== "number" || !Number.isFinite(settlement.observedAt)) return false;
345
+ }
346
+ }
347
+ if (
348
+ responseState !== "pending" &&
349
+ responseState !== "sent" &&
350
+ responseState !== "delivered" &&
351
+ responseState !== "failed"
352
+ )
353
+ return false;
354
+ if (typeof responsePayloadHash !== "string" || !responsePayloadHash) return false;
355
+ if (replayPayloadHash !== undefined && (typeof replayPayloadHash !== "string" || !replayPayloadHash)) return false;
356
+ if (typeof idempotencyKeyHash !== "string" || !SHA256_RE.test(idempotencyKeyHash)) return false;
357
+ if (typeof idempotencyInputHash !== "string" || !SHA256_RE.test(idempotencyInputHash)) return false;
358
+ if (terminalPublished !== undefined && typeof terminalPublished !== "boolean") return false;
359
+ if (typeof acceptedAt !== "number" || !Number.isFinite(acceptedAt)) return false;
360
+ if (terminalAt !== undefined && (typeof terminalAt !== "number" || !Number.isFinite(terminalAt))) return false;
361
+ // An incomplete (pending) scope cannot already be terminal.
362
+ if (turnDisposition === "pending" && terminalAt !== undefined) return false;
363
+ return true;
364
+ }
365
+
366
+ /** Evicted tombstones are replay authority, so every optional replay field
367
+ * is validated before a durable document is trusted. */
368
+ function isValidEvictedTerminalKeyEntry(value: unknown): value is EvictedTerminalKeyEntry {
369
+ if (!isRecord(value)) return false;
370
+ if (typeof value.keyHash !== "string" || !SHA256_RE.test(value.keyHash)) return false;
371
+ if (typeof value.inputHash !== "string" || !SHA256_RE.test(value.inputHash)) return false;
372
+ if (
373
+ value.turnDisposition !== undefined &&
374
+ value.turnDisposition !== "stopped" &&
375
+ value.turnDisposition !== "uncertain" &&
376
+ value.turnDisposition !== "no_effect" &&
377
+ value.turnDisposition !== "no_effect_reserved" &&
378
+ value.turnDisposition !== "no_effect_marker_failure"
379
+ )
380
+ return false;
381
+ if (
382
+ value.ownedWorkDisposition !== undefined &&
383
+ value.ownedWorkDisposition !== "not_requested" &&
384
+ value.ownedWorkDisposition !== "left_running" &&
385
+ value.ownedWorkDisposition !== "stopped" &&
386
+ value.ownedWorkDisposition !== "uncertain"
387
+ )
388
+ return false;
389
+ if (
390
+ value.responseState !== undefined &&
391
+ value.responseState !== "pending" &&
392
+ value.responseState !== "sent" &&
393
+ value.responseState !== "delivered" &&
394
+ value.responseState !== "failed"
395
+ )
396
+ return false;
397
+ if (
398
+ value.responsePayloadHash !== undefined &&
399
+ (typeof value.responsePayloadHash !== "string" || !value.responsePayloadHash)
400
+ )
401
+ return false;
402
+ if (
403
+ value.replayPayloadHash !== undefined &&
404
+ (typeof value.replayPayloadHash !== "string" || !value.replayPayloadHash)
405
+ )
406
+ return false;
407
+ if (value.terminalPublished !== undefined && typeof value.terminalPublished !== "boolean") return false;
408
+ return true;
409
+ }
148
410
 
149
411
  function parseDocument(raw: string, expectedSessionId: string): ReconciliationStoreDocument {
150
412
  const value = JSON.parse(raw) as unknown;
151
- if (!isRecord(value) || value.version !== RECONCILIATION_STORE_VERSION)
413
+ if (
414
+ !isRecord(value) ||
415
+ (value.version !== RECONCILIATION_STORE_VERSION && value.version !== RECONCILIATION_STORE_VERSION_V1)
416
+ )
152
417
  throw new Error("invalid reconciliation store version");
153
418
  if (value.sessionId !== expectedSessionId) throw new Error("session id mismatch");
154
419
  if (!Array.isArray(value.records)) throw new Error("invalid records");
155
420
  if (!value.records.every(isValidRecord)) throw new Error("invalid reconciliation record");
156
- return value as unknown as ReconciliationStoreDocument;
421
+ // v1 documents migrate to v2 (records only; terminalScopes added later).
422
+ if (value.version === RECONCILIATION_STORE_VERSION_V1)
423
+ return {
424
+ version: RECONCILIATION_STORE_VERSION,
425
+ sessionId: expectedSessionId,
426
+ records: value.records as DurableReconciliationRecord[],
427
+ };
428
+ const terminalScopes = value.terminalScopes;
429
+ if (terminalScopes !== undefined) {
430
+ if (!Array.isArray(terminalScopes)) throw new Error("invalid terminal scopes");
431
+ if (!terminalScopes.every(isValidTerminalScope)) throw new Error("invalid terminal scope");
432
+ }
433
+ const evictedTerminalKeys = value.evictedTerminalKeys;
434
+ if (
435
+ evictedTerminalKeys !== undefined &&
436
+ (!Array.isArray(evictedTerminalKeys) || !evictedTerminalKeys.every(isValidEvictedTerminalKeyEntry))
437
+ ) {
438
+ throw new Error("invalid evicted terminal keys");
439
+ }
440
+ return {
441
+ version: RECONCILIATION_STORE_VERSION,
442
+ sessionId: expectedSessionId,
443
+ records: value.records as DurableReconciliationRecord[],
444
+ ...(terminalScopes !== undefined ? { terminalScopes: terminalScopes as DurableTerminalScopeRecord[] } : {}),
445
+ ...(evictedTerminalKeys !== undefined
446
+ ? { evictedTerminalKeys: evictedTerminalKeys as EvictedTerminalKeyEntry[] }
447
+ : {}),
448
+ };
157
449
  }
158
450
 
159
451
  /**
160
452
  * Settle non-terminal durable records after process death.
161
- * Prompt records preserve a durable pending outcome; skills retain the existing
162
- * reconciliation-incomplete result.
453
+ * Any record with a durable pending outcome preserves that exact terminal claim;
454
+ * outcome-less skills retain the existing reconciliation-incomplete result.
163
455
  */
456
+ /**
457
+ * Settle incomplete terminal scopes (turnDisposition "pending") to safe
458
+ * uncertainty after process death. A terminal scope that never finalized its
459
+ * semantic CAS replays as uncertainty, never as success.
460
+ */
461
+ export function settleTerminalScopeRestart(
462
+ scopes: DurableTerminalScopeRecord[],
463
+ now: number,
464
+ ): DurableTerminalScopeRecord[] {
465
+ return scopes.map(scope => {
466
+ // An abandoned no_effect_reserved reservation (the process exited or the
467
+ // finalize failed after the reservation was written) becomes a completed
468
+ // no_effect so normal retention can evict it: reserved rows are otherwise
469
+ // permanently non-evictable and same-key replay never finalizes them,
470
+ // so repeated crashes with unique idle-abort keys grow the reconciliation
471
+ // document without bound (review thread P2).
472
+ if (scope.turnDisposition === "no_effect_reserved") {
473
+ // The reservation's only deliverable after restart is the
474
+ // metadata-bearing no_active_turn replay; store the replay-shaped
475
+ // payload hash so a written replay can advance the row instead of
476
+ // staying durably pending (review thread P2). The stored
477
+ // responsePayloadHash stays the ORIGINAL placeholder: the delivered
478
+ // replay envelope embeds it, so the replay hash computed from that
479
+ // envelope is the hash the delivery observer will actually compare —
480
+ // replacing the field with the replay hash would make the delivered
481
+ // envelope embed a DIFFERENT value and never advance the row
482
+ // (review thread P2).
483
+ const replayResult = {
484
+ ok: true,
485
+ selection: scope.selection,
486
+ turn: "no_active_turn",
487
+ terminal: "terminal_no_effect",
488
+ replay: {
489
+ responseState: "pending",
490
+ responsePayloadHash: scope.responsePayloadHash,
491
+ terminalPublished: scope.terminalPublished === true,
492
+ },
493
+ };
494
+ const replayPayloadHash = createHash("sha256").update(JSON.stringify(replayResult)).digest("hex");
495
+ return {
496
+ ...scope,
497
+ turnDisposition: "no_effect" as const,
498
+ replayPayloadHash,
499
+ terminalAt: scope.terminalAt ?? now,
500
+ };
501
+ }
502
+ if (scope.turnDisposition !== "pending" || scope.terminalAt !== undefined) return scope;
503
+ // A restart-replay is the ONLY response a settled pending row can ever
504
+ // deliver (the process died before the original response was written), so
505
+ // the stored payload hash must describe the replay-shaped public result —
506
+ // the replay appends metadata (reason + replay envelope) that the delivery
507
+ // hash check would otherwise reject forever, leaving the row durably
508
+ // pending (review thread P2).
509
+ const replayResult = {
510
+ ok: true,
511
+ selection: scope.selection,
512
+ turn: "uncertain",
513
+ ownedWork: scope.selection === "turn" ? "left_running" : "uncertain",
514
+ automaticDelivery: scope.selection === "turn" ? "enabled" : "none",
515
+ resumeOnOwnedCompletion: scope.selection === "turn",
516
+ reason: "replay_uncertain",
517
+ replay: {
518
+ responseState: "pending",
519
+ responsePayloadHash: scope.responsePayloadHash,
520
+ terminalPublished: scope.terminalPublished === true,
521
+ },
522
+ };
523
+ const replayPayloadHash = createHash("sha256").update(JSON.stringify(replayResult)).digest("hex");
524
+ return {
525
+ ...scope,
526
+ turnDisposition: "uncertain",
527
+ ownedWorkDisposition: scope.ownedWorkDisposition === "not_requested" ? "not_requested" : "uncertain",
528
+ // responsePayloadHash stays the ORIGINAL hash: the delivered replay
529
+ // envelope embeds it, so replayPayloadHash (computed from that
530
+ // envelope) is the hash the delivery observer compares — replacing
531
+ // the field with the replay hash would embed a different value and
532
+ // keep the row durably pending (review thread P2).
533
+ responsePayloadHash: scope.responsePayloadHash,
534
+ replayPayloadHash,
535
+ terminalAt: now,
536
+ };
537
+ });
538
+ }
164
539
  export function settleProcessRestart(
165
540
  records: DurableReconciliationRecord[],
166
541
  now: number,
167
542
  ): DurableReconciliationRecord[] {
168
543
  return records.map(record => {
544
+ if (record.kind === "steer") {
545
+ if (record.status !== "dispatching") return record;
546
+ return {
547
+ ...record,
548
+ status: "uncertain",
549
+ settledAt: now,
550
+ error: { code: "process_restart_uncertain", message: "Steer delivery is uncertain after process restart." },
551
+ };
552
+ }
169
553
  if (record.terminalAt !== undefined) return record;
170
- if (record.kind === "prompt") {
554
+ if (record.pendingOutcome !== undefined || record.kind === "prompt") {
171
555
  const outcome: SdkPromptTerminalOutcome = record.pendingOutcome ?? {
172
556
  kind: "failed",
173
557
  code: "prompt_failed",
@@ -179,7 +563,9 @@ export function settleProcessRestart(
179
563
  status: outcome.kind === "stopped" ? "terminal_ok" : "failed",
180
564
  terminalAt: now,
181
565
  outcome,
566
+ receiptState: record.pendingReceiptState ?? (outcome.kind === "stopped" ? "missing" : "unknown"),
182
567
  pendingOutcome: undefined,
568
+ pendingReceiptState: undefined,
183
569
  ...(outcome.kind === "failed" ? { error: { code: outcome.code, message: outcome.message } } : {}),
184
570
  };
185
571
  }
@@ -188,6 +574,7 @@ export function settleProcessRestart(
188
574
  status: "failed",
189
575
  terminalAt: now,
190
576
  error: { code: "process_restart", message: "Reconciliation incomplete after process restart." },
577
+ receiptState: "unknown",
191
578
  };
192
579
  });
193
580
  }
@@ -200,6 +587,23 @@ export interface ReconciliationStore {
200
587
  load(): Promise<DurableReconciliationRecord[]>;
201
588
  /** Snapshot currently held in memory after last load/transact. */
202
589
  snapshot(): DurableReconciliationRecord[];
590
+ /** Terminal-scope mutations through the same serialized full-document owner. */
591
+ transactTerminalScopes(
592
+ mutator: (scopes: DurableTerminalScopeRecord[]) => DurableTerminalScopeRecord[],
593
+ ): Promise<void>;
594
+ transactTerminalState(
595
+ mutator: (state: { scopes: DurableTerminalScopeRecord[]; keys: EvictedTerminalKeyEntry[] }) => {
596
+ scopes: DurableTerminalScopeRecord[];
597
+ keys: EvictedTerminalKeyEntry[];
598
+ },
599
+ ): Promise<void>;
600
+ /** Wait until every previously admitted reconciliation transaction settles. */
601
+ drain?(): Promise<void>;
602
+ transactTerminalKeys(mutator: (keys: EvictedTerminalKeyEntry[]) => EvictedTerminalKeyEntry[]): Promise<void>;
603
+ snapshotTerminalKeys(): EvictedTerminalKeyEntry[];
604
+ loadTerminalScopes(): Promise<DurableTerminalScopeRecord[]>;
605
+ /** Snapshot of terminal scopes currently held in memory. */
606
+ snapshotTerminalScopes(): DurableTerminalScopeRecord[];
203
607
  delete(): Promise<void>;
204
608
  }
205
609
 
@@ -218,13 +622,15 @@ export function createReconciliationStore(options: {
218
622
  : null;
219
623
 
220
624
  let memory: DurableReconciliationRecord[] = [];
625
+ let terminalMemory: DurableTerminalScopeRecord[] = [];
626
+ let terminalKeyMemory: EvictedTerminalKeyEntry[] = [];
221
627
  let chain: Promise<void> = Promise.resolve();
222
628
 
223
629
  const writeAtomic = async (document: ReconciliationStoreDocument): Promise<void> => {
224
630
  if (!filePath) return;
225
631
  const directory = path.dirname(filePath);
226
632
  await fileFs.mkdir(directory, { recursive: true, mode: 0o700 });
227
- const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
633
+ const temporary = `${filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
228
634
  try {
229
635
  await fileFs.writeFile(temporary, `${JSON.stringify(document)}\n`, { mode: 0o600 });
230
636
  try {
@@ -249,6 +655,11 @@ export function createReconciliationStore(options: {
249
655
  const load = async (): Promise<DurableReconciliationRecord[]> => {
250
656
  if (!filePath) {
251
657
  memory = [];
658
+ terminalMemory = [];
659
+ // No durable store means no evicted-key tombstones either: a store
660
+ // instance that already loaded tombstones must not keep replaying or
661
+ // conflicting on keys that no longer exist on disk (review thread P2).
662
+ terminalKeyMemory = [];
252
663
  return memory;
253
664
  }
254
665
  let raw: string;
@@ -259,6 +670,8 @@ export function createReconciliationStore(options: {
259
670
  // so the endpoint never becomes ready as if no prompt had been accepted.
260
671
  if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
261
672
  memory = [];
673
+ terminalMemory = [];
674
+ terminalKeyMemory = [];
262
675
  return memory;
263
676
  }
264
677
  let document: ReconciliationStoreDocument;
@@ -272,15 +685,30 @@ export function createReconciliationStore(options: {
272
685
  // ignore
273
686
  }
274
687
  memory = [];
688
+ terminalMemory = [];
689
+ terminalKeyMemory = [];
275
690
  return memory;
276
691
  }
277
692
  const settled = settleProcessRestart(document.records, now());
693
+ const settledTerminal = settleTerminalScopeRestart(document.terminalScopes ?? [], now());
278
694
  // Restart settlement must be durable before it is observable: a failed rewrite
279
695
  // propagates so the endpoint stays unready instead of serving empty state as if
280
696
  // no prompt had ever been accepted.
281
- if (settled.some((record, index) => record !== document.records[index]))
282
- await writeAtomic({ version: RECONCILIATION_STORE_VERSION, sessionId, records: settled });
697
+ const recordsChanged = settled.some((record, index) => record !== document.records[index]);
698
+ const terminalChanged = settledTerminal.some((scope, index) => scope !== (document.terminalScopes ?? [])[index]);
699
+ if (recordsChanged || terminalChanged)
700
+ await writeAtomic({
701
+ version: RECONCILIATION_STORE_VERSION,
702
+ sessionId,
703
+ records: settled,
704
+ ...(document.terminalScopes !== undefined || terminalChanged ? { terminalScopes: settledTerminal } : {}),
705
+ ...(document.evictedTerminalKeys !== undefined
706
+ ? { evictedTerminalKeys: document.evictedTerminalKeys }
707
+ : {}),
708
+ });
283
709
  memory = settled;
710
+ terminalMemory = settledTerminal;
711
+ terminalKeyMemory = document.evictedTerminalKeys ?? [];
284
712
  return memory;
285
713
  };
286
714
 
@@ -289,7 +717,13 @@ export function createReconciliationStore(options: {
289
717
  ): Promise<void> => {
290
718
  const run = async () => {
291
719
  const next = mutator(memory.map(r => ({ ...r })));
292
- await writeAtomic({ version: RECONCILIATION_STORE_VERSION, sessionId, records: next });
720
+ await writeAtomic({
721
+ version: RECONCILIATION_STORE_VERSION,
722
+ sessionId,
723
+ records: next,
724
+ ...(terminalMemory.length > 0 ? { terminalScopes: terminalMemory } : {}),
725
+ ...(terminalKeyMemory.length > 0 ? { evictedTerminalKeys: terminalKeyMemory } : {}),
726
+ });
293
727
  memory = next;
294
728
  };
295
729
  const pending = chain.then(run, run);
@@ -300,8 +734,83 @@ export function createReconciliationStore(options: {
300
734
  await pending;
301
735
  };
302
736
 
737
+ const transactTerminalScopes = async (
738
+ mutator: (scopes: DurableTerminalScopeRecord[]) => DurableTerminalScopeRecord[],
739
+ ): Promise<void> => {
740
+ const run = async () => {
741
+ const next = mutator(terminalMemory.map(s => ({ ...s })));
742
+ await writeAtomic({
743
+ version: RECONCILIATION_STORE_VERSION,
744
+ sessionId,
745
+ records: memory,
746
+ ...(next.length > 0 ? { terminalScopes: next } : {}),
747
+ ...(terminalKeyMemory.length > 0 ? { evictedTerminalKeys: terminalKeyMemory } : {}),
748
+ });
749
+ terminalMemory = next;
750
+ };
751
+ const pending = chain.then(run, run);
752
+ chain = pending.then(
753
+ () => undefined,
754
+ () => undefined,
755
+ );
756
+ await pending;
757
+ };
758
+
759
+ const transactTerminalState = async (
760
+ mutator: (state: { scopes: DurableTerminalScopeRecord[]; keys: EvictedTerminalKeyEntry[] }) => {
761
+ scopes: DurableTerminalScopeRecord[];
762
+ keys: EvictedTerminalKeyEntry[];
763
+ },
764
+ ): Promise<void> => {
765
+ const run = async () => {
766
+ const next = mutator({
767
+ scopes: terminalMemory.map(s => ({ ...s })),
768
+ keys: terminalKeyMemory.map(k => ({ ...k })),
769
+ });
770
+ await writeAtomic({
771
+ version: RECONCILIATION_STORE_VERSION,
772
+ sessionId,
773
+ records: memory,
774
+ ...(next.scopes.length > 0 ? { terminalScopes: next.scopes } : {}),
775
+ ...(next.keys.length > 0 ? { evictedTerminalKeys: next.keys } : {}),
776
+ });
777
+ terminalMemory = next.scopes;
778
+ terminalKeyMemory = next.keys;
779
+ };
780
+ const pending = chain.then(run, run);
781
+ chain = pending.then(
782
+ () => undefined,
783
+ () => undefined,
784
+ );
785
+ await pending;
786
+ };
787
+
788
+ const transactTerminalKeys = async (
789
+ mutator: (keys: EvictedTerminalKeyEntry[]) => EvictedTerminalKeyEntry[],
790
+ ): Promise<void> => {
791
+ const run = async () => {
792
+ const next = mutator(terminalKeyMemory.map(k => ({ ...k })));
793
+ await writeAtomic({
794
+ version: RECONCILIATION_STORE_VERSION,
795
+ sessionId,
796
+ records: memory,
797
+ ...(terminalMemory.length > 0 ? { terminalScopes: terminalMemory } : {}),
798
+ ...(next.length > 0 ? { evictedTerminalKeys: next } : {}),
799
+ });
800
+ terminalKeyMemory = next;
801
+ };
802
+ const pending = chain.then(run, run);
803
+ chain = pending.then(
804
+ () => undefined,
805
+ () => undefined,
806
+ );
807
+ await pending;
808
+ };
809
+
303
810
  const deleteStore = async (): Promise<void> => {
304
811
  memory = [];
812
+ terminalMemory = [];
813
+ terminalKeyMemory = [];
305
814
  if (!filePath) return;
306
815
  await fileFs.unlink(filePath).catch(error => {
307
816
  if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
@@ -314,6 +823,26 @@ export function createReconciliationStore(options: {
314
823
  transact,
315
824
  load,
316
825
  snapshot: () => memory.map(r => ({ ...r })),
826
+ transactTerminalScopes,
827
+ transactTerminalState,
828
+ transactTerminalKeys,
829
+ drain: async () => {
830
+ while (true) {
831
+ const observed = chain;
832
+ await observed;
833
+ // Fire-and-forget frame handlers can enqueue their first transaction in
834
+ // a later microtask. Yield once and require the queue tail to remain
835
+ // stable before reporting durable quiescence.
836
+ await Bun.sleep(0);
837
+ if (chain === observed) return;
838
+ }
839
+ },
840
+ loadTerminalScopes: async () => {
841
+ await load();
842
+ return terminalMemory.map(s => ({ ...s }));
843
+ },
844
+ snapshotTerminalScopes: () => terminalMemory.map(s => ({ ...s })),
845
+ snapshotTerminalKeys: () => terminalKeyMemory.map(k => ({ ...k })),
317
846
  delete: deleteStore,
318
847
  };
319
848
  }