@gajae-code/coding-agent 0.11.1 → 0.11.2

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 (404) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/notify-cli.d.ts +1 -0
  4. package/dist/types/cli/session-picker.d.ts +2 -1
  5. package/dist/types/cli/setup-cli.d.ts +1 -1
  6. package/dist/types/commands/mcp-serve.d.ts +33 -0
  7. package/dist/types/commands/mcp.d.ts +1 -1
  8. package/dist/types/commands/notify.d.ts +0 -2
  9. package/dist/types/commands/sdk.d.ts +1 -1
  10. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  11. package/dist/types/config/file-lock.d.ts +14 -4
  12. package/dist/types/config/keybindings.d.ts +42 -1
  13. package/dist/types/config/model-auth.d.ts +3 -0
  14. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  15. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  16. package/dist/types/config/model-equivalence.d.ts +20 -0
  17. package/dist/types/config/model-registry.d.ts +16 -15
  18. package/dist/types/config/model-resolver.d.ts +28 -18
  19. package/dist/types/config/models-config-schema.d.ts +11 -0
  20. package/dist/types/config/settings-schema.d.ts +56 -1
  21. package/dist/types/config/settings.d.ts +14 -11
  22. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  23. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  24. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  25. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  26. package/dist/types/discovery/helpers.d.ts +0 -6
  27. package/dist/types/edit/index.d.ts +3 -0
  28. package/dist/types/eval/py/env.d.ts +9 -0
  29. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  30. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  31. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  32. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  33. package/dist/types/extensibility/shared-events.d.ts +2 -0
  34. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  35. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  36. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  37. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  38. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  39. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  40. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  41. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  42. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  43. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  44. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  45. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  46. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  47. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  49. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  50. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  51. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  52. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  53. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  54. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  55. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  56. package/dist/types/goals/runtime.d.ts +1 -0
  57. package/dist/types/goals/state.d.ts +8 -0
  58. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  59. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  60. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  61. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  62. package/dist/types/hindsight/state.d.ts +11 -4
  63. package/dist/types/lsp/lspmux.d.ts +5 -3
  64. package/dist/types/lsp/path-trust.d.ts +1 -0
  65. package/dist/types/main.d.ts +3 -2
  66. package/dist/types/memories/storage.d.ts +1 -0
  67. package/dist/types/modes/action-registry.d.ts +29 -0
  68. package/dist/types/modes/components/chrome.d.ts +14 -0
  69. package/dist/types/modes/components/command-palette.d.ts +27 -0
  70. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  71. package/dist/types/modes/components/index.d.ts +2 -1
  72. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  73. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  74. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  75. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  76. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  77. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  78. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  79. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  80. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  81. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  82. package/dist/types/modes/components/status-line.d.ts +1 -94
  83. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  84. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  85. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  86. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  87. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  88. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  89. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  90. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  91. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  92. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  93. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  94. package/dist/types/modes/interactive-mode.d.ts +19 -17
  95. package/dist/types/modes/jobs-observer.d.ts +2 -0
  96. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  97. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  98. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  99. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  100. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  101. package/dist/types/modes/types.d.ts +20 -13
  102. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  103. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  104. package/dist/types/sdk/broker/broker.d.ts +78 -12
  105. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  106. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  107. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  108. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  109. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  110. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  111. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  112. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  113. package/dist/types/sdk/bus/config.d.ts +11 -0
  114. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  115. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  116. package/dist/types/sdk/bus/index.d.ts +27 -0
  117. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  118. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  119. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  120. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  121. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  122. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  123. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  124. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  125. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  126. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  127. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  128. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  129. package/dist/types/sdk/host/host.d.ts +2 -0
  130. package/dist/types/sdk/index.d.ts +1 -0
  131. package/dist/types/sdk/session-directory.d.ts +56 -0
  132. package/dist/types/session/agent-session.d.ts +82 -47
  133. package/dist/types/session/agent-storage.d.ts +7 -0
  134. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  135. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  136. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  137. package/dist/types/session/messages.d.ts +16 -0
  138. package/dist/types/session/session-manager-internal.d.ts +14 -0
  139. package/dist/types/session/session-manager.d.ts +82 -12
  140. package/dist/types/session/session-storage.d.ts +43 -3
  141. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  142. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  143. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  144. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  145. package/dist/types/system-prompt.d.ts +2 -11
  146. package/dist/types/task/executor.d.ts +3 -1
  147. package/dist/types/thinking.d.ts +7 -0
  148. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  149. package/dist/types/tools/ask.d.ts +64 -2
  150. package/dist/types/tools/bash.d.ts +3 -0
  151. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  152. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  153. package/dist/types/tools/cron.d.ts +2 -0
  154. package/dist/types/tools/fetch.d.ts +1 -0
  155. package/dist/types/tools/index.d.ts +30 -15
  156. package/dist/types/tools/output-meta.d.ts +1 -0
  157. package/dist/types/tools/read.d.ts +3 -0
  158. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  159. package/dist/types/utils/event-bus.d.ts +1 -1
  160. package/dist/types/web/search/index.d.ts +3 -1
  161. package/package.json +21 -9
  162. package/scripts/build-sdk-package-smoke.ts +34 -1
  163. package/scripts/generate-hotkeys-docs.ts +74 -0
  164. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  165. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  166. package/src/async/job-manager.ts +49 -6
  167. package/src/cli/config-cli.ts +75 -2
  168. package/src/cli/notify-cli.ts +92 -26
  169. package/src/cli/session-picker.ts +20 -3
  170. package/src/cli/setup-cli.ts +1 -1
  171. package/src/cli.ts +1 -1
  172. package/src/commands/config.ts +1 -1
  173. package/src/commands/harness.ts +34 -106
  174. package/src/commands/launch.ts +6 -4
  175. package/src/commands/mcp-serve.ts +85 -8
  176. package/src/commands/mcp.ts +2 -2
  177. package/src/commands/notify.ts +8 -4
  178. package/src/commands/sdk.ts +61 -40
  179. package/src/commands/ultragoal.ts +4 -1
  180. package/src/config/atomic-yaml-patch.ts +80 -13
  181. package/src/config/file-lock.ts +105 -43
  182. package/src/config/keybindings.ts +64 -20
  183. package/src/config/model-auth.ts +9 -0
  184. package/src/config/model-bindings-applier.ts +139 -0
  185. package/src/config/model-discovery-manager.ts +224 -0
  186. package/src/config/model-equivalence.ts +66 -0
  187. package/src/config/model-registry.ts +169 -302
  188. package/src/config/model-resolver.ts +361 -369
  189. package/src/config/models-config-schema.ts +5 -4
  190. package/src/config/settings-schema.ts +159 -2
  191. package/src/config/settings.ts +458 -482
  192. package/src/coordinator-mcp/policy.ts +76 -11
  193. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  194. package/src/coordinator-mcp/question-state.ts +778 -0
  195. package/src/coordinator-mcp/server.ts +1702 -238
  196. package/src/debug/index.ts +1 -1
  197. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  198. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  199. package/src/discovery/helpers.ts +1 -50
  200. package/src/edit/index.ts +64 -0
  201. package/src/eval/py/env.ts +26 -0
  202. package/src/eval/py/kernel.ts +5 -3
  203. package/src/extensibility/extensions/loader.ts +9 -1
  204. package/src/extensibility/extensions/runner.ts +12 -3
  205. package/src/extensibility/extensions/types.ts +39 -1
  206. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  207. package/src/extensibility/hooks/runner.ts +2 -2
  208. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  209. package/src/extensibility/shared-events.ts +2 -0
  210. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  211. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  212. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  213. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  214. package/src/gjc-runtime/launch-tmux.ts +2 -5
  215. package/src/gjc-runtime/launch-worktree.ts +5 -5
  216. package/src/gjc-runtime/linux-proc.ts +71 -0
  217. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  218. package/src/gjc-runtime/session-resolution.ts +18 -3
  219. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  220. package/src/gjc-runtime/state-migrations.ts +1 -1
  221. package/src/gjc-runtime/state-runtime.ts +320 -249
  222. package/src/gjc-runtime/state-writer.ts +36 -40
  223. package/src/gjc-runtime/team-launch.ts +319 -0
  224. package/src/gjc-runtime/team-notify.ts +326 -0
  225. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  226. package/src/gjc-runtime/team-store.ts +828 -0
  227. package/src/gjc-runtime/team-workers.ts +962 -0
  228. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  229. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  230. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  231. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  232. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  233. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  234. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  235. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  236. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  237. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  238. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  239. package/src/goals/continuation-timeout-guard.ts +79 -0
  240. package/src/goals/runtime.ts +7 -5
  241. package/src/goals/state.ts +18 -0
  242. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  243. package/src/harness-control-plane/phase-rollup.ts +4 -7
  244. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  245. package/src/harness-control-plane/receipt-spool.ts +51 -7
  246. package/src/harness-control-plane/session-lease.ts +12 -130
  247. package/src/hindsight/backend.ts +26 -27
  248. package/src/hindsight/state.ts +101 -32
  249. package/src/hooks/skill-state.ts +17 -11
  250. package/src/internal-urls/docs-index.generated.ts +21 -18
  251. package/src/lsp/client.ts +1 -1
  252. package/src/lsp/config.ts +145 -26
  253. package/src/lsp/index.ts +1 -1
  254. package/src/lsp/lspmux.ts +39 -10
  255. package/src/lsp/path-trust.ts +81 -0
  256. package/src/main.ts +28 -8
  257. package/src/memories/index.ts +1 -0
  258. package/src/memories/storage.ts +6 -2
  259. package/src/modes/action-registry.ts +165 -0
  260. package/src/modes/components/chrome.ts +40 -0
  261. package/src/modes/components/command-palette.ts +168 -0
  262. package/src/modes/components/custom-editor.ts +1 -1
  263. package/src/modes/components/custom-provider-wizard.ts +51 -14
  264. package/src/modes/components/index.ts +2 -1
  265. package/src/modes/components/pet-selector.ts +11 -17
  266. package/src/modes/components/plan-preview-overlay.ts +295 -0
  267. package/src/modes/components/queue-mode-selector.ts +10 -27
  268. package/src/modes/components/queue-pane.ts +79 -0
  269. package/src/modes/components/read-tool-group.ts +13 -0
  270. package/src/modes/components/session-observer-overlay.ts +207 -772
  271. package/src/modes/components/sessions-dashboard.ts +181 -0
  272. package/src/modes/components/settings-selector.ts +1 -1
  273. package/src/modes/components/show-images-selector.ts +10 -24
  274. package/src/modes/components/status-line/types.ts +1 -1
  275. package/src/modes/components/status-line.ts +1 -969
  276. package/src/modes/components/tasks-pane.ts +73 -0
  277. package/src/modes/components/theme-selector.ts +11 -31
  278. package/src/modes/components/tool-execution.ts +22 -0
  279. package/src/modes/components/tool-status-header.ts +1056 -0
  280. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  281. package/src/modes/controllers/btw-controller.ts +1 -0
  282. package/src/modes/controllers/event-controller.ts +3 -2
  283. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  284. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  285. package/src/modes/controllers/input-controller.ts +764 -229
  286. package/src/modes/controllers/mode-gate.ts +24 -0
  287. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  288. package/src/modes/controllers/selector-controller.ts +225 -10
  289. package/src/modes/controllers/stt-controller.ts +125 -0
  290. package/src/modes/interactive-mode.ts +289 -1150
  291. package/src/modes/jobs-observer.ts +3 -0
  292. package/src/modes/runtime-init.ts +8 -0
  293. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  294. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  295. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  296. package/src/modes/tasks-aggregator.ts +199 -0
  297. package/src/modes/transcript-item-registry.ts +229 -0
  298. package/src/modes/types.ts +45 -18
  299. package/src/modes/utils/ui-helpers.ts +17 -9
  300. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  301. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  302. package/src/prompts/agents/architect.md +2 -8
  303. package/src/prompts/agents/critic.md +2 -8
  304. package/src/prompts/agents/executor.md +3 -0
  305. package/src/prompts/agents/planner.md +4 -12
  306. package/src/prompts/system/custom-system-prompt.md +1 -23
  307. package/src/prompts/system/plan-mode-approved.md +8 -0
  308. package/src/prompts/system/system-prompt.md +17 -96
  309. package/src/prompts/tools/bash.md +4 -4
  310. package/src/prompts/tools/browser.md +15 -52
  311. package/src/prompts/tools/find.md +2 -2
  312. package/src/prompts/tools/hashline.md +13 -55
  313. package/src/prompts/tools/read.md +3 -6
  314. package/src/prompts/tools/search-tool-bm25.md +0 -2
  315. package/src/prompts/tools/search.md +2 -4
  316. package/src/prompts/tools/skill-discovery.md +1 -2
  317. package/src/prompts/tools/task.md +1 -1
  318. package/src/runtime-mcp/manager.ts +1 -1
  319. package/src/sdk/broker/broker.ts +206 -78
  320. package/src/sdk/broker/discovery.ts +1 -1
  321. package/src/sdk/broker/ensure.ts +3 -1
  322. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  323. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  324. package/src/sdk/broker/lifecycle.ts +1399 -142
  325. package/src/sdk/broker/process-incarnation.ts +5 -13
  326. package/src/sdk/broker/session-index.ts +145 -56
  327. package/src/sdk/broker/state-version.ts +21 -0
  328. package/src/sdk/broker/transport.ts +1 -1
  329. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  330. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  331. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  332. package/src/sdk/bus/config-commands.ts +5 -4
  333. package/src/sdk/bus/config.ts +98 -0
  334. package/src/sdk/bus/conversation-store.ts +5 -29
  335. package/src/sdk/bus/daemon-paths.ts +2 -0
  336. package/src/sdk/bus/html-format.ts +20 -3
  337. package/src/sdk/bus/index.ts +778 -93
  338. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  339. package/src/sdk/bus/natives-augment.d.ts +26 -1
  340. package/src/sdk/bus/notification-service.ts +168 -20
  341. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  342. package/src/sdk/bus/recent-activity.ts +188 -74
  343. package/src/sdk/bus/rich-document.ts +258 -0
  344. package/src/sdk/bus/rich-render.ts +62 -70
  345. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  346. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  347. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  348. package/src/sdk/bus/threaded-inbound.ts +8 -3
  349. package/src/sdk/bus/threaded-render.ts +57 -1
  350. package/src/sdk/bus/topic-registry.ts +120 -13
  351. package/src/sdk/host/control/dispatch.ts +14 -2
  352. package/src/sdk/host/control/operations.ts +6 -1
  353. package/src/sdk/host/host.ts +31 -1
  354. package/src/sdk/host/query/handlers.ts +20 -22
  355. package/src/sdk/host/query/revision-store.ts +80 -57
  356. package/src/sdk/index.ts +1 -0
  357. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  358. package/src/sdk/session-directory.ts +141 -0
  359. package/src/sdk/session.ts +458 -479
  360. package/src/session/agent-session.ts +1700 -583
  361. package/src/session/agent-storage.ts +18 -0
  362. package/src/session/cache-economics.ts +25 -8
  363. package/src/session/fallback-chain-controller.ts +53 -3
  364. package/src/session/internal/managed-session-scope.ts +2226 -0
  365. package/src/session/internal/managed-session-storage.ts +453 -0
  366. package/src/session/messages.ts +91 -2
  367. package/src/session/session-manager-internal.ts +33 -0
  368. package/src/session/session-manager.ts +1112 -333
  369. package/src/session/session-storage.ts +444 -39
  370. package/src/session/volatile-context-pruning.ts +78 -0
  371. package/src/setup/credential-auto-import.ts +2 -1
  372. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  373. package/src/setup/provider-onboarding.ts +36 -61
  374. package/src/setup/provider-presets.json +5 -0
  375. package/src/skill-state/active-state.ts +3 -13
  376. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  377. package/src/skill-state/workflow-state-contract.ts +6 -41
  378. package/src/slash-commands/builtin-registry.ts +46 -11
  379. package/src/system-prompt.ts +37 -44
  380. package/src/task/agents.ts +19 -1
  381. package/src/task/executor.ts +90 -6
  382. package/src/task/index.ts +36 -21
  383. package/src/thinking.ts +21 -6
  384. package/src/tool-discovery/tool-index.ts +2 -134
  385. package/src/tools/ask.ts +157 -21
  386. package/src/tools/ast-edit.ts +1 -1
  387. package/src/tools/bash.ts +38 -0
  388. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  389. package/src/tools/browser/tab-supervisor.ts +282 -36
  390. package/src/tools/computer.ts +10 -3
  391. package/src/tools/cron.ts +30 -16
  392. package/src/tools/eval.ts +1 -2
  393. package/src/tools/fetch.ts +11 -4
  394. package/src/tools/index.ts +76 -24
  395. package/src/tools/output-meta.ts +13 -5
  396. package/src/tools/read.ts +37 -1
  397. package/src/tools/resource-gc.ts +2 -2
  398. package/src/tools/search-tool-bm25.ts +11 -74
  399. package/src/utils/event-bus.ts +9 -4
  400. package/src/web/search/index.ts +3 -3
  401. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  402. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  403. package/src/discovery/plugin-dir-roots.ts +0 -28
  404. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -0,0 +1,30 @@
