@gajae-code/coding-agent 0.11.1 → 0.11.3

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 (413) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/launch-disposition.d.ts +23 -0
  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 +141 -11
  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-runtime.d.ts +55 -33
  54. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  55. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  56. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  57. package/dist/types/goals/runtime.d.ts +1 -0
  58. package/dist/types/goals/state.d.ts +8 -0
  59. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  60. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  61. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  62. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  63. package/dist/types/hindsight/state.d.ts +11 -4
  64. package/dist/types/lsp/lspmux.d.ts +5 -3
  65. package/dist/types/lsp/path-trust.d.ts +1 -0
  66. package/dist/types/main.d.ts +4 -2
  67. package/dist/types/memories/storage.d.ts +1 -0
  68. package/dist/types/modes/action-registry.d.ts +29 -0
  69. package/dist/types/modes/components/chrome.d.ts +14 -0
  70. package/dist/types/modes/components/command-palette.d.ts +27 -0
  71. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  72. package/dist/types/modes/components/index.d.ts +2 -1
  73. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  74. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  75. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  76. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  77. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  78. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  79. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  80. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  81. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  82. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  83. package/dist/types/modes/components/status-line.d.ts +1 -94
  84. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  85. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  86. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  87. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  88. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  89. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  90. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  91. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  92. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  93. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  94. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  95. package/dist/types/modes/interactive-mode.d.ts +19 -17
  96. package/dist/types/modes/jobs-observer.d.ts +2 -0
  97. package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
  98. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  99. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  100. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  101. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  102. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  103. package/dist/types/modes/types.d.ts +20 -13
  104. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  105. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  106. package/dist/types/sdk/broker/broker.d.ts +78 -12
  107. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  108. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  109. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  110. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  111. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  112. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  113. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  114. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  117. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  118. package/dist/types/sdk/bus/index.d.ts +29 -0
  119. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  120. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  121. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  122. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  123. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  124. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  125. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  126. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  127. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  128. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  129. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  130. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  131. package/dist/types/sdk/host/host.d.ts +2 -0
  132. package/dist/types/sdk/index.d.ts +1 -0
  133. package/dist/types/sdk/session-directory.d.ts +56 -0
  134. package/dist/types/sdk/session.d.ts +2 -0
  135. package/dist/types/session/agent-session.d.ts +82 -47
  136. package/dist/types/session/agent-storage.d.ts +7 -0
  137. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  138. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  139. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  140. package/dist/types/session/messages.d.ts +16 -0
  141. package/dist/types/session/session-manager-internal.d.ts +14 -0
  142. package/dist/types/session/session-manager.d.ts +82 -12
  143. package/dist/types/session/session-storage.d.ts +43 -3
  144. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  145. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  146. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  147. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  148. package/dist/types/system-prompt.d.ts +2 -11
  149. package/dist/types/task/executor.d.ts +3 -1
  150. package/dist/types/thinking.d.ts +7 -0
  151. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  152. package/dist/types/tools/ask.d.ts +64 -2
  153. package/dist/types/tools/bash.d.ts +3 -0
  154. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  155. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  156. package/dist/types/tools/cron.d.ts +2 -0
  157. package/dist/types/tools/fetch.d.ts +1 -0
  158. package/dist/types/tools/index.d.ts +30 -15
  159. package/dist/types/tools/output-meta.d.ts +1 -0
  160. package/dist/types/tools/read.d.ts +5 -0
  161. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  162. package/dist/types/tools/subagent.d.ts +1 -0
  163. package/dist/types/utils/event-bus.d.ts +1 -1
  164. package/dist/types/utils/file-mentions.d.ts +3 -3
  165. package/dist/types/web/search/index.d.ts +3 -1
  166. package/package.json +21 -9
  167. package/scripts/build-sdk-package-smoke.ts +34 -1
  168. package/scripts/generate-hotkeys-docs.ts +74 -0
  169. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  170. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  171. package/src/async/job-manager.ts +49 -6
  172. package/src/cli/config-cli.ts +75 -2
  173. package/src/cli/launch-disposition.ts +42 -0
  174. package/src/cli/notify-cli.ts +92 -26
  175. package/src/cli/session-picker.ts +20 -3
  176. package/src/cli/setup-cli.ts +1 -1
  177. package/src/cli.ts +1 -1
  178. package/src/commands/config.ts +1 -1
  179. package/src/commands/harness.ts +34 -106
  180. package/src/commands/launch.ts +6 -4
  181. package/src/commands/mcp-serve.ts +85 -8
  182. package/src/commands/mcp.ts +2 -2
  183. package/src/commands/notify.ts +8 -4
  184. package/src/commands/sdk.ts +61 -40
  185. package/src/commands/ultragoal.ts +4 -1
  186. package/src/config/atomic-yaml-patch.ts +80 -13
  187. package/src/config/file-lock.ts +105 -43
  188. package/src/config/keybindings.ts +64 -20
  189. package/src/config/model-auth.ts +9 -0
  190. package/src/config/model-bindings-applier.ts +139 -0
  191. package/src/config/model-discovery-manager.ts +224 -0
  192. package/src/config/model-equivalence.ts +66 -0
  193. package/src/config/model-registry.ts +169 -302
  194. package/src/config/model-resolver.ts +361 -369
  195. package/src/config/models-config-schema.ts +5 -4
  196. package/src/config/settings-schema.ts +214 -12
  197. package/src/config/settings.ts +458 -482
  198. package/src/coordinator-mcp/policy.ts +76 -11
  199. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  200. package/src/coordinator-mcp/question-state.ts +778 -0
  201. package/src/coordinator-mcp/server.ts +1702 -238
  202. package/src/debug/index.ts +1 -1
  203. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  204. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  205. package/src/discovery/helpers.ts +1 -50
  206. package/src/edit/index.ts +64 -0
  207. package/src/eval/py/env.ts +26 -0
  208. package/src/eval/py/kernel.ts +5 -3
  209. package/src/extensibility/extensions/loader.ts +9 -1
  210. package/src/extensibility/extensions/runner.ts +12 -3
  211. package/src/extensibility/extensions/types.ts +39 -1
  212. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  213. package/src/extensibility/hooks/runner.ts +2 -2
  214. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  215. package/src/extensibility/shared-events.ts +2 -0
  216. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  217. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  218. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  219. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  220. package/src/gjc-runtime/launch-tmux.ts +2 -5
  221. package/src/gjc-runtime/launch-worktree.ts +5 -5
  222. package/src/gjc-runtime/linux-proc.ts +71 -0
  223. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  224. package/src/gjc-runtime/session-resolution.ts +18 -3
  225. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  226. package/src/gjc-runtime/state-migrations.ts +1 -1
  227. package/src/gjc-runtime/state-runtime.ts +327 -249
  228. package/src/gjc-runtime/state-writer.ts +36 -40
  229. package/src/gjc-runtime/team-launch.ts +319 -0
  230. package/src/gjc-runtime/team-notify.ts +326 -0
  231. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  232. package/src/gjc-runtime/team-store.ts +828 -0
  233. package/src/gjc-runtime/team-workers.ts +962 -0
  234. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  235. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  236. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  237. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  238. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  239. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  240. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  241. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  242. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  243. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  244. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  245. package/src/goals/continuation-timeout-guard.ts +79 -0
  246. package/src/goals/runtime.ts +7 -5
  247. package/src/goals/state.ts +18 -0
  248. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  249. package/src/harness-control-plane/phase-rollup.ts +4 -7
  250. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  251. package/src/harness-control-plane/receipt-spool.ts +51 -7
  252. package/src/harness-control-plane/session-lease.ts +12 -130
  253. package/src/hindsight/backend.ts +26 -27
  254. package/src/hindsight/state.ts +101 -32
  255. package/src/hooks/skill-state.ts +17 -11
  256. package/src/internal-urls/docs-index.generated.ts +21 -18
  257. package/src/lsp/client.ts +1 -1
  258. package/src/lsp/config.ts +145 -26
  259. package/src/lsp/index.ts +1 -1
  260. package/src/lsp/lspmux.ts +39 -10
  261. package/src/lsp/path-trust.ts +81 -0
  262. package/src/main.ts +127 -30
  263. package/src/memories/index.ts +1 -0
  264. package/src/memories/storage.ts +6 -2
  265. package/src/modes/action-registry.ts +165 -0
  266. package/src/modes/components/chrome.ts +40 -0
  267. package/src/modes/components/command-palette.ts +168 -0
  268. package/src/modes/components/custom-editor.ts +1 -1
  269. package/src/modes/components/custom-provider-wizard.ts +51 -14
  270. package/src/modes/components/index.ts +2 -1
  271. package/src/modes/components/pet-selector.ts +11 -17
  272. package/src/modes/components/plan-preview-overlay.ts +295 -0
  273. package/src/modes/components/queue-mode-selector.ts +10 -27
  274. package/src/modes/components/queue-pane.ts +79 -0
  275. package/src/modes/components/read-tool-group.ts +13 -0
  276. package/src/modes/components/session-observer-overlay.ts +207 -772
  277. package/src/modes/components/sessions-dashboard.ts +181 -0
  278. package/src/modes/components/settings-selector.ts +1 -1
  279. package/src/modes/components/show-images-selector.ts +10 -24
  280. package/src/modes/components/status-line/types.ts +1 -1
  281. package/src/modes/components/status-line.ts +1 -969
  282. package/src/modes/components/tasks-pane.ts +73 -0
  283. package/src/modes/components/theme-selector.ts +11 -31
  284. package/src/modes/components/tool-execution.ts +22 -0
  285. package/src/modes/components/tool-status-header.ts +1056 -0
  286. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  287. package/src/modes/controllers/btw-controller.ts +1 -0
  288. package/src/modes/controllers/event-controller.ts +3 -2
  289. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  290. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  291. package/src/modes/controllers/input-controller.ts +764 -229
  292. package/src/modes/controllers/mode-gate.ts +24 -0
  293. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  294. package/src/modes/controllers/selector-controller.ts +225 -10
  295. package/src/modes/controllers/stt-controller.ts +125 -0
  296. package/src/modes/interactive-mode.ts +289 -1150
  297. package/src/modes/jobs-observer.ts +3 -0
  298. package/src/modes/prompt-action-autocomplete.ts +7 -0
  299. package/src/modes/runtime-init.ts +8 -0
  300. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  301. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  302. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  303. package/src/modes/tasks-aggregator.ts +199 -0
  304. package/src/modes/transcript-item-registry.ts +229 -0
  305. package/src/modes/types.ts +45 -18
  306. package/src/modes/utils/ui-helpers.ts +17 -9
  307. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  308. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  309. package/src/prompts/agents/architect.md +2 -8
  310. package/src/prompts/agents/critic.md +2 -8
  311. package/src/prompts/agents/executor.md +3 -0
  312. package/src/prompts/agents/planner.md +4 -12
  313. package/src/prompts/system/custom-system-prompt.md +1 -23
  314. package/src/prompts/system/plan-mode-approved.md +8 -0
  315. package/src/prompts/system/system-prompt.md +17 -96
  316. package/src/prompts/tools/bash.md +4 -4
  317. package/src/prompts/tools/browser.md +15 -52
  318. package/src/prompts/tools/find.md +2 -2
  319. package/src/prompts/tools/hashline.md +13 -55
  320. package/src/prompts/tools/read.md +3 -6
  321. package/src/prompts/tools/search-tool-bm25.md +0 -2
  322. package/src/prompts/tools/search.md +2 -4
  323. package/src/prompts/tools/skill-discovery.md +1 -2
  324. package/src/prompts/tools/task.md +1 -1
  325. package/src/runtime-mcp/manager.ts +1 -1
  326. package/src/sdk/broker/broker.ts +206 -78
  327. package/src/sdk/broker/discovery.ts +1 -1
  328. package/src/sdk/broker/ensure.ts +3 -1
  329. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  330. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  331. package/src/sdk/broker/lifecycle.ts +1399 -142
  332. package/src/sdk/broker/process-incarnation.ts +5 -13
  333. package/src/sdk/broker/session-index.ts +145 -56
  334. package/src/sdk/broker/state-version.ts +21 -0
  335. package/src/sdk/broker/transport.ts +1 -1
  336. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  337. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  338. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  339. package/src/sdk/bus/config-commands.ts +5 -4
  340. package/src/sdk/bus/config.ts +98 -0
  341. package/src/sdk/bus/conversation-store.ts +5 -29
  342. package/src/sdk/bus/daemon-paths.ts +2 -0
  343. package/src/sdk/bus/html-format.ts +20 -3
  344. package/src/sdk/bus/index.ts +782 -94
  345. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  346. package/src/sdk/bus/natives-augment.d.ts +26 -1
  347. package/src/sdk/bus/notification-service.ts +168 -20
  348. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  349. package/src/sdk/bus/recent-activity.ts +188 -74
  350. package/src/sdk/bus/rich-document.ts +258 -0
  351. package/src/sdk/bus/rich-render.ts +62 -70
  352. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  353. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  354. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  355. package/src/sdk/bus/threaded-inbound.ts +8 -3
  356. package/src/sdk/bus/threaded-render.ts +57 -1
  357. package/src/sdk/bus/topic-registry.ts +120 -13
  358. package/src/sdk/host/control/dispatch.ts +14 -2
  359. package/src/sdk/host/control/operations.ts +6 -1
  360. package/src/sdk/host/host.ts +44 -2
  361. package/src/sdk/host/query/handlers.ts +20 -22
  362. package/src/sdk/host/query/revision-store.ts +80 -57
  363. package/src/sdk/index.ts +1 -0
  364. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  365. package/src/sdk/session-directory.ts +141 -0
  366. package/src/sdk/session.ts +462 -480
  367. package/src/session/agent-session.ts +1700 -583
  368. package/src/session/agent-storage.ts +18 -0
  369. package/src/session/cache-economics.ts +25 -8
  370. package/src/session/fallback-chain-controller.ts +53 -3
  371. package/src/session/internal/managed-session-scope.ts +2226 -0
  372. package/src/session/internal/managed-session-storage.ts +453 -0
  373. package/src/session/messages.ts +91 -2
  374. package/src/session/session-manager-internal.ts +33 -0
  375. package/src/session/session-manager.ts +1112 -333
  376. package/src/session/session-storage.ts +444 -39
  377. package/src/session/volatile-context-pruning.ts +78 -0
  378. package/src/setup/credential-auto-import.ts +2 -1
  379. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  380. package/src/setup/provider-onboarding.ts +36 -61
  381. package/src/setup/provider-presets.json +5 -0
  382. package/src/skill-state/active-state.ts +3 -13
  383. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  384. package/src/skill-state/workflow-state-contract.ts +6 -41
  385. package/src/slash-commands/builtin-registry.ts +46 -11
  386. package/src/system-prompt.ts +37 -44
  387. package/src/task/agents.ts +19 -1
  388. package/src/task/executor.ts +90 -6
  389. package/src/task/index.ts +36 -21
  390. package/src/thinking.ts +21 -6
  391. package/src/tool-discovery/tool-index.ts +2 -134
  392. package/src/tools/ask.ts +157 -21
  393. package/src/tools/ast-edit.ts +1 -1
  394. package/src/tools/bash.ts +38 -0
  395. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  396. package/src/tools/browser/tab-supervisor.ts +282 -36
  397. package/src/tools/computer.ts +10 -3
  398. package/src/tools/cron.ts +30 -16
  399. package/src/tools/eval.ts +1 -2
  400. package/src/tools/fetch.ts +11 -4
  401. package/src/tools/index.ts +76 -24
  402. package/src/tools/output-meta.ts +19 -7
  403. package/src/tools/read.ts +219 -64
  404. package/src/tools/resource-gc.ts +2 -2
  405. package/src/tools/search-tool-bm25.ts +11 -74
  406. package/src/tools/subagent.ts +43 -2
  407. package/src/utils/event-bus.ts +9 -4
  408. package/src/utils/file-mentions.ts +4 -4
  409. package/src/web/search/index.ts +3 -3
  410. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  411. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  412. package/src/discovery/plugin-dir-roots.ts +0 -28
  413. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -179,6 +179,8 @@ export interface AgentStartEvent {
179
179
  export interface AgentEndEvent {
180
180
  type: "agent_end";
181
181
  messages: AgentMessage[];
182
+ /** Indicates whether the loop ended normally, suspended, cancelled, or entered maintenance. */
183
+ stopReason?: "completed" | "paused" | "cancelled" | "maintenance";
182
184
  }
183
185
 
184
186
  /** Fired at the start of each turn */
@@ -8,13 +8,18 @@ import {
8
8
  } from "./deep-interview-ambiguity";
9
9
  import {
10
10
  answerHash,
11
+ assertDeepInterviewIntentManifest,
12
+ createDeepInterviewIntentManifest,
11
13
  type DeepInterviewEstablishedFact,
14
+ type DeepInterviewIntentItem,
15
+ type DeepInterviewIntentSubstitution,
12
16
  type DeepInterviewRoundRecord,
13
17
  type DeepInterviewStateEnvelope,
14
18
  type DeepInterviewTriggerMetadata,
15
19
  deriveRoundKey,
16
20
  normalizeDeepInterviewEnvelope,
17
21
  questionHash,
22
+ reviewDeepInterviewIntent,
18
23
  } from "./deep-interview-state";
19
24
  import { writeSessionActivityMarker } from "./session-resolution";
20
25
  import { readExistingStateForMutation, writeGuardedWorkflowEnvelopeAtomic } from "./state-writer";
@@ -48,6 +53,12 @@ export interface DeepInterviewAnswerInput {
48
53
  ambiguity?: number;
49
54
  selectedOptions?: string[];
50
55
  customInput?: string;
56
+ intent_contract?: { items: DeepInterviewIntentItem[]; confirmation_options: string[] };
57
+ intent_review?: {
58
+ observed_items: DeepInterviewIntentItem[];
59
+ supporting_substitutions: DeepInterviewIntentSubstitution[];
60
+ approval_options: string[];
61
+ };
51
62
  }
52
63
 
53
64
  export interface DeepInterviewScoringInput {
@@ -417,15 +428,64 @@ export async function appendOrMergeDeepInterviewRound(
417
428
  ): Promise<{ action: AppendOrMergeAction; record: DeepInterviewRoundRecord; disputedFactIds?: string[] }> {
418
429
  const envelope = await readEnvelope(statePath);
419
430
  const interviewId = input.interviewId ?? interviewIdOf(envelope);
420
- const shell = buildAnswerShell({ ...input, interviewId });
431
+ const shell = buildAnswerShell({
432
+ ...input,
433
+ interviewId,
434
+ customInput: input.intent_contract || input.intent_review ? undefined : input.customInput,
435
+ });
421
436
  const rounds = readRounds(envelope);
422
437
  const priorRecord = rounds.find(r => r.round_key === shell.round_key);
423
438
  const result = appendOrMergeRound(rounds, shell);
424
- if (result.action === "noop") {
439
+ const inner = envelope.state as Record<string, unknown>;
440
+ let intentStateChanged = false;
441
+ if (input.intent_contract) {
442
+ if (input.round !== 0 || input.component !== "review-topology" || input.dimension !== "topology")
443
+ throw new Error("intent contract requires Round 0 topology metadata");
444
+ const confirmed =
445
+ input.selectedOptions?.length === 1 &&
446
+ input.intent_contract.confirmation_options.includes(input.selectedOptions[0]);
447
+ if (confirmed) {
448
+ const contract = createDeepInterviewIntentManifest(input.intent_contract.items, {
449
+ round: 0,
450
+ answer_hash: shell.answer_hash,
451
+ });
452
+ const existingContract = inner.intent_contract;
453
+ if (existingContract !== undefined) {
454
+ assertDeepInterviewIntentManifest(existingContract);
455
+ if (
456
+ existingContract.digest !== contract.digest ||
457
+ existingContract.confirmation_answer_hash !== contract.confirmation_answer_hash
458
+ )
459
+ throw new Error("locked intent contract cannot be replaced");
460
+ } else {
461
+ inner.intent_contract = contract;
462
+ intentStateChanged = true;
463
+ }
464
+ }
465
+ }
466
+ if (input.intent_review) {
467
+ if (input.round <= 0) throw new Error("intent review requires a post-Round-0 answer");
468
+ const locked = inner.intent_contract;
469
+ assertDeepInterviewIntentManifest(locked);
470
+ const approved =
471
+ input.selectedOptions?.length === 1 && input.intent_review.approval_options.includes(input.selectedOptions[0]);
472
+ inner.intent_review = reviewDeepInterviewIntent(locked, input.intent_review.observed_items, {
473
+ status: approved ? "approved" : "pending",
474
+ supporting_substitutions: input.intent_review.supporting_substitutions,
475
+ ...(approved
476
+ ? {
477
+ approval_round: input.round,
478
+ answer_hash: shell.answer_hash,
479
+ user_answer_evidence: `answer_hash:${shell.answer_hash}`,
480
+ }
481
+ : {}),
482
+ });
483
+ intentStateChanged = true;
484
+ }
485
+ if (result.action === "noop" && !intentStateChanged) {
425
486
  await repairRecorderHudFromPersisted(cwd, statePath, options.sessionId);
426
487
  return { action: result.action, record: result.record };
427
488
  }
428
- const inner = envelope.state as Record<string, unknown>;
429
489
  inner.rounds = result.rounds;
430
490
  let disputedFactIds: string[] | undefined;
431
491
  if (result.action === "replaced" && priorRecord?.lifecycle === "scored") {
@@ -6,12 +6,21 @@ import { YAML } from "bun";
6
6
  import { syncSkillActiveState } from "../skill-state/active-state";
7
7
  import { deriveDeepInterviewHud } from "../skill-state/workflow-hud";
8
8
  import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
9
- import { normalizeDeepInterviewEnvelope } from "./deep-interview-state";
9
+ import {
10
+ assertDeepInterviewIntentReview,
11
+ type DeepInterviewIntentCategory,
12
+ type DeepInterviewIntentItem,
13
+ type DeepInterviewIntentManifest,
14
+ type DeepInterviewIntentReview,
15
+ normalizeDeepInterviewEnvelope,
16
+ reviewDeepInterviewIntent,
17
+ } from "./deep-interview-state";
10
18
  import { runNativeRalplanCommand } from "./ralplan-runtime";
11
19
  import { modeStatePath, sessionSpecsDir } from "./session-layout";
12
20
  import { resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
13
21
  import { runNativeStateCommand } from "./state-runtime";
14
22
  import { appendJsonl, readExistingStateForMutation, writeArtifact, writeWorkflowEnvelopeAtomic } from "./state-writer";
23
+ import { assertSafePathComponent, CommandError, flagValue, hasFlag } from "./workflow-cli-common";
15
24
 
16
25
  export * from "./deep-interview-recorder";
17
26
 
@@ -31,8 +40,6 @@ export interface DeepInterviewCommandResult {
31
40
  stderr?: string;
32
41
  }
33
42
 
34
- const PATH_COMPONENT_RE = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
35
-
36
43
  const DEFAULT_AMBIGUITY_THRESHOLD = 0.05;
37
44
 
38
45
  const RESOLUTION_THRESHOLDS = {
@@ -105,12 +112,9 @@ interface DeepInterviewTraceSummary {
105
112
 
106
113
  type DeepInterviewResolution = keyof typeof RESOLUTION_THRESHOLDS;
107
114
 
108
- class DeepInterviewCommandError extends Error {
109
- constructor(
110
- public readonly exitStatus: number,
111
- message: string,
112
- ) {
113
- super(message);
115
+ class DeepInterviewCommandError extends CommandError {
116
+ constructor(exitStatus: number, message: string) {
117
+ super(exitStatus, message);
114
118
  this.name = "DeepInterviewCommandError";
115
119
  }
116
120
  }
@@ -125,22 +129,6 @@ const VALUE_FLAGS = new Set([
125
129
  "--handoff",
126
130
  ]);
127
131
 
128
- function flagValue(args: readonly string[], flag: string): string | undefined {
129
- const index = args.indexOf(flag);
130
- if (index < 0) return undefined;
131
- return args[index + 1];
132
- }
133
-
134
- function hasFlag(args: readonly string[], flag: string): boolean {
135
- return args.includes(flag);
136
- }
137
-
138
- function assertSafePathComponent(value: string, label: string): void {
139
- if (!PATH_COMPONENT_RE.test(value) || value.includes("..")) {
140
- throw new DeepInterviewCommandError(2, `invalid path component for --${label}: ${value}`);
141
- }
142
- }
143
-
144
132
  function defaultSpecSlug(now: Date = new Date()): string {
145
133
  const yyyy = now.getUTCFullYear().toString().padStart(4, "0");
146
134
  const mm = (now.getUTCMonth() + 1).toString().padStart(2, "0");
@@ -585,6 +573,61 @@ async function resolveDeepInterviewArgs(args: readonly string[], cwd: string): P
585
573
  };
586
574
  }
587
575
 
576
+ function intentIdsInSpec(content: string): string[] {
577
+ const ids = content.match(/(?:artifact|surface|integration|constraint):[a-z0-9][a-z0-9._/-]{0,127}/g) ?? [];
578
+ return [...new Set(ids)].sort();
579
+ }
580
+
581
+ function resolveLockedIntentReview(existing: unknown, content: string): DeepInterviewIntentReview | undefined {
582
+ const envelope = normalizeDeepInterviewEnvelope(existing);
583
+ const state = envelope.state;
584
+ if (!state) return undefined;
585
+ if (state.intent_contract === undefined) {
586
+ if (state.intent_contract_required === true)
587
+ throw new DeepInterviewCommandError(
588
+ 2,
589
+ "deep-interview locked intent blocks spec persistence: missing Round 0 intent contract",
590
+ );
591
+ return undefined;
592
+ }
593
+ const locked = state.intent_contract as DeepInterviewIntentManifest;
594
+ const observedIds = intentIdsInSpec(content);
595
+ const rounds = Array.isArray(state.rounds)
596
+ ? state.rounds
597
+ .filter(
598
+ (round): round is Record<string, unknown> =>
599
+ Boolean(round) && typeof round === "object" && !Array.isArray(round),
600
+ )
601
+ .map(round => ({ round: round.round, answer_hash: round.answer_hash }))
602
+ : [];
603
+ try {
604
+ if (state.intent_review === undefined) {
605
+ if (locked.items.some(item => !observedIds.includes(item.id))) throw new Error("missing intent review");
606
+ const lockedById = new Map(locked.items.map(item => [item.id, item]));
607
+ const observedItems: DeepInterviewIntentItem[] = observedIds.map(id => {
608
+ const existingItem = lockedById.get(id);
609
+ if (existingItem) return existingItem;
610
+ return {
611
+ id,
612
+ category: id.slice(0, id.indexOf(":")) as DeepInterviewIntentCategory,
613
+ statement: id,
614
+ };
615
+ });
616
+ return reviewDeepInterviewIntent(locked, observedItems, {
617
+ status: "not_required",
618
+ supporting_substitutions: [],
619
+ });
620
+ }
621
+ assertDeepInterviewIntentReview(state.intent_review, locked, observedIds, rounds);
622
+ return state.intent_review as DeepInterviewIntentReview;
623
+ } catch (error) {
624
+ throw new DeepInterviewCommandError(
625
+ 2,
626
+ `deep-interview locked intent blocks spec persistence: ${error instanceof Error ? error.message : String(error)}`,
627
+ );
628
+ }
629
+ }
630
+
588
631
  export async function persistDeepInterviewSpec(
589
632
  cwd: string,
590
633
  resolved: ResolvedDeepInterviewSpecWriteArgs,
@@ -599,8 +642,9 @@ export async function persistDeepInterviewSpec(
599
642
  }
600
643
  const existing = existingRead.kind === "valid" ? existingRead.value : {};
601
644
 
602
- const specPath = path.join(sessionSpecsDir(cwd, resolved.sessionId), `deep-interview-${resolved.slug}.md`);
603
645
  const content = resolved.spec.endsWith("\n") ? resolved.spec : `${resolved.spec}\n`;
646
+ const intentReview = resolveLockedIntentReview(existing, content);
647
+ const specPath = path.join(sessionSpecsDir(cwd, resolved.sessionId), `deep-interview-${resolved.slug}.md`);
604
648
  await writeArtifact(specPath, content, {
605
649
  cwd,
606
650
  audit: {
@@ -642,6 +686,10 @@ export async function persistDeepInterviewSpec(
642
686
  spec_persisted_at: createdAt,
643
687
  updated_at: createdAt,
644
688
  }) as Record<string, unknown>;
689
+ if (intentReview) {
690
+ const state = payload.state as Record<string, unknown>;
691
+ state.intent_review = intentReview;
692
+ }
645
693
  if (resolved.sessionId) payload.session_id = resolved.sessionId;
646
694
  await writeWorkflowEnvelopeAtomic(statePath, payload, {
647
695
  cwd,
@@ -694,6 +742,7 @@ async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepIntervi
694
742
  threshold_source: resolved.thresholdSource,
695
743
  state: {
696
744
  initial_idea: resolved.idea,
745
+ intent_contract_required: true,
697
746
  rounds: [],
698
747
  established_facts: [],
699
748
  current_ambiguity: 1.0,
@@ -867,7 +916,7 @@ export async function runNativeDeepInterviewCommand(
867
916
  ].join("\n");
868
917
  return { status: 0, stdout };
869
918
  } catch (error) {
870
- if (error instanceof DeepInterviewCommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
919
+ if (error instanceof CommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
871
920
  return { status: 1, stderr: `${error instanceof Error ? error.message : String(error)}\n` };
872
921
  }
873
922
  }
@@ -102,7 +102,9 @@ export function questionHash(questionText: string): string {
102
102
  }
103
103
 
104
104
  export function answerHash(selectedOptions: string[] | undefined, customInput: string | undefined): string {
105
- return hashContent(JSON.stringify({ selected: selectedOptions ?? [], custom: customInput ?? null }));
105
+ return createHash("sha256")
106
+ .update(JSON.stringify({ selected: selectedOptions ?? [], custom: customInput ?? null }))
107
+ .digest("hex");
106
108
  }
107
109
 
108
110
  /**
@@ -335,9 +337,30 @@ export function mergeDeepInterviewEnvelope(
335
337
  const incomingHasEstablishedFacts =
336
338
  Object.hasOwn(incomingNestedState, "established_facts") || Object.hasOwn(incomingEnvelope, "established_facts");
337
339
  const normalizedIncoming = normalizeDeepInterviewEnvelope(incoming);
338
- if (options.replace) return normalizedIncoming;
339
-
340
340
  const normalizedExisting = normalizeDeepInterviewEnvelope(existing);
341
+ const existingState = isPlainObject(normalizedExisting.state) ? normalizedExisting.state : {};
342
+ const incomingState = isPlainObject(normalizedIncoming.state) ? { ...normalizedIncoming.state } : {};
343
+ if (existingState.intent_contract !== undefined) {
344
+ assertDeepInterviewIntentManifest(existingState.intent_contract);
345
+ if (Object.hasOwn(incomingState, "intent_contract")) {
346
+ if (incomingState.intent_contract === null) throw new Error("locked intent contract cannot be deleted");
347
+ assertDeepInterviewIntentManifest(incomingState.intent_contract);
348
+ if (
349
+ incomingState.intent_contract.digest !== existingState.intent_contract.digest ||
350
+ incomingState.intent_contract.confirmation_answer_hash !==
351
+ existingState.intent_contract.confirmation_answer_hash
352
+ )
353
+ throw new Error("locked intent contract cannot be replaced");
354
+ } else {
355
+ incomingState.intent_contract = existingState.intent_contract;
356
+ }
357
+ }
358
+ if (existingState.intent_contract_required === true) incomingState.intent_contract_required = true;
359
+ if (options.replace) {
360
+ normalizedIncoming.state = incomingState;
361
+ return normalizedIncoming;
362
+ }
363
+
341
364
  const merged: Record<string, unknown> = {};
342
365
  for (const [key, value] of Object.entries(normalizedExisting)) {
343
366
  if (key !== "state") merged[key] = value;
@@ -348,8 +371,6 @@ export function mergeDeepInterviewEnvelope(
348
371
  else merged[key] = value;
349
372
  }
350
373
 
351
- const existingState = isPlainObject(normalizedExisting.state) ? normalizedExisting.state : {};
352
- const incomingState = isPlainObject(normalizedIncoming.state) ? normalizedIncoming.state : {};
353
374
  const mergedState: Record<string, unknown> = { ...existingState };
354
375
  for (const [key, value] of Object.entries(incomingState)) {
355
376
  if (key === "rounds") continue;
@@ -364,3 +385,294 @@ export function mergeDeepInterviewEnvelope(
364
385
  merged.state = mergedState;
365
386
  return merged as DeepInterviewStateEnvelope;
366
387
  }
388
+
389
+ export const DEEP_INTERVIEW_INTENT_CATEGORIES = ["artifact", "surface", "integration", "constraint"] as const;
390
+ export type DeepInterviewIntentCategory = (typeof DEEP_INTERVIEW_INTENT_CATEGORIES)[number];
391
+
392
+ const INTENT_ID_RE = /^(artifact|surface|integration|constraint):[a-z0-9][a-z0-9._/-]{0,127}$/;
393
+ const ANSWER_HASH_RE = /^[a-f0-9]{64}$/;
394
+ const INTENT_RATIONALE_REF_RE = /^sha256:[a-f0-9]{64}$/;
395
+ const MAX_INTENT_ITEMS = 64;
396
+ const MAX_INTENT_STATEMENT_LENGTH = 1_000;
397
+ const MAX_INTENT_RATIONALE_LENGTH = 500;
398
+ const MAX_INTENT_EVIDENCE_LENGTH = 80;
399
+
400
+ export interface DeepInterviewIntentItem {
401
+ id: string;
402
+ category: DeepInterviewIntentCategory;
403
+ statement: string;
404
+ }
405
+
406
+ export interface DeepInterviewIntentManifest {
407
+ version: 1;
408
+ items: DeepInterviewIntentItem[];
409
+ digest: string;
410
+ confirmation_round: 0;
411
+ confirmation_answer_hash: string;
412
+ confirmation_evidence: string;
413
+ }
414
+
415
+ export interface DeepInterviewIntentSubstitution {
416
+ removed_id: string;
417
+ replacement_ids: string[];
418
+ rationale: string;
419
+ }
420
+
421
+ export interface DeepInterviewIntentReview {
422
+ version: 1;
423
+ status: "not_required" | "pending" | "approved";
424
+ locked_digest: string;
425
+ observed_digest: string;
426
+ removed_locked_ids: string[];
427
+ supporting_substitutions: DeepInterviewIntentSubstitution[];
428
+ approval_round?: number;
429
+ answer_hash?: string;
430
+ user_answer_evidence?: string;
431
+ }
432
+
433
+ export function isCanonicalDeepInterviewAnswerHash(value: unknown): value is string {
434
+ return typeof value === "string" && ANSWER_HASH_RE.test(value);
435
+ }
436
+
437
+ function isEvidenceReference(value: unknown, answerHashValue: string): value is string {
438
+ return (
439
+ typeof value === "string" &&
440
+ value.length <= MAX_INTENT_EVIDENCE_LENGTH &&
441
+ value === `answer_hash:${answerHashValue}`
442
+ );
443
+ }
444
+
445
+ function assertIntentItem(value: unknown): asserts value is DeepInterviewIntentItem {
446
+ if (!isPlainObject(value) || Object.keys(value).length !== 3) throw new Error("invalid intent item");
447
+ const { id, category, statement } = value;
448
+ if (typeof id !== "string" || !INTENT_ID_RE.test(id)) throw new Error("invalid intent id");
449
+ if (
450
+ !DEEP_INTERVIEW_INTENT_CATEGORIES.includes(category as DeepInterviewIntentCategory) ||
451
+ !id.startsWith(`${category}:`)
452
+ )
453
+ throw new Error("invalid intent category");
454
+ if (typeof statement !== "string" || !statement.trim() || statement.length > MAX_INTENT_STATEMENT_LENGTH)
455
+ throw new Error(`intent item ${id} requires a bounded statement`);
456
+ }
457
+
458
+ function canonicalIntentItems(items: readonly DeepInterviewIntentItem[]): DeepInterviewIntentItem[] {
459
+ return [...items]
460
+ .map(item => ({ id: item.id.trim(), category: item.category, statement: item.statement.trim() }))
461
+ .sort((left, right) => left.id.localeCompare(right.id));
462
+ }
463
+
464
+ export function deepInterviewIntentManifestDigest(items: readonly DeepInterviewIntentItem[]): string {
465
+ return createHash("sha256")
466
+ .update(JSON.stringify(canonicalIntentItems(items)))
467
+ .digest("hex");
468
+ }
469
+
470
+ export function deepInterviewObservedIntentDigest(ids: readonly string[]): string {
471
+ return createHash("sha256")
472
+ .update(JSON.stringify([...new Set(ids)].sort()))
473
+ .digest("hex");
474
+ }
475
+
476
+ export function createDeepInterviewIntentManifest(
477
+ items: readonly DeepInterviewIntentItem[],
478
+ confirmation: { round: 0; answer_hash: string },
479
+ ): DeepInterviewIntentManifest {
480
+ if (!Array.isArray(items) || items.length === 0 || items.length > MAX_INTENT_ITEMS)
481
+ throw new Error("intent manifest requires bounded items");
482
+ if (confirmation.round !== 0 || !isCanonicalDeepInterviewAnswerHash(confirmation.answer_hash))
483
+ throw new Error("intent confirmation requires Round 0 canonical answer hash");
484
+ const canonical = canonicalIntentItems(items);
485
+ const ids = new Set<string>();
486
+ for (const item of canonical) {
487
+ assertIntentItem(item);
488
+ if (ids.has(item.id)) throw new Error(`duplicate intent id: ${item.id}`);
489
+ ids.add(item.id);
490
+ }
491
+ return {
492
+ version: 1,
493
+ items: canonical,
494
+ digest: deepInterviewIntentManifestDigest(canonical),
495
+ confirmation_round: 0,
496
+ confirmation_answer_hash: confirmation.answer_hash,
497
+ confirmation_evidence: `answer_hash:${confirmation.answer_hash}`,
498
+ };
499
+ }
500
+
501
+ export function assertDeepInterviewIntentManifest(value: unknown): asserts value is DeepInterviewIntentManifest {
502
+ if (!isPlainObject(value) || Object.keys(value).length !== 6 || value.version !== 1 || !Array.isArray(value.items))
503
+ throw new Error("invalid intent contract");
504
+ const manifest = createDeepInterviewIntentManifest(value.items as DeepInterviewIntentItem[], {
505
+ round: value.confirmation_round as 0,
506
+ answer_hash: value.confirmation_answer_hash as string,
507
+ });
508
+ if (
509
+ value.digest !== manifest.digest ||
510
+ value.confirmation_round !== 0 ||
511
+ !isEvidenceReference(value.confirmation_evidence, manifest.confirmation_answer_hash)
512
+ )
513
+ throw new Error("intent contract integrity mismatch");
514
+ }
515
+
516
+ export function reviewDeepInterviewIntent(
517
+ locked: DeepInterviewIntentManifest,
518
+ observedItems: readonly DeepInterviewIntentItem[],
519
+ input: Omit<DeepInterviewIntentReview, "version" | "locked_digest" | "observed_digest" | "removed_locked_ids">,
520
+ ): DeepInterviewIntentReview {
521
+ assertDeepInterviewIntentManifest(locked);
522
+ const observed = createDeepInterviewIntentManifest(observedItems, {
523
+ round: 0,
524
+ answer_hash: locked.confirmation_answer_hash,
525
+ });
526
+ const observedIds = new Set(observed.items.map(item => item.id));
527
+ const removed = locked.items
528
+ .map(item => item.id)
529
+ .filter(id => !observedIds.has(id))
530
+ .sort();
531
+ const substitutions = input.supporting_substitutions;
532
+ if (!Array.isArray(substitutions)) throw new Error("invalid intent substitutions");
533
+ const substitutionIds = new Set(substitutions.map(item => item.removed_id));
534
+ if (substitutionIds.size !== substitutions.length) throw new Error("duplicate intent substitution");
535
+ for (const substitution of substitutions) {
536
+ if (!isPlainObject(substitution) || !removed.includes(substitution.removed_id))
537
+ throw new Error("substitution does not bind removed intent");
538
+ if (
539
+ !Array.isArray(substitution.replacement_ids) ||
540
+ substitution.replacement_ids.length === 0 ||
541
+ substitution.replacement_ids.some(id => typeof id !== "string" || !observedIds.has(id)) ||
542
+ typeof substitution.rationale !== "string" ||
543
+ !substitution.rationale.trim() ||
544
+ substitution.rationale.length > MAX_INTENT_RATIONALE_LENGTH
545
+ )
546
+ throw new Error("invalid intent substitution");
547
+ }
548
+ const redactedSubstitutions = substitutions.map(substitution => ({
549
+ removed_id: substitution.removed_id,
550
+ replacement_ids: [...new Set(substitution.replacement_ids)].sort(),
551
+ rationale: `sha256:${createHash("sha256").update(substitution.rationale.trim()).digest("hex")}`,
552
+ }));
553
+ if (input.status !== "not_required" && input.status !== "pending" && input.status !== "approved")
554
+ throw new Error("invalid intent review status");
555
+ if (input.status === "not_required") {
556
+ if (
557
+ removed.length > 0 ||
558
+ substitutions.length > 0 ||
559
+ input.approval_round !== undefined ||
560
+ input.answer_hash !== undefined ||
561
+ input.user_answer_evidence !== undefined
562
+ )
563
+ throw new Error("not_required intent review cannot carry reduction evidence");
564
+ }
565
+ if (
566
+ input.status === "pending" &&
567
+ (input.approval_round !== undefined ||
568
+ input.answer_hash !== undefined ||
569
+ input.user_answer_evidence !== undefined)
570
+ )
571
+ throw new Error("pending intent review cannot carry approval evidence");
572
+ if (input.status === "approved") {
573
+ if (removed.some(id => !substitutionIds.has(id)))
574
+ throw new Error("approved intent reduction requires every substitution");
575
+ if (
576
+ typeof input.approval_round !== "number" ||
577
+ !Number.isFinite(input.approval_round) ||
578
+ !Number.isInteger(input.approval_round) ||
579
+ input.approval_round <= 0 ||
580
+ !isCanonicalDeepInterviewAnswerHash(input.answer_hash) ||
581
+ !isEvidenceReference(input.user_answer_evidence, input.answer_hash)
582
+ )
583
+ throw new Error("approved intent reduction requires durable redacted answer evidence");
584
+ }
585
+ return {
586
+ ...input,
587
+ supporting_substitutions: redactedSubstitutions,
588
+ version: 1,
589
+ locked_digest: locked.digest,
590
+ observed_digest: deepInterviewObservedIntentDigest([...observedIds]),
591
+ removed_locked_ids: removed,
592
+ };
593
+ }
594
+
595
+ export function assertDeepInterviewIntentReview(
596
+ value: unknown,
597
+ locked: DeepInterviewIntentManifest,
598
+ observedIds: readonly string[],
599
+ recordedAnswers: readonly { round: unknown; answer_hash: unknown }[],
600
+ ): asserts value is DeepInterviewIntentReview {
601
+ assertDeepInterviewIntentManifest(locked);
602
+ if (!isPlainObject(value)) throw new Error("missing or invalid intent review");
603
+ const allowedKeys = new Set([
604
+ "version",
605
+ "status",
606
+ "locked_digest",
607
+ "observed_digest",
608
+ "removed_locked_ids",
609
+ "supporting_substitutions",
610
+ "approval_round",
611
+ "answer_hash",
612
+ "user_answer_evidence",
613
+ ]);
614
+ if (Object.keys(value).some(key => !allowedKeys.has(key)) || value.version !== 1)
615
+ throw new Error("invalid intent review");
616
+ if (
617
+ value.locked_digest !== locked.digest ||
618
+ value.observed_digest !== deepInterviewObservedIntentDigest(observedIds)
619
+ )
620
+ throw new Error("stale intent review");
621
+ const observed = new Set(observedIds);
622
+ const removed = locked.items
623
+ .map(item => item.id)
624
+ .filter(id => !observed.has(id))
625
+ .sort();
626
+ if (
627
+ !Array.isArray(value.removed_locked_ids) ||
628
+ JSON.stringify([...value.removed_locked_ids].sort()) !== JSON.stringify(removed)
629
+ )
630
+ throw new Error("intent review removed IDs mismatch");
631
+ if (!Array.isArray(value.supporting_substitutions)) throw new Error("invalid intent substitutions");
632
+ const substitutions = value.supporting_substitutions;
633
+ const substitutionIds = new Set<string>();
634
+ for (const substitution of substitutions) {
635
+ if (!isPlainObject(substitution) || Object.keys(substitution).length !== 3)
636
+ throw new Error("invalid intent substitution");
637
+ const { removed_id, replacement_ids, rationale } = substitution;
638
+ if (
639
+ typeof removed_id !== "string" ||
640
+ !removed.includes(removed_id) ||
641
+ substitutionIds.has(removed_id) ||
642
+ !Array.isArray(replacement_ids) ||
643
+ replacement_ids.length === 0 ||
644
+ replacement_ids.some(id => typeof id !== "string" || !INTENT_ID_RE.test(id) || !observed.has(id)) ||
645
+ typeof rationale !== "string" ||
646
+ !INTENT_RATIONALE_REF_RE.test(rationale)
647
+ )
648
+ throw new Error("invalid intent substitution");
649
+ substitutionIds.add(removed_id);
650
+ }
651
+ if (value.status === "not_required") {
652
+ if (
653
+ removed.length > 0 ||
654
+ substitutions.length > 0 ||
655
+ value.approval_round !== undefined ||
656
+ value.answer_hash !== undefined ||
657
+ value.user_answer_evidence !== undefined
658
+ )
659
+ throw new Error("not_required intent review cannot carry reduction evidence");
660
+ return;
661
+ }
662
+ if (value.status !== "approved") {
663
+ if (value.status === "pending") throw new Error("intent reduction is pending or unapproved");
664
+ throw new Error("invalid intent review status");
665
+ }
666
+ if (removed.some(id => !substitutionIds.has(id)))
667
+ throw new Error("approved intent reduction requires every substitution");
668
+ if (
669
+ typeof value.approval_round !== "number" ||
670
+ !Number.isFinite(value.approval_round) ||
671
+ !Number.isInteger(value.approval_round) ||
672
+ value.approval_round <= 0 ||
673
+ !isCanonicalDeepInterviewAnswerHash(value.answer_hash) ||
674
+ !isEvidenceReference(value.user_answer_evidence, value.answer_hash) ||
675
+ !recordedAnswers.some(answer => answer.round === value.approval_round && answer.answer_hash === value.answer_hash)
676
+ )
677
+ throw new Error("intent review approval evidence is invalid");
678
+ }