@gajae-code/coding-agent 0.11.0 → 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 (418) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/dist/types/async/job-manager.d.ts +46 -7
  3. package/dist/types/cli/config-cli.d.ts +11 -1
  4. package/dist/types/cli/notify-cli.d.ts +1 -0
  5. package/dist/types/cli/session-picker.d.ts +2 -1
  6. package/dist/types/cli/setup-cli.d.ts +1 -1
  7. package/dist/types/commands/mcp-serve.d.ts +33 -0
  8. package/dist/types/commands/mcp.d.ts +1 -1
  9. package/dist/types/commands/notify.d.ts +0 -2
  10. package/dist/types/commands/sdk.d.ts +1 -1
  11. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  12. package/dist/types/config/file-lock.d.ts +14 -4
  13. package/dist/types/config/keybindings.d.ts +42 -1
  14. package/dist/types/config/model-auth.d.ts +3 -0
  15. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  16. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  17. package/dist/types/config/model-equivalence.d.ts +20 -0
  18. package/dist/types/config/model-registry.d.ts +16 -15
  19. package/dist/types/config/model-resolver.d.ts +28 -18
  20. package/dist/types/config/models-config-schema.d.ts +11 -0
  21. package/dist/types/config/settings-schema.d.ts +56 -1
  22. package/dist/types/config/settings.d.ts +14 -11
  23. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  24. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  26. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  27. package/dist/types/discovery/helpers.d.ts +0 -6
  28. package/dist/types/edit/index.d.ts +3 -0
  29. package/dist/types/eval/py/env.d.ts +9 -0
  30. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  31. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  32. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  33. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  34. package/dist/types/extensibility/shared-events.d.ts +2 -0
  35. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  36. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  37. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  38. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  39. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  40. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  41. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  42. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  43. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  44. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  45. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  46. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  47. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  48. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  49. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  50. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  51. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  52. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  53. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +11 -0
  54. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  55. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  56. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  57. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  58. package/dist/types/goals/runtime.d.ts +1 -0
  59. package/dist/types/goals/state.d.ts +8 -0
  60. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  61. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  62. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  63. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  64. package/dist/types/hindsight/state.d.ts +11 -4
  65. package/dist/types/lsp/lspmux.d.ts +5 -3
  66. package/dist/types/lsp/path-trust.d.ts +1 -0
  67. package/dist/types/main.d.ts +3 -2
  68. package/dist/types/memories/storage.d.ts +1 -0
  69. package/dist/types/modes/action-registry.d.ts +29 -0
  70. package/dist/types/modes/components/chrome.d.ts +14 -0
  71. package/dist/types/modes/components/command-palette.d.ts +27 -0
  72. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  73. package/dist/types/modes/components/index.d.ts +2 -1
  74. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  75. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  76. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  77. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  78. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  79. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  80. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  81. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  82. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  83. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  84. package/dist/types/modes/components/status-line.d.ts +1 -94
  85. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  86. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  87. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  88. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  89. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  90. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  91. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -0
  92. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  93. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  94. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  95. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  96. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  97. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  98. package/dist/types/modes/interactive-mode.d.ts +21 -19
  99. package/dist/types/modes/jobs-observer.d.ts +2 -0
  100. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  101. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +21 -2
  102. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  103. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  104. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  105. package/dist/types/modes/types.d.ts +22 -15
  106. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  107. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  108. package/dist/types/sdk/broker/broker.d.ts +78 -12
  109. package/dist/types/sdk/broker/ensure.d.ts +9 -0
  110. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  111. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  112. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  113. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  114. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  115. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  116. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  117. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  118. package/dist/types/sdk/bus/config.d.ts +11 -0
  119. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  120. package/dist/types/sdk/bus/discord-daemon.d.ts +5 -0
  121. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  122. package/dist/types/sdk/bus/index.d.ts +27 -0
  123. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  124. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  125. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  126. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  127. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  128. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  129. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  130. package/dist/types/sdk/bus/telegram-daemon.d.ts +69 -3
  131. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  132. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  133. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  134. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  135. package/dist/types/sdk/host/host.d.ts +2 -0
  136. package/dist/types/sdk/index.d.ts +1 -0
  137. package/dist/types/sdk/session-directory.d.ts +56 -0
  138. package/dist/types/session/agent-session.d.ts +82 -47
  139. package/dist/types/session/agent-storage.d.ts +7 -0
  140. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  141. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  142. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  143. package/dist/types/session/messages.d.ts +16 -0
  144. package/dist/types/session/session-manager-internal.d.ts +14 -0
  145. package/dist/types/session/session-manager.d.ts +82 -12
  146. package/dist/types/session/session-storage.d.ts +43 -3
  147. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  148. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  149. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  150. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  151. package/dist/types/system-prompt.d.ts +2 -11
  152. package/dist/types/task/executor.d.ts +3 -1
  153. package/dist/types/thinking.d.ts +7 -0
  154. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  155. package/dist/types/tools/ask.d.ts +64 -2
  156. package/dist/types/tools/bash.d.ts +3 -0
  157. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  158. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  159. package/dist/types/tools/cron.d.ts +2 -0
  160. package/dist/types/tools/fetch.d.ts +1 -0
  161. package/dist/types/tools/index.d.ts +30 -15
  162. package/dist/types/tools/output-meta.d.ts +1 -0
  163. package/dist/types/tools/read.d.ts +3 -0
  164. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  165. package/dist/types/tools/subagent-render.d.ts +1 -0
  166. package/dist/types/tools/subagent.d.ts +6 -1
  167. package/dist/types/utils/event-bus.d.ts +1 -1
  168. package/dist/types/web/search/index.d.ts +3 -1
  169. package/package.json +21 -9
  170. package/scripts/build-sdk-package-smoke.ts +39 -1
  171. package/scripts/generate-hotkeys-docs.ts +74 -0
  172. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  173. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  174. package/src/async/job-manager.ts +455 -69
  175. package/src/cli/config-cli.ts +75 -2
  176. package/src/cli/notify-cli.ts +92 -26
  177. package/src/cli/session-picker.ts +20 -3
  178. package/src/cli/setup-cli.ts +1 -1
  179. package/src/cli.ts +1 -1
  180. package/src/commands/config.ts +1 -1
  181. package/src/commands/harness.ts +34 -106
  182. package/src/commands/launch.ts +6 -4
  183. package/src/commands/mcp-serve.ts +85 -8
  184. package/src/commands/mcp.ts +2 -2
  185. package/src/commands/notify.ts +8 -4
  186. package/src/commands/sdk.ts +61 -40
  187. package/src/commands/ultragoal.ts +4 -1
  188. package/src/config/atomic-yaml-patch.ts +80 -13
  189. package/src/config/file-lock.ts +105 -43
  190. package/src/config/keybindings.ts +64 -20
  191. package/src/config/model-auth.ts +9 -0
  192. package/src/config/model-bindings-applier.ts +139 -0
  193. package/src/config/model-discovery-manager.ts +224 -0
  194. package/src/config/model-equivalence.ts +66 -0
  195. package/src/config/model-registry.ts +169 -302
  196. package/src/config/model-resolver.ts +361 -369
  197. package/src/config/models-config-schema.ts +5 -4
  198. package/src/config/settings-schema.ts +159 -2
  199. package/src/config/settings.ts +458 -482
  200. package/src/coordinator-mcp/policy.ts +76 -11
  201. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  202. package/src/coordinator-mcp/question-state.ts +778 -0
  203. package/src/coordinator-mcp/server.ts +1702 -238
  204. package/src/debug/index.ts +1 -1
  205. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  206. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  207. package/src/discovery/helpers.ts +1 -50
  208. package/src/edit/index.ts +64 -0
  209. package/src/eval/py/env.ts +26 -0
  210. package/src/eval/py/kernel.ts +5 -3
  211. package/src/extensibility/extensions/loader.ts +9 -1
  212. package/src/extensibility/extensions/runner.ts +12 -3
  213. package/src/extensibility/extensions/types.ts +39 -1
  214. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  215. package/src/extensibility/hooks/runner.ts +2 -2
  216. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  217. package/src/extensibility/shared-events.ts +2 -0
  218. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  219. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  220. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  221. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  222. package/src/gjc-runtime/launch-tmux.ts +2 -5
  223. package/src/gjc-runtime/launch-worktree.ts +5 -5
  224. package/src/gjc-runtime/linux-proc.ts +71 -0
  225. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  226. package/src/gjc-runtime/session-resolution.ts +18 -3
  227. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  228. package/src/gjc-runtime/state-migrations.ts +1 -1
  229. package/src/gjc-runtime/state-runtime.ts +320 -249
  230. package/src/gjc-runtime/state-writer.ts +36 -40
  231. package/src/gjc-runtime/team-launch.ts +319 -0
  232. package/src/gjc-runtime/team-notify.ts +326 -0
  233. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  234. package/src/gjc-runtime/team-store.ts +828 -0
  235. package/src/gjc-runtime/team-workers.ts +962 -0
  236. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  237. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  238. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  239. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  240. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  241. package/src/gjc-runtime/ultragoal-guard.ts +83 -12
  242. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  243. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +102 -1
  244. package/src/gjc-runtime/ultragoal-runtime.ts +156 -1319
  245. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  246. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  247. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  248. package/src/goals/continuation-timeout-guard.ts +79 -0
  249. package/src/goals/runtime.ts +7 -5
  250. package/src/goals/state.ts +18 -0
  251. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  252. package/src/harness-control-plane/phase-rollup.ts +4 -7
  253. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  254. package/src/harness-control-plane/receipt-spool.ts +51 -7
  255. package/src/harness-control-plane/session-lease.ts +12 -130
  256. package/src/hindsight/backend.ts +26 -27
  257. package/src/hindsight/state.ts +101 -32
  258. package/src/hooks/skill-state.ts +17 -11
  259. package/src/internal-urls/docs-index.generated.ts +21 -18
  260. package/src/lsp/client.ts +1 -1
  261. package/src/lsp/config.ts +145 -26
  262. package/src/lsp/index.ts +1 -1
  263. package/src/lsp/lspmux.ts +39 -10
  264. package/src/lsp/path-trust.ts +81 -0
  265. package/src/main.ts +28 -8
  266. package/src/memories/index.ts +1 -0
  267. package/src/memories/storage.ts +6 -2
  268. package/src/modes/action-registry.ts +165 -0
  269. package/src/modes/components/chrome.ts +40 -0
  270. package/src/modes/components/command-palette.ts +168 -0
  271. package/src/modes/components/custom-editor.ts +1 -1
  272. package/src/modes/components/custom-provider-wizard.ts +51 -14
  273. package/src/modes/components/index.ts +2 -1
  274. package/src/modes/components/pet-selector.ts +11 -17
  275. package/src/modes/components/plan-preview-overlay.ts +295 -0
  276. package/src/modes/components/queue-mode-selector.ts +10 -27
  277. package/src/modes/components/queue-pane.ts +79 -0
  278. package/src/modes/components/read-tool-group.ts +13 -0
  279. package/src/modes/components/session-observer-overlay.ts +207 -772
  280. package/src/modes/components/sessions-dashboard.ts +181 -0
  281. package/src/modes/components/settings-selector.ts +1 -1
  282. package/src/modes/components/show-images-selector.ts +10 -24
  283. package/src/modes/components/status-line/types.ts +1 -1
  284. package/src/modes/components/status-line.ts +1 -969
  285. package/src/modes/components/tasks-pane.ts +73 -0
  286. package/src/modes/components/theme-selector.ts +11 -31
  287. package/src/modes/components/tool-execution.ts +22 -0
  288. package/src/modes/components/tool-status-header.ts +1056 -0
  289. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  290. package/src/modes/controllers/btw-controller.ts +1 -0
  291. package/src/modes/controllers/command-controller.ts +9 -15
  292. package/src/modes/controllers/event-controller.ts +3 -2
  293. package/src/modes/controllers/extension-ui-controller.ts +72 -25
  294. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  295. package/src/modes/controllers/input-controller.ts +764 -229
  296. package/src/modes/controllers/mode-gate.ts +24 -0
  297. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  298. package/src/modes/controllers/selector-controller.ts +225 -10
  299. package/src/modes/controllers/stt-controller.ts +125 -0
  300. package/src/modes/interactive-mode.ts +302 -1151
  301. package/src/modes/jobs-observer.ts +3 -0
  302. package/src/modes/runtime-init.ts +8 -0
  303. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  304. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +78 -19
  305. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  306. package/src/modes/tasks-aggregator.ts +199 -0
  307. package/src/modes/transcript-item-registry.ts +229 -0
  308. package/src/modes/types.ts +47 -20
  309. package/src/modes/utils/ui-helpers.ts +17 -9
  310. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  311. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  312. package/src/prompts/agents/architect.md +2 -8
  313. package/src/prompts/agents/critic.md +2 -8
  314. package/src/prompts/agents/executor.md +3 -0
  315. package/src/prompts/agents/planner.md +4 -12
  316. package/src/prompts/system/custom-system-prompt.md +1 -23
  317. package/src/prompts/system/plan-mode-approved.md +8 -0
  318. package/src/prompts/system/system-prompt.md +17 -96
  319. package/src/prompts/tools/bash.md +4 -4
  320. package/src/prompts/tools/browser.md +15 -52
  321. package/src/prompts/tools/find.md +2 -2
  322. package/src/prompts/tools/hashline.md +13 -55
  323. package/src/prompts/tools/read.md +3 -6
  324. package/src/prompts/tools/search-tool-bm25.md +0 -2
  325. package/src/prompts/tools/search.md +2 -4
  326. package/src/prompts/tools/skill-discovery.md +1 -2
  327. package/src/prompts/tools/task.md +1 -1
  328. package/src/runtime-mcp/manager.ts +1 -1
  329. package/src/sdk/broker/broker.ts +206 -78
  330. package/src/sdk/broker/discovery.ts +21 -2
  331. package/src/sdk/broker/ensure.ts +85 -25
  332. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  333. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  334. package/src/sdk/broker/lifecycle.ts +1399 -142
  335. package/src/sdk/broker/process-incarnation.ts +5 -13
  336. package/src/sdk/broker/session-index.ts +145 -56
  337. package/src/sdk/broker/state-version.ts +21 -0
  338. package/src/sdk/broker/transport.ts +1 -1
  339. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  340. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  341. package/src/sdk/bus/chat-effect-journal.ts +14 -4
  342. package/src/sdk/bus/config-commands.ts +5 -4
  343. package/src/sdk/bus/config.ts +98 -0
  344. package/src/sdk/bus/conversation-store.ts +5 -29
  345. package/src/sdk/bus/daemon-paths.ts +2 -0
  346. package/src/sdk/bus/discord-daemon.ts +25 -8
  347. package/src/sdk/bus/html-format.ts +20 -3
  348. package/src/sdk/bus/index.ts +834 -113
  349. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  350. package/src/sdk/bus/native-runtime-compatibility.ts +1 -1
  351. package/src/sdk/bus/natives-augment.d.ts +26 -1
  352. package/src/sdk/bus/notification-service.ts +168 -20
  353. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  354. package/src/sdk/bus/recent-activity.ts +188 -74
  355. package/src/sdk/bus/rich-document.ts +258 -0
  356. package/src/sdk/bus/rich-render.ts +62 -70
  357. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  358. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  359. package/src/sdk/bus/telegram-daemon.ts +1491 -247
  360. package/src/sdk/bus/threaded-inbound.ts +8 -3
  361. package/src/sdk/bus/threaded-render.ts +57 -1
  362. package/src/sdk/bus/topic-registry.ts +120 -13
  363. package/src/sdk/host/control/dispatch.ts +14 -2
  364. package/src/sdk/host/control/operations.ts +6 -1
  365. package/src/sdk/host/host.ts +31 -1
  366. package/src/sdk/host/query/handlers.ts +20 -22
  367. package/src/sdk/host/query/revision-store.ts +80 -57
  368. package/src/sdk/index.ts +1 -0
  369. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  370. package/src/sdk/session-directory.ts +141 -0
  371. package/src/sdk/session.ts +458 -479
  372. package/src/session/agent-session.ts +1901 -637
  373. package/src/session/agent-storage.ts +18 -0
  374. package/src/session/cache-economics.ts +25 -8
  375. package/src/session/fallback-chain-controller.ts +53 -3
  376. package/src/session/internal/managed-session-scope.ts +2226 -0
  377. package/src/session/internal/managed-session-storage.ts +453 -0
  378. package/src/session/messages.ts +91 -2
  379. package/src/session/session-manager-internal.ts +33 -0
  380. package/src/session/session-manager.ts +1112 -333
  381. package/src/session/session-storage.ts +444 -39
  382. package/src/session/volatile-context-pruning.ts +78 -0
  383. package/src/setup/credential-auto-import.ts +2 -1
  384. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  385. package/src/setup/provider-onboarding.ts +36 -61
  386. package/src/setup/provider-presets.json +5 -0
  387. package/src/skill-state/active-state.ts +3 -13
  388. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  389. package/src/skill-state/workflow-state-contract.ts +6 -41
  390. package/src/slash-commands/builtin-registry.ts +46 -11
  391. package/src/system-prompt.ts +37 -44
  392. package/src/task/agents.ts +19 -1
  393. package/src/task/executor.ts +90 -6
  394. package/src/task/index.ts +62 -37
  395. package/src/thinking.ts +21 -6
  396. package/src/tool-discovery/tool-index.ts +2 -134
  397. package/src/tools/ask.ts +157 -21
  398. package/src/tools/ast-edit.ts +1 -1
  399. package/src/tools/bash.ts +38 -0
  400. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  401. package/src/tools/browser/tab-supervisor.ts +282 -36
  402. package/src/tools/computer.ts +10 -3
  403. package/src/tools/cron.ts +30 -16
  404. package/src/tools/eval.ts +1 -2
  405. package/src/tools/fetch.ts +11 -4
  406. package/src/tools/index.ts +76 -24
  407. package/src/tools/output-meta.ts +13 -5
  408. package/src/tools/read.ts +37 -1
  409. package/src/tools/resource-gc.ts +2 -2
  410. package/src/tools/search-tool-bm25.ts +11 -74
  411. package/src/tools/subagent-render.ts +19 -8
  412. package/src/tools/subagent.ts +84 -32
  413. package/src/utils/event-bus.ts +9 -4
  414. package/src/web/search/index.ts +3 -3
  415. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  416. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  417. package/src/discovery/plugin-dir-roots.ts +0 -28
  418. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -1,3 +1,4 @@