1
+ export class CommandError extends Error {
2
+ constructor(
3
+ public readonly exitStatus: number,
4
+ message: string,
5
+ ) {
6
+ super(message);
7
+ this.name = "CommandError";
8
+ }
9
+ }
10
+
11
+ export function flagValue(args: readonly string[], flag: string): string | undefined {
12
+ const index = args.indexOf(flag);
13
+ return index < 0 ? undefined : args[index + 1];
14
+ }
15
+
16
+ export function hasFlag(args: readonly string[], flag: string): boolean {
17
+ return args.includes(flag);
18
+ }
19
+
20
+ export function isPlainObject(value: unknown): value is Record<string, unknown> {
21
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
22
+ }
23
+
24
+ const PATH_COMPONENT_RE = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
25
+
26
+ export function assertSafePathComponent(value: string, label: string): void {
27
+ if (!PATH_COMPONENT_RE.test(value) || value.includes("..")) {
28
+ throw new CommandError(2, `invalid path component for --${label}: ${value}`);
29
+ }
30
+ }
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "deep-interview": {
3
+ "canonicalOverrides": [],
3
4
  "graphLabel": "Deep Interview",
4
5
  "hudFields": [
5
6
  "current_phase",
@@ -10,6 +11,7 @@
10
11
  "topology"
11
12
  ],
12
13
  "initialState": "interviewing",
14
+ "phaseLock": [],
13
15
  "retention": [
14
16
  {
15
17
  "category": "state",
@@ -42,6 +44,14 @@
42
44
  "terminal": true
43
45
  }
44
46
  ],
47
+ "stopReleasingPhases": [
48
+ "complete",
49
+ "completed",
50
+ "failed",
51
+ "cancelled",
52
+ "canceled",
53
+ "inactive"
54
+ ],
45
55
  "terminalStates": [
46
56
  "handoff",
47
57
  "complete"
@@ -318,6 +328,16 @@
318
328
  ]
319
329
  },
