@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
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import * as fs from "node:fs/promises";
2
3
  import * as path from "node:path";
3
4
  import type { WorkflowHudSummary } from "../skill-state/active-state";
@@ -27,7 +28,13 @@ import {
27
28
  } from "../skill-state/workflow-state-contract";
28
29
  import { renderCliWriteReceipt } from "./cli-write-receipt";
29
30
  import { applyAmbiguityFloorToEnvelope } from "./deep-interview-ambiguity";
30
- import { mergeDeepInterviewEnvelope, normalizeDeepInterviewEnvelope } from "./deep-interview-state";
31
+ import {
32
+ assertDeepInterviewIntentManifest,
33
+ assertDeepInterviewIntentReview,
34
+ type DeepInterviewIntentManifest,
35
+ mergeDeepInterviewEnvelope,
36
+ normalizeDeepInterviewEnvelope,
37
+ } from "./deep-interview-state";
31
38
  import { activeSnapshotPath, auditPath, modeStatePath, sessionStateDir } from "./session-layout";
32
39
  import {
33
40
  resolveGjcSessionForRead,
@@ -61,8 +68,10 @@ import {
61
68
  softDelete,
62
69
  updateWorkflowTransactionJournal,
63
70
  type WorkflowEnvelopeIntegrityMismatch,
71
+ withWorkflowStateLock,
64
72
  writeGuardedWorkflowEnvelopeAtomic,
65
73
  } from "./state-writer";
74
+ import { assertSafePathComponent, CommandError, flagValue, hasFlag, isPlainObject } from "./workflow-cli-common";
66
75
  import { getSkillManifest, isKnownWorkflowState, isValidTransition, typedArgsFor } from "./workflow-manifest";
67
76
 
68
77
  /**
@@ -80,30 +89,15 @@ export interface StateCommandResult {
80
89
  }
81
90
 
82
91
  const SKILL_ACTIVE_STATE_FILE = "skill-active-state.json";
83
- const TERMINAL_CLEAR_PHASES = new Set(["complete", "completed", "cancelled", "canceled", "failed"]);
84
- const PATH_COMPONENT_RE = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
85
92
  const KNOWN_MODES: readonly string[] = CANONICAL_GJC_WORKFLOW_SKILLS;
86
93
 
87
- class StateCommandError extends Error {
88
- constructor(
89
- public readonly exitStatus: number,
90
- message: string,
91
- ) {
92
- super(message);
94
+ class StateCommandError extends CommandError {
95
+ constructor(exitStatus: number, message: string) {
96
+ super(exitStatus, message);
93
97
  this.name = "StateCommandError";
94
98
  }
95
99
  }
96
100
 
97
- function flagValue(args: readonly string[], flag: string): string | undefined {
98
- const index = args.indexOf(flag);
99
- if (index < 0) return undefined;
100
- return args[index + 1];
101
- }
102
-
103
- function hasFlag(args: readonly string[], flag: string): boolean {
104
- return args.includes(flag);
105
- }
106
-
107
101
  const GRAPH_FORMATS = new Set(["ascii", "mermaid", "dot"]);
108
102
  const FLAGS_WITH_VALUES = new Set([
109
103
  "--input",
@@ -237,11 +231,6 @@ function parsePositionalArgs(args: readonly string[]): ParsedInvocation {
237
231
  return { action: "read" };
238
232
  }
239
233
 
240
- function assertSafePathComponent(value: string, label: string): void {
241
- if (!PATH_COMPONENT_RE.test(value) || value.includes("..")) {
242
- throw new StateCommandError(2, `invalid path component for --${label}: ${value}`);
243
- }
244
- }
245
234
  function isKnownMode(mode: string): mode is CanonicalGjcWorkflowSkill {
246
235
  return KNOWN_MODES.includes(mode);
247
236
  }
@@ -391,7 +380,9 @@ async function describeStaleClearState(
391
380
  existing: Record<string, unknown>,
392
381
  ): Promise<string | undefined> {
393
382
  const phase = typeof existing.current_phase === "string" ? existing.current_phase.trim() : undefined;
394
- if (phase && TERMINAL_CLEAR_PHASES.has(phase)) return `mode-state is already terminal (${phase})`;
383
+ if (phase && getSkillManifest(mode).stopReleasingPhases.includes(phase) && phase !== "inactive") {
384
+ return `mode-state is already terminal (${phase})`;
385
+ }
395
386
  const activePhase = await readActivePhaseForSkill(cwd, sessionId, mode);
396
387
  if (activePhase && phase && activePhase !== phase) {
397
388
  return `active-state phase ${activePhase} differs from mode-state phase ${phase}`;
@@ -504,22 +495,11 @@ function phaseFromActiveValue(value: unknown): string | undefined {
504
495
  return phase || undefined;
505
496
  }
506
497
 
507
- const RALPLAN_CANONICAL_PHASE_OVERRIDES = new Set([
508
- "final",
509
- "handoff",
510
- "complete",
511
- "completed",
512
- "failed",
513
- "cancelled",
514
- "canceled",
515
- "inactive",
516
- ]);
517
-
518
498
  function modeStatePhase(value: unknown): string | undefined {
519
499
  if (!isPlainObject(value) || typeof value.current_phase !== "string") return undefined;
520
500
  const phase = value.current_phase.trim();
521
501
  if (!phase) return undefined;
522
- if (value.active === false && !RALPLAN_CANONICAL_PHASE_OVERRIDES.has(phase)) return undefined;
502
+ if (value.active === false && !getSkillManifest("ralplan").canonicalOverrides.includes(phase)) return undefined;
523
503
  return phase;
524
504
  }
525
505
 
@@ -821,6 +801,7 @@ async function writeJsonAtomic(
821
801
  fromPhase?: string;
822
802
  toPhase?: string;
823
803
  owner?: WorkflowStateMutationOwner;
804
+ lockHeld?: boolean;
824
805
  },
825
806
  ): Promise<{ warning?: string; stamped: Record<string, unknown>; revision: number }> {
826
807
  const warning = options?.skill
@@ -850,6 +831,7 @@ async function writeJsonAtomic(
850
831
  toPhase: options?.toPhase,
851
832
  forced: options?.force ?? false,
852
833
  },
834
+ lockHeld: options?.lockHeld ?? false,
853
835
  });
854
836
  // `writeResult.stamped` and `.revision` are computed inside the writer lock, so they are
855
837
  // the envelope/revision this write actually owns. Never post-lock re-read here: a concurrent
@@ -938,10 +920,6 @@ async function readAuditWindow(
938
920
  return { entries: selected.reverse(), limit, ...(since ? { since } : {}), truncated: matched > limit };
939
921
  }
940
922
 
941
- function isPlainObject(value: unknown): value is Record<string, unknown> {
942
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
943
- }
944
-
945
923
  /**
946
924
  * Shallow-merge `source` into `target`, with the convention that a `source` key whose value is
947
925
  * `null` deletes that key from `target`. Nested objects are replaced wholesale (not deep-merged)
@@ -1127,11 +1105,21 @@ export async function reconcileWorkflowSkillState(options: {
1127
1105
  payload: Record<string, unknown>;
1128
1106
  sourceRevision?: number;
1129
1107
  }): Promise<{ stateFile: string }> {
1130
- const { cwd, mode, threadId, turnId, active, payload } = options;
1131
- const { gjcSessionId: sessionId } = resolveGjcSessionForWrite(cwd, {
1108
+ const { gjcSessionId: sessionId } = resolveGjcSessionForWrite(options.cwd, {
1132
1109
  payloadSessionId: options.sessionId,
1133
1110
  envSessionId: process.env.GJC_SESSION_ID,
1134
1111
  });
1112
+ return withWorkflowStateLock(
1113
+ path.relative(options.cwd, modeStateFile(options.cwd, options.mode, sessionId)),
1114
+ async () => reconcileWorkflowSkillStateUnlocked(options, sessionId),
1115
+ );
1116
+ }
1117
+
1118
+ async function reconcileWorkflowSkillStateUnlocked(
1119
+ options: Parameters<typeof reconcileWorkflowSkillState>[0],
1120
+ sessionId: string,
1121
+ ): Promise<{ stateFile: string }> {
1122
+ const { cwd, mode, threadId, turnId, active, payload } = options;
1135
1123
  const filePath = modeStateFile(cwd, mode, sessionId);
1136
1124
  const existingRead = await readExistingStateForMutation(filePath);
1137
1125
  const existingPayload = existingRead.kind === "valid" ? existingRead.value : {};
@@ -1181,9 +1169,10 @@ export async function reconcileWorkflowSkillState(options: {
1181
1169
  if (!validation.valid) throw new StateCommandError(2, validation.error ?? `invalid ${mode} state envelope`);
1182
1170
 
1183
1171
  if (existingRead.kind === "corrupt") await fs.rm(filePath, { force: true });
1184
- await writeGuardedWorkflowEnvelopeAtomic(filePath, merged, {
1172
+ const writeResult = await writeGuardedWorkflowEnvelopeAtomic(filePath, merged, {
1185
1173
  cwd,
1186
1174
  policy: "source",
1175
+ lockHeld: true,
1187
1176
  receipt: {
1188
1177
  cwd,
1189
1178
  skill: mode,
@@ -1209,8 +1198,7 @@ export async function reconcileWorkflowSkillState(options: {
1209
1198
  toPhase: trimmedPhase,
1210
1199
  },
1211
1200
  });
1212
- const persisted = (await readJsonFile(filePath)) ?? {};
1213
- const sourceRevision = options.sourceRevision ?? existingStateRevision(persisted);
1201
+ const sourceRevision = options.sourceRevision ?? writeResult.revision;
1214
1202
 
1215
1203
  // Reconciliation drives the active-state/HUD update directly (not via the
1216
1204
  // best-effort syncWorkflowSkillState wrapper) so a failed HUD/active-state write
@@ -1332,132 +1320,149 @@ async function handleWrite(
1332
1320
 
1333
1321
  const filePath = modeStateFile(cwd, mode, sessionId);
1334
1322
  const forced = hasFlag(args, "--force");
1335
- const existingRead = await readExistingStateForMutation(filePath);
1336
- if (existingRead.kind === "corrupt" && !forced) {
1337
- throw new StateCommandError(
1338
- 2,
1339
- `existing state for ${mode} is corrupt or tampered (${existingRead.error}); use --force to overwrite`,
1340
- );
1341
- }
1342
- const existingPayload = existingRead.kind === "valid" ? existingRead.value : {};
1343
- const nowIsoStr = nowIso();
1344
- const mutationId = `${mode}:${nowIsoStr}`;
1345
- const receipt = buildWorkflowStateReceipt({
1346
- cwd,
1347
- skill: mode,
1348
- owner: "gjc-state-cli",
1349
- command: `gjc state ${mode} write`,
1350
- sessionId,
1351
- nowIso: nowIsoStr,
1352
- mutationId,
1353
- });
1354
- const innerState = (payload.state as Record<string, unknown> | undefined) ?? {};
1355
- const incomingPhase =
1356
- typeof payload.current_phase === "string" && payload.current_phase.trim()
1357
- ? payload.current_phase.trim()
1358
- : typeof payload.phase === "string" && payload.phase.trim()
1359
- ? payload.phase.trim()
1360
- : typeof innerState.current_phase === "string" && (innerState.current_phase as string).trim()
1361
- ? (innerState.current_phase as string).trim()
1362
- : undefined;
1363
- let merged: Record<string, unknown>;
1364
- if (mode === "deep-interview") {
1365
- // Deep-interview keeps interview data nested under `state` and merges rounds
1366
- // losslessly by durable key; never flatten or delete `state` (that drops recorder history).
1367
- // The deterministic ambiguity floor is applied after the merge so a reported
1368
- // score written through the CLI can never undercut persisted contradiction evidence.
1369
- merged = applyAmbiguityFloorToEnvelope(
1370
- mergeDeepInterviewEnvelope(existingPayload, payload, { replace: hasFlag(args, "--replace") }),
1371
- ).envelope;
1372
- } else if (hasFlag(args, "--replace")) {
1373
- merged = { ...payload };
1374
- } else {
1375
- merged = mergeWithNullDelete(existingPayload, payload);
1376
- // Flatten payload.state.* into the top-level envelope so downstream consumers
1377
- // see a single canonical structure with the receipt at top level.
1378
- if (payload.state && typeof payload.state === "object" && !Array.isArray(payload.state)) {
1379
- merged = mergeWithNullDelete(merged, payload.state as Record<string, unknown>);
1380
- delete merged.state;
1381
- }
1382
- }
1383
- const preDefaultValidation = validateWorkflowStateEnvelope(mode, merged);
1384
- if (!preDefaultValidation.valid) {
1385
- throw new StateCommandError(2, preDefaultValidation.error ?? `invalid ${mode} state envelope`);
1386
- }
1387
- merged.skill = mode;
1388
- if (incomingPhase) {
1389
- merged.current_phase = incomingPhase;
1390
- } else if (typeof merged.current_phase !== "string" || !merged.current_phase.trim()) {
1391
- const retainedPhase =
1392
- typeof existingPayload.current_phase === "string" ? existingPayload.current_phase.trim() : "";
1393
- merged.current_phase = retainedPhase || initialPhaseForSkill(mode);
1394
- } else {
1395
- merged.current_phase = merged.current_phase.trim();
1396
- }
1397
- merged.version = WORKFLOW_STATE_VERSION;
1398
- if (typeof merged.active !== "boolean") merged.active = true;
1399
- merged.updated_at = nowIsoStr;
1400
- merged.receipt = receipt;
1401
- if (sessionId && typeof merged.session_id !== "string") merged.session_id = sessionId;
1402
-
1403
- const fromPhase =
1404
- typeof existingPayload.current_phase === "string" ? existingPayload.current_phase.trim() : undefined;
1405
- const toPhase = merged.current_phase as string;
1406
- const manifestStates = new Set(getSkillManifest(mode).states.map(state => state.id));
1407
- if (!manifestStates.has(toPhase) && !forced) {
1408
- throw new StateCommandError(2, `unknown ${mode} phase "${toPhase}"; use --force to bypass`);
1409
- }
1410
- if (fromPhase && toPhase && isKnownWorkflowState(mode, fromPhase) && isKnownWorkflowState(mode, toPhase)) {
1411
- if (!isValidTransition(mode, fromPhase, toPhase) && !forced) {
1412
- throw new StateCommandError(
1413
- 2,
1414
- `invalid ${mode} phase transition from ${fromPhase} to ${toPhase}; use --force to bypass`,
1415
- );
1416
- }
1417
- }
1418
-
1419
- const validation = validateWorkflowStateEnvelope(mode, merged);
1420
- if (!validation.valid) throw new StateCommandError(2, validation.error ?? `invalid ${mode} state envelope`);
1323
+ return await withWorkflowStateLock(
1324
+ filePath,
1325
+ async () => {
1326
+ const existingRead = await readExistingStateForMutation(filePath);
1327
+ if (existingRead.kind === "corrupt" && !forced) {
1328
+ throw new StateCommandError(
1329
+ 2,
1330
+ `existing state for ${mode} is corrupt or tampered (${existingRead.error}); use --force to overwrite`,
1331
+ );
1332
+ }
1333
+ const existingPayload = existingRead.kind === "valid" ? existingRead.value : {};
1334
+ const nowIsoStr = nowIso();
1335
+ const mutationId = `${mode}:${nowIsoStr}`;
1336
+ const receipt = buildWorkflowStateReceipt({
1337
+ cwd,
1338
+ skill: mode,
1339
+ owner: "gjc-state-cli",
1340
+ command: `gjc state ${mode} write`,
1341
+ sessionId,
1342
+ nowIso: nowIsoStr,
1343
+ mutationId,
1344
+ });
1345
+ const innerState = (payload.state as Record<string, unknown> | undefined) ?? {};
1346
+ const incomingPhase =
1347
+ typeof payload.current_phase === "string" && payload.current_phase.trim()
1348
+ ? payload.current_phase.trim()
1349
+ : typeof payload.phase === "string" && payload.phase.trim()
1350
+ ? payload.phase.trim()
1351
+ : typeof innerState.current_phase === "string" && (innerState.current_phase as string).trim()
1352
+ ? (innerState.current_phase as string).trim()
1353
+ : undefined;
1354
+ let merged: Record<string, unknown>;
1355
+ if (mode === "deep-interview") {
1356
+ // Deep-interview keeps interview data nested under `state` and merges rounds
1357
+ // losslessly by durable key; never flatten or delete `state` (that drops recorder history).
1358
+ // The deterministic ambiguity floor is applied after the merge so a reported
1359
+ // score written through the CLI can never undercut persisted contradiction evidence.
1360
+ merged = applyAmbiguityFloorToEnvelope(
1361
+ mergeDeepInterviewEnvelope(existingPayload, payload, { replace: hasFlag(args, "--replace") }),
1362
+ ).envelope;
1363
+ } else if (hasFlag(args, "--replace")) {
1364
+ merged = { ...payload };
1365
+ } else {
1366
+ merged = mergeWithNullDelete(existingPayload, payload);
1367
+ // Flatten payload.state.* into the top-level envelope so downstream consumers
1368
+ // see a single canonical structure with the receipt at top level.
1369
+ if (payload.state && typeof payload.state === "object" && !Array.isArray(payload.state)) {
1370
+ merged = mergeWithNullDelete(merged, payload.state as Record<string, unknown>);
1371
+ delete merged.state;
1372
+ }
1373
+ }
1374
+ const preDefaultValidation = validateWorkflowStateEnvelope(mode, merged);
1375
+ if (!preDefaultValidation.valid) {
1376
+ throw new StateCommandError(2, preDefaultValidation.error ?? `invalid ${mode} state envelope`);
1377
+ }
1378
+ merged.skill = mode;
1379
+ if (incomingPhase) {
1380
+ merged.current_phase = incomingPhase;
1381
+ } else if (typeof merged.current_phase !== "string" || !merged.current_phase.trim()) {
1382
+ const retainedPhase =
1383
+ typeof existingPayload.current_phase === "string" ? existingPayload.current_phase.trim() : "";
1384
+ merged.current_phase = retainedPhase || initialPhaseForSkill(mode);
1385
+ } else {
1386
+ merged.current_phase = merged.current_phase.trim();
1387
+ }
1388
+ merged.version = WORKFLOW_STATE_VERSION;
1389
+ if (typeof merged.active !== "boolean") merged.active = true;
1390
+ merged.updated_at = nowIsoStr;
1391
+ merged.receipt = receipt;
1392
+ if (sessionId && typeof merged.session_id !== "string") merged.session_id = sessionId;
1393
+
1394
+ const fromPhase =
1395
+ typeof existingPayload.current_phase === "string" ? existingPayload.current_phase.trim() : undefined;
1396
+ const toPhase = merged.current_phase as string;
1397
+ const manifestStates = new Set(getSkillManifest(mode).states.map(state => state.id));
1398
+ if (!manifestStates.has(toPhase) && !forced) {
1399
+ throw new StateCommandError(2, `unknown ${mode} phase "${toPhase}"; use --force to bypass`);
1400
+ }
1401
+ if (fromPhase && toPhase && isKnownWorkflowState(mode, fromPhase) && isKnownWorkflowState(mode, toPhase)) {
1402
+ if (!isValidTransition(mode, fromPhase, toPhase) && !forced) {
1403
+ throw new StateCommandError(
1404
+ 2,
1405
+ `invalid ${mode} phase transition from ${fromPhase} to ${toPhase}; use --force to bypass`,
1406
+ );
1407
+ }
1408
+ }
1421
1409
 
1422
- const {
1423
- warning: outOfBandWarning,
1424
- stamped,
1425
- revision: stampedRevision,
1426
- } = await writeJsonAtomic(cwd, filePath, merged, "write", {
1427
- sessionId,
1428
- skill: mode,
1429
- mutationId,
1430
- force: forced,
1431
- fromPhase,
1432
- toPhase,
1433
- });
1434
- const stampedReceipt = isPlainObject(stamped.receipt) ? stamped.receipt : {};
1435
-
1436
- const phase = typeof merged.current_phase === "string" ? merged.current_phase : undefined;
1437
- const active = merged.active !== false;
1438
- // Reflect the lock-owned mode-state revision onto the in-memory payload so the active-state/HUD
1439
- // sync derives a `sourceRevision` from the revision this write actually owns (computed inside the
1440
- // writer lock), not the stale pre-write value or a post-lock re-read a concurrent writer could
1441
- // have advanced; otherwise the active-state writer stale-skips the update and the mirror keeps the
1442
- // prior phase (e.g. staying "interviewing" after a "handoff" write).
1443
- merged.state_revision = stampedRevision;
1444
- await syncWorkflowSkillState({ cwd, mode, sessionId, threadId, turnId, active, phase, payload: merged, receipt });
1445
- await touchStateActivityMarker(cwd, sessionId, filePath);
1410
+ const validation = validateWorkflowStateEnvelope(mode, merged);
1411
+ if (!validation.valid) throw new StateCommandError(2, validation.error ?? `invalid ${mode} state envelope`);
1412
+
1413
+ const {
1414
+ warning: outOfBandWarning,
1415
+ stamped,
1416
+ revision: stampedRevision,
1417
+ } = await writeJsonAtomic(cwd, filePath, merged, "write", {
1418
+ sessionId,
1419
+ skill: mode,
1420
+ mutationId,
1421
+ force: forced,
1422
+ fromPhase,
1423
+ toPhase,
1424
+ lockHeld: true,
1425
+ });
1426
+ const stampedReceipt = isPlainObject(stamped.receipt) ? stamped.receipt : {};
1427
+
1428
+ const phase = typeof merged.current_phase === "string" ? merged.current_phase : undefined;
1429
+ const active = merged.active !== false;
1430
+ // Reflect the lock-owned mode-state revision onto the in-memory payload so the active-state/HUD
1431
+ // sync derives a `sourceRevision` from the revision this write actually owns (computed inside the
1432
+ // writer lock), not the stale pre-write value or a post-lock re-read a concurrent writer could
1433
+ // have advanced; otherwise the active-state writer stale-skips the update and the mirror keeps the
1434
+ // prior phase (e.g. staying "interviewing" after a "handoff" write).
1435
+ merged.state_revision = stampedRevision;
1436
+ await syncWorkflowSkillState({
1437
+ cwd,
1438
+ mode,
1439
+ sessionId,
1440
+ threadId,
1441
+ turnId,
1442
+ active,
1443
+ phase,
1444
+ payload: merged,
1445
+ receipt,
1446
+ });
1447
+ await touchStateActivityMarker(cwd, sessionId, filePath);
1446
1448
 
1447
- return {
1448
- status: 0,
1449
- stdout: renderCliWriteReceipt({
1450
- ok: true,
1451
- skill: mode,
1452
- state_path: filePath,
1453
- current_phase: phase,
1454
- active,
1455
- mutation_id: typeof stampedReceipt.mutation_id === "string" ? stampedReceipt.mutation_id : mutationId,
1456
- status: typeof stampedReceipt.status === "string" ? stampedReceipt.status : undefined,
1457
- content_sha256: stampedReceipt.content_sha256,
1458
- }),
1459
- ...(outOfBandWarning ? { stderr: `${outOfBandWarning}\n` } : {}),
1460
- };
1449
+ return {
1450
+ status: 0,
1451
+ stdout: renderCliWriteReceipt({
1452
+ ok: true,
1453
+ skill: mode,
1454
+ state_path: receipt.state_path,
1455
+ current_phase: phase,
1456
+ active,
1457
+ mutation_id: typeof stampedReceipt.mutation_id === "string" ? stampedReceipt.mutation_id : mutationId,
1458
+ status: typeof stampedReceipt.status === "string" ? stampedReceipt.status : undefined,
1459
+ content_sha256: stampedReceipt.content_sha256,
1460
+ }),
1461
+ ...(outOfBandWarning ? { stderr: `${outOfBandWarning}\n` } : {}),
1462
+ };
1463
+ },
1464
+ { cwd },
1465
+ );
1461
1466
  }
1462
1467
 
1463
1468
  async function handleClear(
@@ -1476,74 +1481,135 @@ async function handleClear(
1476
1481
 
1477
1482
  const filePath = modeStateFile(cwd, mode, sessionId);
1478
1483
  const forced = hasFlag(args, "--force");
1479
- const existingRead = await readExistingStateForMutation(filePath);
1480
- if (existingRead.kind === "corrupt" && !forced) {
1484
+ return await withWorkflowStateLock(
1485
+ filePath,
1486
+ async () => {
1487
+ const existingRead = await readExistingStateForMutation(filePath);
1488
+ if (existingRead.kind === "corrupt" && !forced) {
1489
+ throw new StateCommandError(
1490
+ 2,
1491
+ `existing state for ${mode} is corrupt or tampered (${existingRead.error}); use --force to overwrite`,
1492
+ );
1493
+ }
1494
+ const existing = existingRead.kind === "valid" ? existingRead.value : {};
1495
+ const staleReason = await describeStaleClearState(cwd, sessionId, mode, existing);
1496
+ if (staleReason && !forced) {
1497
+ throw new StateCommandError(
1498
+ 2,
1499
+ `existing state for ${mode} is stale (${staleReason}); use --force to clear`,
1500
+ );
1501
+ }
1502
+ const clearedAt = nowIso();
1503
+ const cleared: Record<string, unknown> = {
1504
+ skill: mode,
1505
+ ...existing,
1506
+ active: false,
1507
+ current_phase: "complete",
1508
+ updated_at: clearedAt,
1509
+ version: WORKFLOW_STATE_VERSION,
1510
+ };
1511
+ cleared.skill = mode;
1512
+ const mutationId = `${mode}:clear:${clearedAt}`;
1513
+ const receipt = buildWorkflowStateReceipt({
1514
+ cwd,
1515
+ skill: mode,
1516
+ owner: "gjc-state-cli",
1517
+ command: `gjc state ${mode} clear`,
1518
+ sessionId,
1519
+ nowIso: clearedAt,
1520
+ mutationId,
1521
+ });
1522
+ cleared.receipt = receipt;
1523
+ const { warning: outOfBandWarning, stamped } = await writeJsonAtomic(cwd, filePath, cleared, "clear", {
1524
+ sessionId,
1525
+ skill: mode,
1526
+ mutationId,
1527
+ force: forced,
1528
+ fromPhase: typeof existing.current_phase === "string" ? existing.current_phase : undefined,
1529
+ toPhase: "complete",
1530
+ lockHeld: true,
1531
+ });
1532
+ const stampedReceipt = isPlainObject(stamped.receipt) ? stamped.receipt : {};
1533
+
1534
+ await syncWorkflowSkillState({
1535
+ cwd,
1536
+ mode,
1537
+ sessionId,
1538
+ threadId,
1539
+ turnId,
1540
+ active: false,
1541
+ phase: "complete",
1542
+ payload: cleared,
1543
+ });
1544
+ await touchStateActivityMarker(cwd, sessionId, filePath);
1545
+ return {
1546
+ status: 0,
1547
+ stdout: renderCliWriteReceipt({
1548
+ ok: true,
1549
+ skill: mode,
1550
+ state_path: receipt.state_path,
1551
+ active: false,
1552
+ current_phase: typeof cleared.current_phase === "string" ? cleared.current_phase : undefined,
1553
+ mutation_id: typeof stampedReceipt.mutation_id === "string" ? stampedReceipt.mutation_id : mutationId,
1554
+ status: typeof stampedReceipt.status === "string" ? stampedReceipt.status : undefined,
1555
+ content_sha256: stampedReceipt.content_sha256,
1556
+ }),
1557
+ ...(outOfBandWarning ? { stderr: `${outOfBandWarning}\n` } : {}),
1558
+ };
1559
+ },
1560
+ { cwd },
1561
+ );
1562
+ }
1563
+
1564
+ const DEEP_INTERVIEW_INTENT_ID_RE = /(?:artifact|surface|integration|constraint):[a-z0-9][a-z0-9._/-]{0,127}/g;
1565
+
1566
+ async function assertDeepInterviewHandoffReady(state: Record<string, unknown>): Promise<void> {
1567
+ const envelope = normalizeDeepInterviewEnvelope(state);
1568
+ const inner = envelope.state;
1569
+ if (!inner) return;
1570
+ if (inner.intent_contract === undefined) {
1571
+ if (inner.intent_contract_required === true)
1572
+ throw new StateCommandError(2, "deep-interview handoff requires a locked Round 0 intent contract");
1573
+ return;
1574
+ }
1575
+ assertDeepInterviewIntentManifest(inner.intent_contract);
1576
+ const specPath = typeof state.spec_path === "string" ? state.spec_path : undefined;
1577
+ const expectedSha = typeof state.spec_sha256 === "string" ? state.spec_sha256 : undefined;
1578
+ if (!specPath || !expectedSha)
1579
+ throw new StateCommandError(2, "deep-interview handoff requires a persisted intent-validated spec");
1580
+ let content: string;
1581
+ try {
1582
+ content = await fs.readFile(specPath, "utf-8");
1583
+ } catch (error) {
1481
1584
  throw new StateCommandError(
1482
1585
  2,
1483
- `existing state for ${mode} is corrupt or tampered (${existingRead.error}); use --force to overwrite`,
1586
+ `deep-interview handoff cannot read persisted spec: ${error instanceof Error ? error.message : String(error)}`,
1484
1587
  );
1485
1588
  }
1486
- const existing = existingRead.kind === "valid" ? existingRead.value : {};
1487
- const staleReason = await describeStaleClearState(cwd, sessionId, mode, existing);
1488
- if (staleReason && !forced) {
1489
- throw new StateCommandError(2, `existing state for ${mode} is stale (${staleReason}); use --force to clear`);
1589
+ if (createHash("sha256").update(content).digest("hex") !== expectedSha)
1590
+ throw new StateCommandError(2, "deep-interview handoff spec hash mismatch");
1591
+ const observedIds = [...new Set(content.match(DEEP_INTERVIEW_INTENT_ID_RE) ?? [])].sort();
1592
+ const rounds = Array.isArray(inner.rounds)
1593
+ ? inner.rounds
1594
+ .filter(
1595
+ (round): round is Record<string, unknown> =>
1596
+ Boolean(round) && typeof round === "object" && !Array.isArray(round),
1597
+ )
1598
+ .map(round => ({ round: round.round, answer_hash: round.answer_hash }))
1599
+ : [];
1600
+ try {
1601
+ assertDeepInterviewIntentReview(
1602
+ inner.intent_review,
1603
+ inner.intent_contract as DeepInterviewIntentManifest,
1604
+ observedIds,
1605
+ rounds,
1606
+ );
1607
+ } catch (error) {
1608
+ throw new StateCommandError(
1609
+ 2,
1610
+ `deep-interview handoff intent validation failed: ${error instanceof Error ? error.message : String(error)}`,
1611
+ );
1490
1612
  }
1491
- const clearedAt = nowIso();
1492
- const cleared: Record<string, unknown> = {
1493
- skill: mode,
1494
- ...existing,
1495
- active: false,
1496
- current_phase: "complete",
1497
- updated_at: clearedAt,
1498
- version: WORKFLOW_STATE_VERSION,
1499
- };
1500
- cleared.skill = mode;
1501
- const mutationId = `${mode}:clear:${clearedAt}`;
1502
- const receipt = buildWorkflowStateReceipt({
1503
- cwd,
1504
- skill: mode,
1505
- owner: "gjc-state-cli",
1506
- command: `gjc state ${mode} clear`,
1507
- sessionId,
1508
- nowIso: clearedAt,
1509
- mutationId,
1510
- });
1511
- cleared.receipt = receipt;
1512
- const { warning: outOfBandWarning, stamped } = await writeJsonAtomic(cwd, filePath, cleared, "clear", {
1513
- sessionId,
1514
- skill: mode,
1515
- mutationId,
1516
- force: forced,
1517
- fromPhase: typeof existing.current_phase === "string" ? existing.current_phase : undefined,
1518
- toPhase: "complete",
1519
- });
1520
- const stampedReceipt = isPlainObject(stamped.receipt) ? stamped.receipt : {};
1521
-
1522
- await syncWorkflowSkillState({
1523
- cwd,
1524
- mode,
1525
- sessionId,
1526
- threadId,
1527
- turnId,
1528
- active: false,
1529
- phase: "complete",
1530
- payload: cleared,
1531
- });
1532
- await touchStateActivityMarker(cwd, sessionId, filePath);
1533
- return {
1534
- status: 0,
1535
- stdout: renderCliWriteReceipt({
1536
- ok: true,
1537
- skill: mode,
1538
- state_path: filePath,
1539
- active: false,
1540
- current_phase: typeof cleared.current_phase === "string" ? cleared.current_phase : undefined,
1541
- mutation_id: typeof stampedReceipt.mutation_id === "string" ? stampedReceipt.mutation_id : mutationId,
1542
- status: typeof stampedReceipt.status === "string" ? stampedReceipt.status : undefined,
1543
- content_sha256: stampedReceipt.content_sha256,
1544
- }),
1545
- ...(outOfBandWarning ? { stderr: `${outOfBandWarning}\n` } : {}),
1546
- };
1547
1613
  }
1548
1614
 
1549
1615
  /**
@@ -1565,7 +1631,7 @@ async function handleClear(
1565
1631
  * the phase remains in `skill-active-state.json` until a chain call (or
1566
1632
  * explicit `clear`) demotes it.
1567
1633
  */
1568
- async function handleHandoff(
1634
+ async function handleHandoffUnlocked(
1569
1635
  args: readonly string[],
1570
1636
  cwd: string,
1571
1637
  positionalSkill: string | undefined,
@@ -1619,18 +1685,19 @@ async function handleHandoff(
1619
1685
  nowIso: handoffAt,
1620
1686
  mutationId,
1621
1687
  });
1688
+ const normalizedCaller =
1689
+ caller === "deep-interview"
1690
+ ? (normalizeDeepInterviewEnvelope(migrateWorkflowState(existingCaller, caller).state) as Record<
1691
+ string,
1692
+ unknown
1693
+ >)
1694
+ : migrateWorkflowState(existingCaller, caller).state;
1695
+ if (caller === "deep-interview") await assertDeepInterviewHandoffReady(normalizedCaller);
1622
1696
 
1623
1697
  // Runtime callees have no native mode-state to clear later, so do not
1624
1698
  // persist them as active-state entries; the prompt observer tracks them
1625
1699
  // in memory the same way direct `/skill:<runtime>` invocation does.
1626
1700
  if (!calleeIsWorkflow) {
1627
- const normalizedCaller =
1628
- caller === "deep-interview"
1629
- ? (normalizeDeepInterviewEnvelope(migrateWorkflowState(existingCaller, caller).state) as Record<
1630
- string,
1631
- unknown
1632
- >)
1633
- : migrateWorkflowState(existingCaller, caller).state;
1634
1701
  const mergedCallerState: Record<string, unknown> = {
1635
1702
  ...normalizedCaller,
1636
1703
  skill: caller,
@@ -1727,13 +1794,6 @@ async function handleHandoff(
1727
1794
  });
1728
1795
 
1729
1796
  const calleeInitial = initialPhaseForSkill(callee);
1730
- const normalizedCaller =
1731
- caller === "deep-interview"
1732
- ? (normalizeDeepInterviewEnvelope(migrateWorkflowState(existingCaller, caller).state) as Record<
1733
- string,
1734
- unknown
1735
- >)
1736
- : migrateWorkflowState(existingCaller, caller).state;
1737
1797
  const normalizedCallee =
1738
1798
  callee === "deep-interview"
1739
1799
  ? (normalizeDeepInterviewEnvelope(migrateWorkflowState(existingCallee, callee).state) as Record<
@@ -1884,6 +1944,24 @@ async function handleHandoff(
1884
1944
  };
1885
1945
  }
1886
1946
 
1947
+ async function handleHandoff(
1948
+ args: readonly string[],
1949
+ cwd: string,
1950
+ positionalSkill: string | undefined,
1951
+ ): Promise<StateCommandResult> {
1952
+ const selectors = await resolveSelectors(args, cwd, positionalSkill, "handoff");
1953
+ // Serialize concurrent handoffs on a dedicated sentinel lock, NOT on the
1954
+ // derived `skill-active-state.json` cache. The inner transaction
1955
+ // (applyHandoffToActiveState / syncSkillActiveState -> rebuildActiveSnapshot)
1956
+ // re-locks that cache file, and `withFileLock` is not reentrant: holding the
1957
+ // active-state lock here made the inner rebuild self-contend and fail after
1958
+ // all retries whenever `cwd === process.cwd()` (the real CLI case). Pass
1959
+ // `{ cwd }` so the sentinel resolves against the handoff cwd rather than
1960
+ // `process.cwd()`.
1961
+ const handoffLock = path.join(sessionStateDir(cwd, selectors.gjcSessionId), "handoff");
1962
+ return withWorkflowStateLock(handoffLock, async () => handleHandoffUnlocked(args, cwd, positionalSkill), { cwd });
1963
+ }
1964
+
1887
1965
  async function handleContract(
1888
1966
  args: readonly string[],
1889
1967
  cwd: string,
@@ -2253,7 +2331,7 @@ export async function runNativeStateCommand(args: string[], cwd = process.cwd())
2253
2331
  return { status: 2, stderr: `Unknown gjc state command: ${parsed.action}\n` };
2254
2332
  }
2255
2333
  } catch (error) {
2256
- if (error instanceof StateCommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
2334
+ if (error instanceof CommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
2257
2335
  if (error instanceof SessionResolutionError) return { status: 2, stderr: `${error.message}\n` };
2258
2336
  return { status: 1, stderr: `${error instanceof Error ? error.message : String(error)}\n` };
2259
2337
  }