1
+ import { getAgentDir } from "@gajae-code/utils";
1
2
  import { Args, Command, Flags } from "@gajae-code/utils/cli";
2
3
  import {
3
4
  COORDINATOR_MCP_PROTOCOL_VERSION,
@@ -5,12 +6,88 @@ import {
5
6
  COORDINATOR_MCP_TOOL_NAMES,
6
7
  } from "../coordinator/contract";
7
8
  import { runCoordinatorMcpStdio } from "../coordinator-mcp/server";
9
+ import { type BrokerDiscovery, readBrokerDiscovery } from "../sdk/broker/discovery";
10
+ import { UnsupportedStateVersionError } from "../sdk/broker/state-version";
8
11
  import { runSdkMcpStdio, SDK_MCP_TOOL_NAMES } from "../sdk/mcp/server";
9
12
 
10
13
  function writeJson(value: unknown): void {
11
14
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
12
15
  }
13
16
 
17
+ export type CoordinatorBrokerDiscoveryStatus = "ready" | "unavailable" | "error";
18
+ export type CoordinatorBrokerDiscoveryReason =
19
+ | null
20
+ | "absent_or_invalid"
21
+ | "unsupported_state_version"
22
+ | "discovery_access_denied"
23
+ | "discovery_read_failed";
24
+
25
+ export interface CoordinatorBrokerObservation {
26
+ discovery_status: CoordinatorBrokerDiscoveryStatus;
27
+ reason: CoordinatorBrokerDiscoveryReason;
28
+ }
29
+
30
+ export interface CoordinatorCheckProbe {
31
+ agentDir?: string;
32
+ readBrokerDiscovery?: (agentDir: string) => Promise<BrokerDiscovery | null>;
33
+ }
34
+
35
+ export interface CoordinatorCheckPayload {
36
+ ok: true;
37
+ server: { name: typeof COORDINATOR_MCP_SERVER_NAME; protocolVersion: typeof COORDINATOR_MCP_PROTOCOL_VERSION };
38
+ readOnly: true;
39
+ tools: readonly string[];
40
+ catalog: { ready: true; reason: null };
41
+ broker: CoordinatorBrokerObservation & {
42
+ operational_ready: null;
43
+ bootstrap_supported: true;
44
+ bootstrap_attempted: false;
45
+ };
46
+ }
47
+
48
+ function discoveryErrorCode(error: unknown): unknown {
49
+ if (typeof error !== "object" || error === null || !("code" in error)) return undefined;
50
+ return error.code;
51
+ }
52
+
53
+ export async function probeCoordinatorBrokerCheck(
54
+ probe: CoordinatorCheckProbe = {},
55
+ ): Promise<CoordinatorBrokerObservation> {
56
+ try {
57
+ const discovery = await (probe.readBrokerDiscovery ?? readBrokerDiscovery)(probe.agentDir ?? getAgentDir());
58
+ return discovery
59
+ ? { discovery_status: "ready", reason: null }
60
+ : { discovery_status: "unavailable", reason: "absent_or_invalid" };
61
+ } catch (error) {
62
+ if (error instanceof UnsupportedStateVersionError)
63
+ return { discovery_status: "error", reason: "unsupported_state_version" };
64
+ const code = discoveryErrorCode(error);
65
+ if (code === "EACCES" || code === "EPERM")
66
+ return { discovery_status: "error", reason: "discovery_access_denied" };
67
+ return { discovery_status: "error", reason: "discovery_read_failed" };
68
+ }
69
+ }
70
+
71
+ export function formatCoordinatorCheckPayload(observation: CoordinatorBrokerObservation): CoordinatorCheckPayload {
72
+ return {
73
+ ok: true,
74
+ server: { name: COORDINATOR_MCP_SERVER_NAME, protocolVersion: COORDINATOR_MCP_PROTOCOL_VERSION },
75
+ readOnly: true,
76
+ tools: [...COORDINATOR_MCP_TOOL_NAMES],
77
+ catalog: { ready: true, reason: null },
78
+ broker: {
79
+ ...observation,
80
+ operational_ready: null,
81
+ bootstrap_supported: true,
82
+ bootstrap_attempted: false,
83
+ },
84
+ };
85
+ }
86
+
87
+ export async function buildCoordinatorCheckPayload(probe?: CoordinatorCheckProbe): Promise<CoordinatorCheckPayload> {
88
+ return formatCoordinatorCheckPayload(await probeCoordinatorBrokerCheck(probe));
89
+ }
90
+
14
91
  export function validateMcpServeSubcommandForTest(server: string | undefined): void {
15
92
  if (server !== "coordinator" && server !== "hermes" && server !== "sdk")
16
93
  throw new Error(`unknown_mcp_serve_subcommand:${server ?? ""}`);
@@ -46,17 +123,17 @@ export default class McpServe extends Command {
46
123
  }
47
124
 
48
125
  if (flags.check) {
126
+ if (!flags.json) {
127
+ const serverName = server === "sdk" ? "gjc-sdk-mcp" : COORDINATOR_MCP_SERVER_NAME;
128
+ const toolCount = server === "sdk" ? SDK_MCP_TOOL_NAMES.length : COORDINATOR_MCP_TOOL_NAMES.length;
129
+ process.stdout.write(`server: ${serverName}\ntools: ${toolCount}\n`);
130
+ return;
131
+ }
49
132
  const payload =
50
133
  server === "sdk"
51
134
  ? { ok: true, server: { name: "gjc-sdk-mcp" }, readOnly: false, tools: [...SDK_MCP_TOOL_NAMES] }
52
- : {
53
- ok: true,
54
- server: { name: COORDINATOR_MCP_SERVER_NAME, protocolVersion: COORDINATOR_MCP_PROTOCOL_VERSION },
55
- readOnly: true,
56
- tools: [...COORDINATOR_MCP_TOOL_NAMES],
57
- };
58
- if (flags.json) writeJson(payload);
59
- else process.stdout.write(`server: ${payload.server.name}\ntools: ${payload.tools.length}\n`);
135
+ : await buildCoordinatorCheckPayload();
136
+ writeJson(payload);
60
137
  return;
61
138
  }
62
139
 
@@ -53,7 +53,7 @@ export default class MCP extends Command {
53
53
 
54
54
  async run(): Promise<void> {
55
55
  if (this.argv.includes("--help") || this.argv.includes("-h")) {
56
- this.printHelp();
56
+ this.#printHelp();
57
57
  return;
58
58
  }
59
59
 
@@ -80,7 +80,7 @@ export default class MCP extends Command {
80
80
  await runMCPCommand(cmd);
81
81
  }
82
82
 
83
- private printHelp(): void {
83
+ #printHelp(): void {
84
84
  process.stdout.write(`Store standalone MCP server definitions in GJC config without loading them at runtime
85
85
 
86
86
  USAGE
@@ -12,9 +12,8 @@ export default class Notify extends Command {
12
12
 
13
13
  static args = {
14
14
  action: Args.string({
15
- description: "Notify action",
15
+ description: "Notify action (setup|status|health|test|recovery|daemon-internal)",
16
16
  required: false,
17
- options: ACTIONS,
18
17
  }),
19
18
  extra: Args.string({
20
19
  description: "Provider or additional internal args",
@@ -49,7 +48,12 @@ export default class Notify extends Command {
49
48
 
50
49
  async run(): Promise<void> {
51
50
  const { args, flags } = await this.parse(Notify);
52
- const action = (args.action ?? "status") as NotifyAction;
51
+ const action = args.action ?? "status";
52
+ if (!ACTIONS.includes(action as NotifyAction)) {
53
+ console.error(`Unknown notify action: ${action}`);
54
+ console.error(`Valid actions: ${ACTIONS.join(", ")}`);
55
+ process.exit(1);
56
+ }
53
57
  const extra = Array.isArray(args.extra) ? args.extra : args.extra ? [args.extra] : [];
54
58
  const flagRec = flags as Record<string, unknown>;
55
59
  const ownerId = flagRec["owner-id"] as string | undefined;
@@ -72,7 +76,7 @@ export default class Notify extends Command {
72
76
  }
73
77
 
74
78
  const cmd: NotifyCommandArgs = {
75
- action,
79
+ action: action as NotifyAction,
76
80
  smoke: flags.smoke,
77
81
  rawArgs,
78
82
  provider: provider === "telegram" || provider === "discord" || provider === "slack" ? provider : undefined,
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import { Args, Command, Flags } from "@gajae-code/utils/cli";
5
5
  import type { Args as ParsedArgs } from "../cli/args";
6
+ import { Settings } from "../config/settings";
6
7
  import { applyStartupModelProfiles, createSessionManager } from "../main";
7
8
  import { initializeExtensions } from "../modes/runtime-init";
8
9
  import { Broker } from "../sdk/broker/broker";
@@ -16,20 +17,28 @@ import {
16
17
  } from "../sdk/broker/lifecycle";
17
18
  import { processIncarnation } from "../sdk/broker/process-incarnation";
18
19
  import { type CreateLifecycleAgentSessionResult, createLifecycleAgentSession } from "../sdk/lifecycle-session";
20
+ import { listManagedSessionCandidates, resolveManagedSessionScope } from "../sdk/session-directory";
19
21
  import {
20
22
  normalizeSdkStartupFailure,
21
23
  type SdkStartupFailure,
22
24
  type SdkStartupRollbackResult,
23
25
  SdkStartupRollbackTracker,
24
26
  } from "../sdk/startup-capability";
25
-
26
27
  import {
27
28
  type CapturedSessionTranscriptSnapshot,
28
29
  type ResumeSessionIdentity,
29
30
  SessionManager,
30
31
  } from "../session/session-manager";
31
32
 
32
- export function lifecycleArgs(request: SessionLifecycleLaunchRequest, cwd: string, agentDir: string): ParsedArgs {
33
+ export async function lifecycleArgs(
34
+ request: SessionLifecycleLaunchRequest,
35
+ cwd: string,
36
+ agentDir: string,
37
+ ): Promise<ParsedArgs> {
38
+ const targetScope = await resolveManagedSessionScope({ cwd, agentDir });
39
+ if (targetScope.kind !== "resolved") throw new Error(`Lifecycle session scope is invalid: ${targetScope.message}`);
40
+ const forkSessionDir =
41
+ request.operation === "session.fork" ? SessionManager.getDefaultSessionDir(cwd, agentDir) : undefined;
33
42
  return {
34
43
  messages: [],
35
44
  fileArgs: [],
@@ -39,7 +48,7 @@ export function lifecycleArgs(request: SessionLifecycleLaunchRequest, cwd: strin
39
48
  ...(request.operation === "session.fork"
40
49
  ? {
41
50
  fork: request.sourceSessionPath ?? request.sourceSessionId,
42
- sessionDir: SessionManager.getDefaultSessionDirReadOnly(cwd, agentDir),
51
+ sessionDir: forkSessionDir,
43
52
  }
44
53
  : {}),
45
54
  };
@@ -53,7 +62,7 @@ type LifecycleTranscriptSource = {
53
62
  };
54
63
 
55
64
  function sameTranscriptIdentity(
56
- actual: { dev: bigint; ino: bigint; size: number; mtimeMs: number; mtimeNs: bigint },
65
+ actual: { dev: bigint; ino: bigint; size: number; mtimeMs: number; mtimeNs: bigint; sha256: string },
57
66
  expected: SessionLifecycleTranscriptIdentity,
58
67
  ): boolean {
59
68
  return (
@@ -61,7 +70,8 @@ function sameTranscriptIdentity(
61
70
  actual.ino.toString() === expected.ino &&
62
71
  actual.size === expected.size &&
63
72
  actual.mtimeMs === expected.mtimeMs &&
64
- actual.mtimeNs.toString() === expected.mtimeNs
73
+ actual.mtimeNs.toString() === expected.mtimeNs &&
74
+ actual.sha256 === expected.sha256
65
75
  );
66
76
  }
67
77
 
@@ -85,26 +95,38 @@ function lifecycleTranscriptSource(request: SessionLifecycleLaunchRequest, cwd:
85
95
  throw new Error("A new lifecycle session has no persisted transcript authority.");
86
96
  }
87
97
 
88
- function verifyLifecycleTranscript(
98
+ async function captureLifecycleTranscript(
89
99
  request: SessionLifecycleLaunchRequest,
90
100
  cwd: string,
91
101
  agentDir: string,
92
- ): LifecycleTranscriptSource {
102
+ migrationPolicy: "copy-retain" | "disabled",
103
+ ): Promise<CapturedSessionTranscriptSnapshot> {
93
104
  const source = lifecycleTranscriptSource(request, cwd);
94
- const inventory = SessionManager.inventorySessionsStrict(source.cwd, {
95
- sessionDir: SessionManager.getDefaultSessionDir(source.cwd, agentDir),
96
- });
105
+ const scope = await resolveManagedSessionScope({ cwd: source.cwd, agentDir });
106
+ if (scope.kind !== "resolved")
107
+ throw new Error("Lifecycle saved session storage could not be verified for the requested workspace.");
108
+ const inventory = await listManagedSessionCandidates({ scope: scope.scope });
97
109
  if (inventory.kind !== "complete")
98
110
  throw new Error("Lifecycle saved session storage could not be verified for the requested workspace.");
99
- const matches = inventory.candidates.filter(
111
+ const captured = SessionManager.captureTranscriptStrict(source.path);
112
+ if (
113
+ captured.kind !== "captured" ||
114
+ captured.snapshot.sourcePath !== path.resolve(source.path) ||
115
+ captured.snapshot.identity.sessionId !== source.id ||
116
+ !sameTranscriptIdentity(captured.snapshot.identity, source.identity)
117
+ )
118
+ throw new Error("Lifecycle saved session authority changed before the session host consumed it.");
119
+ const matches = inventory.owned.filter(
100
120
  candidate =>
101
- candidate.path === path.resolve(source.path) &&
102
- candidate.id === source.id &&
103
- sameTranscriptIdentity(candidate.identity, source.identity),
121
+ candidate.path === captured.snapshot.sourcePath &&
122
+ candidate.sessionId === source.id &&
123
+ sameLifecycleTranscriptSnapshot(candidate.identity, captured.snapshot.identity),
104
124
  );
105
125
  if (matches.length !== 1)
106
126
  throw new Error("Lifecycle saved session authority changed before the session host started.");
107
- return source;
127
+ if (matches[0]!.provenance === "legacy" && migrationPolicy === "disabled")
128
+ throw new Error("Lifecycle legacy session migration is disabled by policy.");
129
+ return captured.snapshot;
108
130
  }
109
131
 
110
132
  function sameLifecycleTranscriptSnapshot(left: ResumeSessionIdentity, right: ResumeSessionIdentity): boolean {
@@ -120,23 +142,6 @@ function sameLifecycleTranscriptSnapshot(left: ResumeSessionIdentity, right: Res
120
142
  );
121
143
  }
122
144
 
123
- function captureLifecycleTranscript(
124
- request: SessionLifecycleLaunchRequest,
125
- cwd: string,
126
- agentDir: string,
127
- ): CapturedSessionTranscriptSnapshot {
128
- const source = verifyLifecycleTranscript(request, cwd, agentDir);
129
- const captured = SessionManager.captureTranscriptStrict(source.path);
130
- if (
131
- captured.kind !== "captured" ||
132
- captured.snapshot.sourcePath !== path.resolve(source.path) ||
133
- captured.snapshot.identity.sessionId !== source.id ||
134
- !sameTranscriptIdentity(captured.snapshot.identity, source.identity)
135
- )
136
- throw new Error("Lifecycle saved session authority changed before the session host consumed it.");
137
- return captured.snapshot;
138
- }
139
-
140
145
  async function revalidateLifecycleTranscript(snapshot: ResumeSessionIdentity): Promise<void> {
141
146
  const inspected = await SessionManager.inspectSessionTailReadOnly(snapshot.canonicalPath);
142
147
  if (inspected.kind === "error" || !sameLifecycleTranscriptSnapshot(snapshot, inspected.identity))
@@ -149,14 +154,22 @@ export async function openLifecycleSessionManager(
149
154
  cwd: string,
150
155
  agentDir: string,
151
156
  ): Promise<{ parsed: ParsedArgs; sessionManager: SessionManager | undefined }> {
152
- const parsed = lifecycleArgs(request, cwd, agentDir);
157
+ const parsed = await lifecycleArgs(request, cwd, agentDir);
158
+ const lifecycleSettings = await Settings.loadForScope({ cwd, agentDir });
159
+ const migrationPolicy =
160
+ lifecycleSettings.get("session.directoryMigration") === "disabled" ? "disabled" : "copy-retain";
153
161
  if (request.operation === "session.create") {
154
- return { parsed, sessionManager: await createSessionManager(parsed, cwd) };
162
+ return { parsed, sessionManager: await createSessionManager(parsed, cwd, lifecycleSettings) };
155
163
  }
156
- const snapshot = captureLifecycleTranscript(request, cwd, agentDir);
164
+ const snapshot = await captureLifecycleTranscript(request, cwd, agentDir, migrationPolicy);
157
165
  let sessionManager: SessionManager | undefined;
158
166
  if (request.operation === "session.resume") {
159
- const opened = await SessionManager.openExistingStrict(snapshot.identity, parsed.sessionDir);
167
+ const opened = await SessionManager.openExistingStrict(
168
+ snapshot.identity,
169
+ parsed.sessionDir,
170
+ undefined,
171
+ migrationPolicy,
172
+ );
160
173
  if (opened.kind === "error")
161
174
  throw new Error("Lifecycle saved session authority changed while the session host opened it.");
162
175
  sessionManager = opened.manager;
@@ -167,7 +180,7 @@ export async function openLifecycleSessionManager(
167
180
  throw error;
168
181
  }
169
182
  } else {
170
- const forked = await SessionManager.forkFromCaptured(snapshot, cwd, parsed.sessionDir);
183
+ const forked = await SessionManager.forkFromCaptured(snapshot, cwd, parsed.sessionDir, migrationPolicy);
171
184
  if (forked.kind === "error")
172
185
  throw new Error("Lifecycle saved session authority changed while the session host forked it.");
173
186
  sessionManager = forked.manager;
@@ -302,21 +315,23 @@ export async function runSessionHost(
302
315
  fs.readFile(transcriptPath),
303
316
  fs.stat(transcriptPath, { bigint: true }),
304
317
  ]);
318
+ const digest = createHash("sha256").update(bytes).digest("hex");
305
319
  return {
306
- digest: createHash("sha256").update(bytes).digest("hex"),
320
+ digest,
307
321
  identity: {
308
322
  dev: stat.dev.toString(),
309
323
  ino: stat.ino.toString(),
310
324
  size: Number(stat.size),
311
325
  mtimeMs: Number(stat.mtimeMs),
312
326
  mtimeNs: stat.mtimeNs.toString(),
327
+ sha256: digest,
313
328
  },
314
329
  };
315
330
  } catch {
316
331
  return undefined;
317
332
  }
318
333
  })();
319
- return disposal;
334
+ return disposal ?? Promise.resolve(undefined);
320
335
  };
321
336
  let failureRollback: Promise<void> | undefined;
322
337
  const failAfterRollback = (failure: SdkStartupFailure): Promise<void> => {
@@ -415,7 +430,13 @@ export default class Sdk extends Command {
415
430
  }
416
431
  const agentDir = flags["agent-dir"] as string | undefined;
417
432
  if (!agentDir) throw new Error("--agent-dir is required for sdk broker-internal.");
418
- const broker = new Broker({ agentDir });
433
+ const broker = new Broker({
434
+ agentDir,
435
+ resolveDirectoryMigration: async cwd => {
436
+ const policy = (await Settings.loadForScope({ cwd, agentDir })).get("session.directoryMigration");
437
+ return policy === "disabled" ? "disabled" : "copy-retain";
438
+ },
439
+ });
419
440
  await broker.start();
420
441
  if (!broker.ownsDiscovery) return;
421
442
  const stop = () => void broker.stop().finally(() => process.exit(0));
@@ -26,15 +26,18 @@ export default class Ultragoal extends Command {
26
26
  if (isReviewStart && !result.createdReviewPlan && (result.reviewBlockerGoalIds?.length ?? 0) === 0) return;
27
27
 
28
28
  const cwd = process.cwd();
29
- const { objective, goalsPath } = await readUltragoalGjcObjective(cwd);
29
+ const { objective, goalsPath, provenance } = await readUltragoalGjcObjective(cwd);
30
+
30
31
  await writeCurrentSessionGoalModeState({
31
32
  sessionFile: process.env[GJC_SESSION_FILE_ENV],
32
33
  objective,
34
+ provenance,
33
35
  });
34
36
  await writePendingGoalModeRequest({
35
37
  cwd,
36
38
  objective,
37
39
  goalsPath,
40
+ provenance,
38
41
  sessionId: process.env[GJC_SESSION_ID_ENV],
39
42
  });
40
43
  }
@@ -4,19 +4,40 @@ import * as path from "node:path";
4
4
  import { YAML } from "bun";
5
5
  import { withFileLock } from "./file-lock";
6
6
 
7
+ export interface AtomicYamlExpectedPrecondition {
8
+ path: string;
9
+ hash: string;
10
+ }
11
+
7
12
  export interface AtomicYamlSetPatch {
8
13
  path: string;
9
14
  op: "set";
10
15
  value: unknown;
16
+ expected?: AtomicYamlExpectedPrecondition;
11
17
  }
12
18
 
13
19
  export interface AtomicYamlUnsetPatch {
14
20
  path: string;
15
21
  op: "unset";
22
+ expected?: AtomicYamlExpectedPrecondition;
16
23
  }
17
24
 
18
25
  export type AtomicYamlPatch = AtomicYamlSetPatch | AtomicYamlUnsetPatch;
19
26
 
27
+ /** Raised when a compare-and-swap precondition no longer matches durable YAML. */
28
+ export class AtomicYamlConflictError extends Error {
29
+ readonly code = "ATOMIC_YAML_CONFLICT";
30
+
31
+ constructor(
32
+ readonly path: string,
33
+ readonly expectedHash: string,
34
+ readonly actualHash: string,
35
+ ) {
36
+ super(`Atomic YAML precondition failed for ${path}.`);
37
+ this.name = "AtomicYamlConflictError";
38
+ }
39
+ }
40
+
20
41
  export interface AtomicYamlPatchRevision {
21
42
  path: string;
22
43
  beforeHash: string;
@@ -53,6 +74,8 @@ export interface AtomicYamlPatchOptions {
53
74
  sleep?: (ms: number) => Promise<void>;
54
75
  /** Test seam for Windows rename retry behavior. */
55
76
  platform?: NodeJS.Platform;
77
+ /** Called under the config lock before patches are applied. */
78
+ validateRoot?: (root: unknown, patches: readonly AtomicYamlPatch[]) => void | Promise<void>;
56
79
  /** Called under the config lock after a successful CAS restore. */
57
80
  onRestored?: (patches: readonly AtomicYamlPatch[]) => void | Promise<void>;
58
81
  }
@@ -98,6 +121,16 @@ function assertPatch(patch: AtomicYamlPatch): void {
98
121
  ) {
99
122
  throw new Error("Atomic YAML patches require a non-empty dotted path.");
100
123
  }
124
+ if (
125
+ patch.expected &&
126
+ (typeof patch.expected.path !== "string" ||
127
+ patch.expected.path.length === 0 ||
128
+ patch.expected.path.split(".").some(part => !part) ||
129
+ typeof patch.expected.hash !== "string" ||
130
+ patch.expected.hash.length === 0)
131
+ ) {
132
+ throw new Error("Atomic YAML patch preconditions require a non-empty dotted path and hash.");
133
+ }
101
134
  if (patch.op === "set") {
102
135
  if (patch.value === undefined) {
103
136
  throw new TypeError(`Atomic YAML set patch for ${patch.path} cannot carry undefined; use unset instead.`);
@@ -148,6 +181,12 @@ export function deleteByPath(value: Record<string, unknown>, segments: readonly
148
181
  }
149
182
 
150
183
  function stableValue(value: unknown): string {
184
+ if (typeof value === "number") {
185
+ if (Number.isNaN(value)) return "NaN";
186
+ if (value === Infinity) return "Infinity";
187
+ if (value === -Infinity) return "-Infinity";
188
+ if (Object.is(value, -0)) return "-0";
189
+ }
151
190
  if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
152
191
  if (Array.isArray(value)) return `[${value.map(stableValue).join(",")}]`;
153
192
  const object = value as Record<string, unknown>;
@@ -167,12 +206,22 @@ function cloneState(state: PathState): PathState {
167
206
  return state.exists ? { exists: true, value: structuredClone(state.value) } : state;
168
207
  }
169
208
 
170
- async function readYaml(configPath: string): Promise<Record<string, unknown>> {
209
+ /** Hash a dotted YAML path state for an expected-hash patch precondition. */
210
+ export function atomicYamlPathHash(value: Record<string, unknown>, path: string): string {
211
+ return stateHash(stateAtPath(value, path.split(".")));
212
+ }
213
+
214
+ type YamlReadResult = {
215
+ current: Record<string, unknown>;
216
+ root: unknown;
217
+ };
218
+
219
+ async function readYaml(configPath: string): Promise<YamlReadResult> {
171
220
  try {
172
- const parsed = YAML.parse(await fs.readFile(configPath, "utf8"));
173
- return record(parsed) ?? {};
221
+ const root = YAML.parse(await fs.readFile(configPath, "utf8"));
222
+ return { current: record(root) ?? {}, root };
174
223
  } catch (error) {
175
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
224
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return { current: {}, root: undefined };
176
225
  throw error;
177
226
  }
178
227
  }
@@ -254,8 +303,9 @@ function createReceipt(
254
303
  async restore(): Promise<CasRestoreResult> {
255
304
  if (discarded) return { status: "discarded" };
256
305
  return await enqueueAtomicYamlOperation(configPath, async canonicalPath => {
306
+ if (discarded) return { status: "discarded" };
257
307
  return await withFileLock(canonicalPath, async () => {
258
- const current = await readYaml(canonicalPath);
308
+ const { current, root } = await readYaml(canonicalPath);
259
309
  const conflicts = changes
260
310
  .filter(
261
311
  change =>
@@ -269,6 +319,7 @@ function createReceipt(
269
319
  ? { path: change.path, op: "set", value: structuredClone(change.before.value) }
270
320
  : { path: change.path, op: "unset" },
271
321
  );
322
+ await options.validateRoot?.(root, restorePatches);
272
323
  const receipt = await applyPatchesUnderLock(canonicalPath, current, restorePatches, options);
273
324
  await options.onRestored?.(restorePatches);
274
325
  return { status: "restored", receipt };
@@ -286,6 +337,14 @@ async function applyPatchesUnderLock(
286
337
  ): Promise<CasReceipt> {
287
338
  if (patches.length === 0) return createReceipt(configPath, [], options);
288
339
 
340
+ for (const patch of patches) {
341
+ if (!patch.expected) continue;
342
+ const actualHash = stateHash(stateAtPath(current, patch.expected.path.split(".")));
343
+ if (actualHash !== patch.expected.hash) {
344
+ throw new AtomicYamlConflictError(patch.expected.path, patch.expected.hash, actualHash);
345
+ }
346
+ }
347
+
289
348
  const changesByPath = new Map<string, ReceiptChange>();
290
349
  for (const patch of patches) {
291
350
  const segments = patch.path.split(".");
@@ -329,9 +388,10 @@ export function applyAtomicYamlPatchesWithCurrent(
329
388
  return enqueueAtomicYamlOperation(configPath, async canonicalPath => {
330
389
  await fs.mkdir(path.dirname(canonicalPath), { recursive: true, mode: 0o700 });
331
390
  return await withFileLock(canonicalPath, async () => {
332
- const current = await readYaml(canonicalPath);
391
+ const { current, root } = await readYaml(canonicalPath);
333
392
  const patches = await buildPatches(current);
334
393
  for (const patch of patches) assertPatch(patch);
394
+ await options.validateRoot?.(root, patches);
335
395
  return await applyPatchesUnderLock(canonicalPath, current, patches, options);
336
396
  });
337
397
  });
@@ -374,7 +434,8 @@ export function reserveAtomicYamlPatchSlot(
374
434
  for (const patch of nextPatches) assertPatch(patch);
375
435
  await fs.mkdir(path.dirname(canonicalPath), { recursive: true, mode: 0o700 });
376
436
  return await withFileLock(canonicalPath, async () => {
377
- const current = await readYaml(canonicalPath);
437
+ const { current, root } = await readYaml(canonicalPath);
438
+ await options.validateRoot?.(root, nextPatches);
378
439
  return await applyPatchesUnderLock(canonicalPath, current, nextPatches, options);
379
440
  });
380
441
  });
@@ -393,7 +454,7 @@ export function reserveAtomicYamlUpdateSlot<T>(
393
454
  const atomicUpdate = await update();
394
455
  await fs.mkdir(path.dirname(canonicalPath), { recursive: true, mode: 0o700 });
395
456
  return await withFileLock(canonicalPath, async () => {
396
- const current = await readYaml(canonicalPath);
457
+ const { current } = await readYaml(canonicalPath);
397
458
  const result = await atomicUpdate.apply(current);
398
459
  if (atomicUpdate.shouldWrite?.(result) !== false) {
399
460
  await writeAtomicYaml(canonicalPath, current, options);
@@ -414,10 +475,16 @@ export function applyAtomicYamlPatches(
414
475
  options: AtomicYamlPatchOptions = {},
415
476
  ): Promise<CasReceipt> {
416
477
  for (const patch of patches) assertPatch(patch);
417
- const immutablePatches = patches.map(patch =>
418
- patch.op === "set"
419
- ? ({ path: patch.path, op: "set", value: structuredClone(patch.value) } as const)
420
- : ({ path: patch.path, op: "unset" } as const),
421
- );
478
+ const immutablePatches = patches.map(patch => {
479
+ const expected = patch.expected ? { ...patch.expected } : undefined;
480
+ return patch.op === "set"
481
+ ? ({
482
+ path: patch.path,
483
+ op: "set",
484
+ value: structuredClone(patch.value),
485
+ ...(expected ? { expected } : {}),
486
+ } as const)
487
+ : ({ path: patch.path, op: "unset", ...(expected ? { expected } : {}) } as const);
488
+ });
422
489
  return reserveAtomicYamlPatchSlot(configPath, () => immutablePatches, options);
423
490
  }