320
330
  "ralplan": {
331
+ "canonicalOverrides": [
332
+ "final",
333
+ "handoff",
334
+ "complete",
335
+ "completed",
336
+ "failed",
337
+ "cancelled",
338
+ "canceled",
339
+ "inactive"
340
+ ],
321
341
  "graphLabel": "Ralplan",
322
342
  "hudFields": [
323
343
  "current_phase",
@@ -328,6 +348,16 @@
328
348
  "plan_path"
329
349
  ],
330
350
  "initialState": "planner",
351
+ "phaseLock": [
352
+ "final",
353
+ "handoff",
354
+ "complete",
355
+ "completed",
356
+ "failed",
357
+ "cancelled",
358
+ "canceled",
359
+ "inactive"
360
+ ],
331
361
  "retention": [
332
362
  {
333
363
  "category": "state",
@@ -378,6 +408,14 @@
378
408
  "terminal": true
379
409
  }
380
410
  ],
411
+ "stopReleasingPhases": [
412
+ "complete",
413
+ "completed",
414
+ "failed",
415
+ "cancelled",
416
+ "canceled",
417
+ "inactive"
418
+ ],
381
419
  "terminalStates": [
382
420
  "final",
383
421
  "handoff"
@@ -704,6 +742,7 @@
704
742
  ]
705
743
  },
706
744
  "team": {
745
+ "canonicalOverrides": [],
707
746
  "graphLabel": "Team",
708
747
  "hudFields": [
709
748
  "current_phase",
@@ -714,6 +753,7 @@
714
753
  "integration"
715
754
  ],
716
755
  "initialState": "starting",
756
+ "phaseLock": [],
717
757
  "retention": [
718
758
  {
719
759
  "category": "state",
@@ -774,6 +814,14 @@
774
814
  "terminal": true
775
815
  }
776
816
  ],
817
+ "stopReleasingPhases": [
818
+ "complete",
819
+ "completed",
820
+ "failed",
821
+ "cancelled",
822
+ "canceled",
823
+ "inactive"
824
+ ],
777
825
  "terminalStates": [
778
826
  "complete",
779
827
  "failed",
@@ -1150,6 +1198,7 @@
1150
1198
  ]
1151
1199
  },
1152
1200
  "ultragoal": {
1201
+ "canonicalOverrides": [],
1153
1202
  "graphLabel": "Ultragoal",
1154
1203
  "hudFields": [
1155
1204
  "current_phase",
@@ -1160,6 +1209,7 @@
1160
1209
  "brief_path"
1161
1210
  ],
1162
1211
  "initialState": "goal-planning",
1212
+ "phaseLock": [],
1163
1213
  "retention": [
1164
1214
  {
1165
1215
  "category": "state",
@@ -1212,6 +1262,14 @@
1212
1262
  "terminal": true
1213
1263
  }
1214
1264
  ],
1265
+ "stopReleasingPhases": [
1266
+ "complete",
1267
+ "completed",
1268
+ "failed",
1269
+ "cancelled",
1270
+ "canceled",
1271
+ "inactive"
1272
+ ],
1215
1273
  "terminalStates": [
1216
1274
  "missing",
1217
1275
  "failed",
@@ -52,6 +52,9 @@ export interface SkillManifest {
52
52
  retention: RetentionPolicy[];
53
53
  hudFields: string[];
54
54
  graphLabel: string;
55
+ stopReleasingPhases: readonly string[];
56
+ phaseLock: readonly string[];
57
+ canonicalOverrides: readonly string[];
55
58
  }
56
59
 
57
60
  const STATE_RETENTION: RetentionPolicy = { category: "state", keep: 1 };
@@ -116,6 +119,9 @@ function manifest(input: {
116
119
  retention: RetentionPolicy[];
117
120
  hudFields: string[];
118
121
  graphLabel: string;
122
+ stopReleasingPhases?: readonly string[];
123
+ phaseLock?: readonly string[];
124
+ canonicalOverrides?: readonly string[];
119
125
  initialState?: string;
120
126
  }): SkillManifest {
121
127
  const staleInitialState = initialPhaseForSkill(input.skill);
@@ -131,6 +137,16 @@ function manifest(input: {
131
137
  retention: input.retention,
132
138
  hudFields: input.hudFields,
133
139
  graphLabel: input.graphLabel,
140
+ stopReleasingPhases: input.stopReleasingPhases ?? [
141
+ "complete",
142
+ "completed",
143
+ "failed",
144
+ "cancelled",
145
+ "canceled",
146
+ "inactive",
147
+ ],
148
+ phaseLock: input.phaseLock ?? [],
149
+ canonicalOverrides: input.canonicalOverrides ?? [],
134
150
  };
135
151
  }
136
152
 
@@ -207,6 +223,8 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
207
223
  retention: [STATE_RETENTION, ARTIFACT_RETENTION, LEDGER_RETENTION, PRUNE_RETENTION, FORCE_RETENTION],
208
224
  hudFields: ["current_phase", "mode", "run_id", "stage", "stage_n", "plan_path"],
209
225
  graphLabel: "Ralplan",
226
+ phaseLock: ["final", "handoff", "complete", "completed", "failed", "cancelled", "canceled", "inactive"],
227
+ canonicalOverrides: ["final", "handoff", "complete", "completed", "failed", "cancelled", "canceled", "inactive"],
210
228
  }),
211
229
  ultragoal: manifest({
212
230
  skill: "ultragoal",
@@ -0,0 +1,79 @@
1
+ export type ToolOutcomeKind = "timeout" | "success" | "other-error";
2
+
3
+ export interface TimeoutToolOutcome {
4
+ toolName: string;
5
+ argsKey: string;
6
+ kind: ToolOutcomeKind;
7
+ }
8
+
9
+ export interface TimeoutHoldState {
10
+ heldSnapshotKey?: string;
11
+ fingerprint?: string;
12
+ streak: number;
13
+ }
14
+
15
+ function isRecord(value: unknown): value is Record<string, unknown> {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+
19
+ function canonicalizeArgs(value: unknown): unknown {
20
+ if (Array.isArray(value)) {
21
+ return value.map(item => canonicalizeArgs(item));
22
+ }
23
+ if (isRecord(value)) {
24
+ const canonical: Record<string, unknown> = {};
25
+ for (const key of Object.keys(value).sort()) {
26
+ if (value[key] !== undefined) {
27
+ canonical[key] = canonicalizeArgs(value[key]);
28
+ }
29
+ }
30
+ return canonical;
31
+ }
32
+ return value;
33
+ }
34
+
35
+ export function toResultText(result: unknown): string {
36
+ if (typeof result === "string") return result;
37
+ if (!isRecord(result) || !Array.isArray(result.content)) return "";
38
+ return result.content
39
+ .filter(
40
+ (content): content is { type: "text"; text: string } =>
41
+ isRecord(content) && content.type === "text" && typeof content.text === "string",
42
+ )
43
+ .map(content => content.text)
44
+ .join("\n");
45
+ }
46
+
47
+ export function classifyToolOutcome(isError: unknown, text: string): ToolOutcomeKind {
48
+ if (isError !== true) return "success";
49
+ return /\btimed out\b|\btimeout\b/i.test(text) ? "timeout" : "other-error";
50
+ }
51
+
52
+ export function canonicalArgsKey(args: unknown): string {
53
+ return JSON.stringify(canonicalizeArgs(args ?? null)) ?? "null";
54
+ }
55
+
56
+ export function turnTimeoutFingerprint(outcomes: readonly TimeoutToolOutcome[]): string | null {
57
+ if (outcomes.length === 0 || outcomes.some(outcome => outcome.kind !== "timeout")) return null;
58
+ const [first] = outcomes;
59
+ if (!first || outcomes.some(outcome => outcome.toolName !== first.toolName || outcome.argsKey !== first.argsKey)) {
60
+ return null;
61
+ }
62
+ return `${first.toolName}\u0000${first.argsKey}`;
63
+ }
64
+
65
+ export function decideTimeoutHold(
66
+ prev: TimeoutHoldState,
67
+ turn: { snapshotKey: string; fingerprint: string | null },
68
+ ): { hold: boolean; next: TimeoutHoldState } {
69
+ if (turn.fingerprint === null) {
70
+ return { hold: false, next: { heldSnapshotKey: undefined, fingerprint: undefined, streak: 0 } };
71
+ }
72
+ const continues =
73
+ prev.streak >= 1 && prev.fingerprint === turn.fingerprint && prev.heldSnapshotKey === turn.snapshotKey;
74
+ const streak = continues ? prev.streak + 1 : 1;
75
+ return {
76
+ hold: streak >= 2,
77
+ next: { heldSnapshotKey: turn.snapshotKey, fingerprint: turn.fingerprint, streak },
78
+ };
79
+ }
@@ -294,28 +294,30 @@ export class GoalRuntime {
294
294
  await this.#withAccounting(() => this.#flushUsageLocked(currentUsage));
295
295
  }
296
296
 
297
- #createGoalState(objective: string): GoalModeState {
297
+ #createGoalState(input: { objective: string; provenance?: Goal["provenance"] }): GoalModeState {
298
298
  const now = this.#now();
299
299
  const goal: Goal = {
300
300
  id: String(Snowflake.next()),
301
- objective,
301
+ objective: input.objective,
302
302
  status: "active",
303
303
  tokensUsed: 0,
304
304
  timeUsedSeconds: 0,
305
305
  createdAt: now,
306
306
  updatedAt: now,
307
+ ...(input.provenance ? { provenance: input.provenance } : {}),
307
308
  };
308
309
  return { enabled: true, mode: "active", goal };
309
310
  }
310
311
 
311
- async createGoal(input: { objective: string }): Promise<GoalModeState> {
312
+ async createGoal(input: { objective: string; provenance?: Goal["provenance"] }): Promise<GoalModeState> {
312
313
  const objective = validateGoalObjective(input.objective, "create");
313
314
  return await this.#withAccounting(async () => {
314
315
  const existing = this.#getStateClone();
315
316
  if (existing?.goal && existing.goal.status !== "dropped" && existing.goal.status !== "complete") {
316
317
  throw new Error("cannot create a new goal because this session already has a goal");
317
318
  }
318
- const state = this.#createGoalState(objective);
319
+ const state = this.#createGoalState({ objective, provenance: input.provenance ?? { source: "user" } });
320
+
319
321
  this.#markActiveAccounting(state.goal);
320
322
  await this.#commitState(state, { persist: "goal" });
321
323
  return state;
@@ -330,7 +332,7 @@ export class GoalRuntime {
330
332
  throw new Error("cannot replace goal because no goal is active");
331
333
  }
332
334
  await this.#flushUsageLocked();
333
- const state = this.#createGoalState(objective);
335
+ const state = this.#createGoalState({ objective, provenance: { source: "user" } });
334
336
  this.#markActiveAccounting(state.goal);
335
337
  await this.#commitState(state, { persist: "goal" });
336
338
  return state;
@@ -2,6 +2,8 @@ import type { UsageStatistics } from "../session/session-manager";
2
2
 
3
3
  export type GoalStatus = "active" | "paused" | "complete" | "dropped";
4
4
 
5
+ export type GoalProvenance = { source: "ultragoal"; runId: string; goalId: string } | { source: "user" };
6
+
5
7
  export interface Goal {
6
8
  id: string;
7
9
  objective: string;
@@ -10,6 +12,7 @@ export interface Goal {
10
12
  timeUsedSeconds: number;
11
13
  createdAt: number;
12
14
  updatedAt: number;
15
+ provenance?: GoalProvenance;
13
16
  }
14
17
 
15
18
  export interface GoalModeState {
@@ -47,6 +50,20 @@ export function normalizeGoal(candidate: unknown): Goal | null {
47
50
  if (status !== "active" && status !== "paused" && status !== "complete" && status !== "dropped") {
48
51
  return null;
49
52
  }
53
+ const provenance = value.provenance;
54
+ if (
55
+ provenance !== undefined &&
56
+ (!provenance ||
57
+ typeof provenance !== "object" ||
58
+ ((provenance as Record<string, unknown>).source === "ultragoal" &&
59
+ (typeof (provenance as Record<string, unknown>).runId !== "string" ||
60
+ typeof (provenance as Record<string, unknown>).goalId !== "string")) ||
61
+ ((provenance as Record<string, unknown>).source !== "ultragoal" &&
62
+ (provenance as Record<string, unknown>).source !== "user"))
63
+ ) {
64
+ return null;
65
+ }
66
+
50
67
  return {
51
68
  id: value.id,
52
69
  objective: value.objective,
@@ -55,6 +72,7 @@ export function normalizeGoal(candidate: unknown): Goal | null {
55
72
  timeUsedSeconds: value.timeUsedSeconds,
56
73
  createdAt: value.createdAt,
57
74
  updatedAt: value.updatedAt,
75
+ ...(provenance ? { provenance: provenance as GoalProvenance } : {}),
58
76
  };
59
77
  }
60
78
 
@@ -0,0 +1,64 @@
1
+ import type { Observation, SessionState } from "./types";
2
+
3
+ export const OWNER_STARTUP_BLOCKER = "owner-died-before-first-prompt";
4
+
5
+ export interface CompletedTerminalEvent {
6
+ cursor: number;
7
+ createdAt: string;
8
+ kind: string;
9
+ }
10
+
11
+ export function isOwnerLivenessBlocker(blocker: string): boolean {
12
+ return blocker === "detached-owner-not-live" || blocker.startsWith("owner-vanished:");
13
+ }
14
+
15
+ export function needsVanishedOwnerBlock(
16
+ state: SessionState,
17
+ observation: Observation,
18
+ completedTerminal: CompletedTerminalEvent | null,
19
+ ): boolean {
20
+ if (observation.ownerLive || state.lifecycle !== "observing") return false;
21
+ if (completedTerminal || observation.observedSignals.includes("completed")) return false;
22
+ return observation.observedSignals.some(
23
+ signal => signal === "prompt-accepted" || signal === "tool-call" || signal === "streaming",
24
+ );
25
+ }
26
+
27
+ export async function reconcileOwnerLifecycle(input: {
28
+ state: SessionState;
29
+ observation: Observation;
30
+ completedTerminal: CompletedTerminalEvent | null;
31
+ startupBlocked: boolean;
32
+ persist: (state: SessionState) => Promise<void>;
33
+ nowIso: () => string;
34
+ }): Promise<SessionState> {
35
+ const { state, observation, completedTerminal } = input;
36
+ if (
37
+ completedTerminal &&
38
+ !observation.ownerLive &&
39
+ observation.gitDelta === "clean" &&
40
+ state.lifecycle !== "completed" &&
41
+ state.lifecycle !== "retired"
42
+ ) {
43
+ state.lifecycle = "completed";
44
+ state.blockers = state.blockers.filter(blocker => !isOwnerLivenessBlocker(blocker));
45
+ state.updatedAt = input.nowIso();
46
+ await input.persist(state);
47
+ }
48
+ if (needsVanishedOwnerBlock(state, observation, completedTerminal)) {
49
+ const blocker = `owner-vanished:${observation.gitDelta}`;
50
+ state.lifecycle = "blocked";
51
+ state.blockers = state.blockers.includes(blocker) ? state.blockers : [...state.blockers, blocker];
52
+ state.updatedAt = input.nowIso();
53
+ await input.persist(state);
54
+ }
55
+ if (input.startupBlocked && state.lifecycle !== "completed" && state.lifecycle !== "retired") {
56
+ state.lifecycle = "blocked";
57
+ state.blockers = state.blockers.includes(OWNER_STARTUP_BLOCKER)
58
+ ? state.blockers
59
+ : [...state.blockers, OWNER_STARTUP_BLOCKER];
60
+ state.updatedAt = input.nowIso();
61
+ await input.persist(state);
62
+ }
63
+ return state;
64
+ }
@@ -1,12 +1,9 @@
1
1
  /**
2
- * Phase-boundary receipt rollup builder (receipt-of-receipts).
2
+ * EXPERIMENTAL: Phase-boundary receipt rollup builder (receipt-of-receipts).
3
3
  *
4
- * At a harness lifecycle boundary, N child task receipts can be superseded by a
5
- * single `phase-rollup` receipt that preserves per-child pointers (id, status,
6
- * outputRef, sha256) plus aggregate ROI totals. The rollup is hash-sealed via
7
- * the standard receipt envelope and validated fail-closed like every other
8
- * family (see `validatePhaseRollup` in receipts.ts). Pure builder — no runtime
9
- * injection behavior is changed here.
4
+ * This builder is not wired into finalize or retire yet; child receipts remain
5
+ * authoritative until a lifecycle integration preserves their supersession
6
+ * semantics. It is retained for controlled integration experiments only.
10
7
  */
11
8
  import type { TaskResultReceipt } from "../task/receipt";
12
9
  import {
@@ -1,3 +1,8 @@
1
+ /**
2
+ * EXPERIMENTAL: Receipt ingestion is a pure evaluator and is not yet wired to
3
+ * finalize or retire lifecycle mutation. Callers must not treat its result as
4
+ * authoritative lifecycle persistence.
5
+ */
1
6
  import type { CompletionEvidence, ReceiptEnvelope, ReviewVerdictEvidence } from "./receipts";
2
7
  import { validateReceipt } from "./receipts";
3
8
  import { canTransition } from "./state-machine";
@@ -21,6 +21,46 @@ export interface ReceiptSpoolAppendResult {
21
21
  const receiptSpoolDirStorage = new AsyncLocalStorage<string | undefined>();
22
22
  const spoolQueues = new Map<string, Promise<void>>();
23
23
  const noop = (): void => undefined;
24
+
25
+ interface ReceiptSpoolCursorCache {
26
+ mtimeMs: number;
27
+ size: number;
28
+ highest: bigint;
29
+ }
30
+
31
+ const cursorCache = new Map<string, ReceiptSpoolCursorCache>();
32
+ let fullScanCount = 0;
33
+
34
+ /** @internal Test-only instrumentation for steady-state spool scan coverage. */
35
+ export function receiptSpoolFullScanCountForTesting(): number {
36
+ return fullScanCount;
37
+ }
38
+
39
+ /** @internal Test-only cache reset. */
40
+ export function resetReceiptSpoolCursorCacheForTesting(): void {
41
+ cursorCache.clear();
42
+ fullScanCount = 0;
43
+ }
44
+
45
+ async function spoolStat(spoolFile: string): Promise<{ mtimeMs: number; size: number } | undefined> {
46
+ try {
47
+ const stats = await fs.stat(spoolFile);
48
+ return { mtimeMs: stats.mtimeMs, size: stats.size };
49
+ } catch (error) {
50
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
51
+ throw error;
52
+ }
53
+ }
54
+
55
+ function sameSpoolRevision(a: ReceiptSpoolCursorCache, b: { mtimeMs: number; size: number }): boolean {
56
+ return a.mtimeMs === b.mtimeMs && a.size === b.size;
57
+ }
58
+
59
+ async function cacheHighestCursor(spoolFile: string, highest: bigint): Promise<void> {
60
+ const revision = await spoolStat(spoolFile);
61
+ if (revision) cursorCache.set(spoolFile, { ...revision, highest });
62
+ }
63
+
24
64
  export async function withReceiptSpoolDir<T>(spoolDir: string, fn: () => Promise<T>): Promise<T> {
25
65
  const trimmed = spoolDir.trim();
26
66
  if (!trimmed) throw new Error("receipt_spool_dir_empty");
@@ -55,14 +95,16 @@ export function formatReceiptSpoolCursor(cursor: bigint): string {
55
95
 
56
96
  export async function readHighestReceiptSpoolCursor(spoolDir: string): Promise<bigint> {
57
97
  const spoolFile = receiptSpoolPath(spoolDir);
58
- let raw: string;
59
- try {
60
- raw = await fs.readFile(spoolFile, "utf8");
61
- } catch (error) {
62
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return 0n;
63
- throw error;
98
+ const revision = await spoolStat(spoolFile);
99
+ if (!revision) {
100
+ cursorCache.delete(spoolFile);
101
+ return 0n;
64
102
  }
103
+ const cached = cursorCache.get(spoolFile);
104
+ if (cached && sameSpoolRevision(cached, revision)) return cached.highest;
65
105
 
106
+ fullScanCount++;
107
+ const raw = await fs.readFile(spoolFile, "utf8");
66
108
  let highest = 0n;
67
109
  for (const line of raw.split("\n")) {
68
110
  const trimmed = line.trim();
@@ -72,9 +114,10 @@ export async function readHighestReceiptSpoolCursor(spoolDir: string): Promise<b
72
114
  const cursor = parseCursor(parsed.cursor);
73
115
  if (cursor !== undefined && cursor > highest) highest = cursor;
74
116
  } catch {
75
- // A crash may leave a torn tail; consumers skip malformed lines and so do we.
117
+ // A crash may leave a torn tail; a full scan ignores it and re-establishes the cache.
76
118
  }
77
119
  }
120
+ cursorCache.set(spoolFile, { ...revision, highest });
78
121
  return highest;
79
122
  }
80
123
 
@@ -111,6 +154,7 @@ export async function appendReceiptToSpool(
111
154
  } finally {
112
155
  await handle.close();
113
156
  }
157
+ await cacheHighestCursor(spoolFile, BigInt(cursor));
114
158
  return { cursor, path: spoolFile };
115
159
  },
116
160
  { staleMs: 30_000, retries: 100, retryDelayMs: 25 },