@gajae-code/coding-agent 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/notify-cli.d.ts +1 -0
  4. package/dist/types/cli/session-picker.d.ts +2 -1
  5. package/dist/types/cli/setup-cli.d.ts +1 -1
  6. package/dist/types/commands/mcp-serve.d.ts +33 -0
  7. package/dist/types/commands/mcp.d.ts +1 -1
  8. package/dist/types/commands/notify.d.ts +0 -2
  9. package/dist/types/commands/sdk.d.ts +1 -1
  10. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  11. package/dist/types/config/file-lock.d.ts +14 -4
  12. package/dist/types/config/keybindings.d.ts +42 -1
  13. package/dist/types/config/model-auth.d.ts +3 -0
  14. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  15. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  16. package/dist/types/config/model-equivalence.d.ts +20 -0
  17. package/dist/types/config/model-registry.d.ts +16 -15
  18. package/dist/types/config/model-resolver.d.ts +28 -18
  19. package/dist/types/config/models-config-schema.d.ts +11 -0
  20. package/dist/types/config/settings-schema.d.ts +56 -1
  21. package/dist/types/config/settings.d.ts +14 -11
  22. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  23. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  24. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  25. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  26. package/dist/types/discovery/helpers.d.ts +0 -6
  27. package/dist/types/edit/index.d.ts +3 -0
  28. package/dist/types/eval/py/env.d.ts +9 -0
  29. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  30. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  31. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  32. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  33. package/dist/types/extensibility/shared-events.d.ts +2 -0
  34. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  35. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  36. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  37. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  38. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  39. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  40. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  41. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  42. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  43. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  44. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  45. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  46. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  47. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  49. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  50. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  51. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  52. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  53. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  54. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  55. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  56. package/dist/types/goals/runtime.d.ts +1 -0
  57. package/dist/types/goals/state.d.ts +8 -0
  58. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  59. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  60. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  61. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  62. package/dist/types/hindsight/state.d.ts +11 -4
  63. package/dist/types/lsp/lspmux.d.ts +5 -3
  64. package/dist/types/lsp/path-trust.d.ts +1 -0
  65. package/dist/types/main.d.ts +3 -2
  66. package/dist/types/memories/storage.d.ts +1 -0
  67. package/dist/types/modes/action-registry.d.ts +29 -0
  68. package/dist/types/modes/components/chrome.d.ts +14 -0
  69. package/dist/types/modes/components/command-palette.d.ts +27 -0
  70. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  71. package/dist/types/modes/components/index.d.ts +2 -1
  72. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  73. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  74. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  75. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  76. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  77. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  78. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  79. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  80. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  81. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  82. package/dist/types/modes/components/status-line.d.ts +1 -94
  83. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  84. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  85. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  86. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  87. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  88. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  89. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  90. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  91. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  92. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  93. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  94. package/dist/types/modes/interactive-mode.d.ts +19 -17
  95. package/dist/types/modes/jobs-observer.d.ts +2 -0
  96. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  97. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  98. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  99. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  100. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  101. package/dist/types/modes/types.d.ts +20 -13
  102. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  103. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  104. package/dist/types/sdk/broker/broker.d.ts +78 -12
  105. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  106. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  107. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  108. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  109. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  110. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  111. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  112. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  113. package/dist/types/sdk/bus/config.d.ts +11 -0
  114. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  115. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  116. package/dist/types/sdk/bus/index.d.ts +27 -0
  117. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  118. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  119. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  120. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  121. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  122. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  123. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  124. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  125. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  126. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  127. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  128. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  129. package/dist/types/sdk/host/host.d.ts +2 -0
  130. package/dist/types/sdk/index.d.ts +1 -0
  131. package/dist/types/sdk/session-directory.d.ts +56 -0
  132. package/dist/types/session/agent-session.d.ts +82 -47
  133. package/dist/types/session/agent-storage.d.ts +7 -0
  134. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  135. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  136. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  137. package/dist/types/session/messages.d.ts +16 -0
  138. package/dist/types/session/session-manager-internal.d.ts +14 -0
  139. package/dist/types/session/session-manager.d.ts +82 -12
  140. package/dist/types/session/session-storage.d.ts +43 -3
  141. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  142. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  143. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  144. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  145. package/dist/types/system-prompt.d.ts +2 -11
  146. package/dist/types/task/executor.d.ts +3 -1
  147. package/dist/types/thinking.d.ts +7 -0
  148. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  149. package/dist/types/tools/ask.d.ts +64 -2
  150. package/dist/types/tools/bash.d.ts +3 -0
  151. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  152. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  153. package/dist/types/tools/cron.d.ts +2 -0
  154. package/dist/types/tools/fetch.d.ts +1 -0
  155. package/dist/types/tools/index.d.ts +30 -15
  156. package/dist/types/tools/output-meta.d.ts +1 -0
  157. package/dist/types/tools/read.d.ts +3 -0
  158. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  159. package/dist/types/utils/event-bus.d.ts +1 -1
  160. package/dist/types/web/search/index.d.ts +3 -1
  161. package/package.json +21 -9
  162. package/scripts/build-sdk-package-smoke.ts +34 -1
  163. package/scripts/generate-hotkeys-docs.ts +74 -0
  164. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  165. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  166. package/src/async/job-manager.ts +49 -6
  167. package/src/cli/config-cli.ts +75 -2
  168. package/src/cli/notify-cli.ts +92 -26
  169. package/src/cli/session-picker.ts +20 -3
  170. package/src/cli/setup-cli.ts +1 -1
  171. package/src/cli.ts +1 -1
  172. package/src/commands/config.ts +1 -1
  173. package/src/commands/harness.ts +34 -106
  174. package/src/commands/launch.ts +6 -4
  175. package/src/commands/mcp-serve.ts +85 -8
  176. package/src/commands/mcp.ts +2 -2
  177. package/src/commands/notify.ts +8 -4
  178. package/src/commands/sdk.ts +61 -40
  179. package/src/commands/ultragoal.ts +4 -1
  180. package/src/config/atomic-yaml-patch.ts +80 -13
  181. package/src/config/file-lock.ts +105 -43
  182. package/src/config/keybindings.ts +64 -20
  183. package/src/config/model-auth.ts +9 -0
  184. package/src/config/model-bindings-applier.ts +139 -0
  185. package/src/config/model-discovery-manager.ts +224 -0
  186. package/src/config/model-equivalence.ts +66 -0
  187. package/src/config/model-registry.ts +169 -302
  188. package/src/config/model-resolver.ts +361 -369
  189. package/src/config/models-config-schema.ts +5 -4
  190. package/src/config/settings-schema.ts +159 -2
  191. package/src/config/settings.ts +458 -482
  192. package/src/coordinator-mcp/policy.ts +76 -11
  193. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  194. package/src/coordinator-mcp/question-state.ts +778 -0
  195. package/src/coordinator-mcp/server.ts +1702 -238
  196. package/src/debug/index.ts +1 -1
  197. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  198. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  199. package/src/discovery/helpers.ts +1 -50
  200. package/src/edit/index.ts +64 -0
  201. package/src/eval/py/env.ts +26 -0
  202. package/src/eval/py/kernel.ts +5 -3
  203. package/src/extensibility/extensions/loader.ts +9 -1
  204. package/src/extensibility/extensions/runner.ts +12 -3
  205. package/src/extensibility/extensions/types.ts +39 -1
  206. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  207. package/src/extensibility/hooks/runner.ts +2 -2
  208. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  209. package/src/extensibility/shared-events.ts +2 -0
  210. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  211. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  212. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  213. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  214. package/src/gjc-runtime/launch-tmux.ts +2 -5
  215. package/src/gjc-runtime/launch-worktree.ts +5 -5
  216. package/src/gjc-runtime/linux-proc.ts +71 -0
  217. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  218. package/src/gjc-runtime/session-resolution.ts +18 -3
  219. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  220. package/src/gjc-runtime/state-migrations.ts +1 -1
  221. package/src/gjc-runtime/state-runtime.ts +320 -249
  222. package/src/gjc-runtime/state-writer.ts +36 -40
  223. package/src/gjc-runtime/team-launch.ts +319 -0
  224. package/src/gjc-runtime/team-notify.ts +326 -0
  225. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  226. package/src/gjc-runtime/team-store.ts +828 -0
  227. package/src/gjc-runtime/team-workers.ts +962 -0
  228. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  229. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  230. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  231. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  232. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  233. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  234. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  235. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  236. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  237. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  238. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  239. package/src/goals/continuation-timeout-guard.ts +79 -0
  240. package/src/goals/runtime.ts +7 -5
  241. package/src/goals/state.ts +18 -0
  242. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  243. package/src/harness-control-plane/phase-rollup.ts +4 -7
  244. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  245. package/src/harness-control-plane/receipt-spool.ts +51 -7
  246. package/src/harness-control-plane/session-lease.ts +12 -130
  247. package/src/hindsight/backend.ts +26 -27
  248. package/src/hindsight/state.ts +101 -32
  249. package/src/hooks/skill-state.ts +17 -11
  250. package/src/internal-urls/docs-index.generated.ts +21 -18
  251. package/src/lsp/client.ts +1 -1
  252. package/src/lsp/config.ts +145 -26
  253. package/src/lsp/index.ts +1 -1
  254. package/src/lsp/lspmux.ts +39 -10
  255. package/src/lsp/path-trust.ts +81 -0
  256. package/src/main.ts +28 -8
  257. package/src/memories/index.ts +1 -0
  258. package/src/memories/storage.ts +6 -2
  259. package/src/modes/action-registry.ts +165 -0
  260. package/src/modes/components/chrome.ts +40 -0
  261. package/src/modes/components/command-palette.ts +168 -0
  262. package/src/modes/components/custom-editor.ts +1 -1
  263. package/src/modes/components/custom-provider-wizard.ts +51 -14
  264. package/src/modes/components/index.ts +2 -1
  265. package/src/modes/components/pet-selector.ts +11 -17
  266. package/src/modes/components/plan-preview-overlay.ts +295 -0
  267. package/src/modes/components/queue-mode-selector.ts +10 -27
  268. package/src/modes/components/queue-pane.ts +79 -0
  269. package/src/modes/components/read-tool-group.ts +13 -0
  270. package/src/modes/components/session-observer-overlay.ts +207 -772
  271. package/src/modes/components/sessions-dashboard.ts +181 -0
  272. package/src/modes/components/settings-selector.ts +1 -1
  273. package/src/modes/components/show-images-selector.ts +10 -24
  274. package/src/modes/components/status-line/types.ts +1 -1
  275. package/src/modes/components/status-line.ts +1 -969
  276. package/src/modes/components/tasks-pane.ts +73 -0
  277. package/src/modes/components/theme-selector.ts +11 -31
  278. package/src/modes/components/tool-execution.ts +22 -0
  279. package/src/modes/components/tool-status-header.ts +1056 -0
  280. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  281. package/src/modes/controllers/btw-controller.ts +1 -0
  282. package/src/modes/controllers/event-controller.ts +3 -2
  283. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  284. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  285. package/src/modes/controllers/input-controller.ts +764 -229
  286. package/src/modes/controllers/mode-gate.ts +24 -0
  287. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  288. package/src/modes/controllers/selector-controller.ts +225 -10
  289. package/src/modes/controllers/stt-controller.ts +125 -0
  290. package/src/modes/interactive-mode.ts +289 -1150
  291. package/src/modes/jobs-observer.ts +3 -0
  292. package/src/modes/runtime-init.ts +8 -0
  293. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  294. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  295. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  296. package/src/modes/tasks-aggregator.ts +199 -0
  297. package/src/modes/transcript-item-registry.ts +229 -0
  298. package/src/modes/types.ts +45 -18
  299. package/src/modes/utils/ui-helpers.ts +17 -9
  300. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  301. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  302. package/src/prompts/agents/architect.md +2 -8
  303. package/src/prompts/agents/critic.md +2 -8
  304. package/src/prompts/agents/executor.md +3 -0
  305. package/src/prompts/agents/planner.md +4 -12
  306. package/src/prompts/system/custom-system-prompt.md +1 -23
  307. package/src/prompts/system/plan-mode-approved.md +8 -0
  308. package/src/prompts/system/system-prompt.md +17 -96
  309. package/src/prompts/tools/bash.md +4 -4
  310. package/src/prompts/tools/browser.md +15 -52
  311. package/src/prompts/tools/find.md +2 -2
  312. package/src/prompts/tools/hashline.md +13 -55
  313. package/src/prompts/tools/read.md +3 -6
  314. package/src/prompts/tools/search-tool-bm25.md +0 -2
  315. package/src/prompts/tools/search.md +2 -4
  316. package/src/prompts/tools/skill-discovery.md +1 -2
  317. package/src/prompts/tools/task.md +1 -1
  318. package/src/runtime-mcp/manager.ts +1 -1
  319. package/src/sdk/broker/broker.ts +206 -78
  320. package/src/sdk/broker/discovery.ts +1 -1
  321. package/src/sdk/broker/ensure.ts +3 -1
  322. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  323. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  324. package/src/sdk/broker/lifecycle.ts +1399 -142
  325. package/src/sdk/broker/process-incarnation.ts +5 -13
  326. package/src/sdk/broker/session-index.ts +145 -56
  327. package/src/sdk/broker/state-version.ts +21 -0
  328. package/src/sdk/broker/transport.ts +1 -1
  329. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  330. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  331. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  332. package/src/sdk/bus/config-commands.ts +5 -4
  333. package/src/sdk/bus/config.ts +98 -0
  334. package/src/sdk/bus/conversation-store.ts +5 -29
  335. package/src/sdk/bus/daemon-paths.ts +2 -0
  336. package/src/sdk/bus/html-format.ts +20 -3
  337. package/src/sdk/bus/index.ts +778 -93
  338. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  339. package/src/sdk/bus/natives-augment.d.ts +26 -1
  340. package/src/sdk/bus/notification-service.ts +168 -20
  341. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  342. package/src/sdk/bus/recent-activity.ts +188 -74
  343. package/src/sdk/bus/rich-document.ts +258 -0
  344. package/src/sdk/bus/rich-render.ts +62 -70
  345. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  346. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  347. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  348. package/src/sdk/bus/threaded-inbound.ts +8 -3
  349. package/src/sdk/bus/threaded-render.ts +57 -1
  350. package/src/sdk/bus/topic-registry.ts +120 -13
  351. package/src/sdk/host/control/dispatch.ts +14 -2
  352. package/src/sdk/host/control/operations.ts +6 -1
  353. package/src/sdk/host/host.ts +31 -1
  354. package/src/sdk/host/query/handlers.ts +20 -22
  355. package/src/sdk/host/query/revision-store.ts +80 -57
  356. package/src/sdk/index.ts +1 -0
  357. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  358. package/src/sdk/session-directory.ts +141 -0
  359. package/src/sdk/session.ts +458 -479
  360. package/src/session/agent-session.ts +1700 -583
  361. package/src/session/agent-storage.ts +18 -0
  362. package/src/session/cache-economics.ts +25 -8
  363. package/src/session/fallback-chain-controller.ts +53 -3
  364. package/src/session/internal/managed-session-scope.ts +2226 -0
  365. package/src/session/internal/managed-session-storage.ts +453 -0
  366. package/src/session/messages.ts +91 -2
  367. package/src/session/session-manager-internal.ts +33 -0
  368. package/src/session/session-manager.ts +1112 -333
  369. package/src/session/session-storage.ts +444 -39
  370. package/src/session/volatile-context-pruning.ts +78 -0
  371. package/src/setup/credential-auto-import.ts +2 -1
  372. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  373. package/src/setup/provider-onboarding.ts +36 -61
  374. package/src/setup/provider-presets.json +5 -0
  375. package/src/skill-state/active-state.ts +3 -13
  376. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  377. package/src/skill-state/workflow-state-contract.ts +6 -41
  378. package/src/slash-commands/builtin-registry.ts +46 -11
  379. package/src/system-prompt.ts +37 -44
  380. package/src/task/agents.ts +19 -1
  381. package/src/task/executor.ts +90 -6
  382. package/src/task/index.ts +36 -21
  383. package/src/thinking.ts +21 -6
  384. package/src/tool-discovery/tool-index.ts +2 -134
  385. package/src/tools/ask.ts +157 -21
  386. package/src/tools/ast-edit.ts +1 -1
  387. package/src/tools/bash.ts +38 -0
  388. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  389. package/src/tools/browser/tab-supervisor.ts +282 -36
  390. package/src/tools/computer.ts +10 -3
  391. package/src/tools/cron.ts +30 -16
  392. package/src/tools/eval.ts +1 -2
  393. package/src/tools/fetch.ts +11 -4
  394. package/src/tools/index.ts +76 -24
  395. package/src/tools/output-meta.ts +13 -5
  396. package/src/tools/read.ts +37 -1
  397. package/src/tools/resource-gc.ts +2 -2
  398. package/src/tools/search-tool-bm25.ts +11 -74
  399. package/src/utils/event-bus.ts +9 -4
  400. package/src/web/search/index.ts +3 -3
  401. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  402. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  403. package/src/discovery/plugin-dir-roots.ts +0 -28
  404. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -5,7 +5,10 @@
5
5
  * Uses the settings schema as the source of truth for available settings.
6
6
  */
7
7
 
8
+ import * as fs from "node:fs/promises";
9
+ import type * as path from "node:path";
8
10
  import { APP_NAME, getAgentDir } from "@gajae-code/utils";
11
+ import { YAML } from "bun";
9
12
  import chalk from "chalk";
10
13
  import {
11
14
  getDefault,
@@ -25,7 +28,7 @@ import { initXdg } from "./commands/init-xdg";
25
28
  // Types
26
29
  // =============================================================================
27
30
 
28
- export type ConfigAction = "list" | "get" | "set" | "reset" | "path" | "init-xdg";
31
+ export type ConfigAction = "list" | "get" | "set" | "reset" | "path" | "doctor" | "init-xdg";
29
32
 
30
33
  export interface ConfigCommandArgs {
31
34
  action: ConfigAction;
@@ -111,7 +114,7 @@ function getSettingValues(def: CliSettingDef): readonly string[] | undefined {
111
114
  // Argument Parser
112
115
  // =============================================================================
113
116
 
114
- const VALID_ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path", "init-xdg"];
117
+ const VALID_ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path", "doctor", "init-xdg"];
115
118
 
116
119
  /**
117
120
  * Parse config subcommand arguments.
@@ -299,6 +302,9 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
299
302
  case "path":
300
303
  handlePath();
301
304
  break;
305
+ case "doctor":
306
+ handleDoctor(cmd.flags);
307
+ break;
302
308
  case "init-xdg":
303
309
  await initXdg();
304
310
  break;
@@ -443,6 +449,70 @@ function handlePath(): void {
443
449
  console.log(getAgentDir());
444
450
  }
445
451
 
452
+ function handleDoctor(flags: { json?: boolean }): void {
453
+ const report = settings.getSchemaReport();
454
+ if (flags.json) {
455
+ console.log(JSON.stringify(report, null, 2));
456
+ return;
457
+ }
458
+ if (report.issues.length === 0) {
459
+ console.log(chalk.green("Settings schema is healthy."));
460
+ return;
461
+ }
462
+ for (const issue of report.issues) console.log(`${issue.kind}\t${issue.path}\t${issue.detail}`);
463
+ }
464
+
465
+ type ConfigDoctorReport = {
466
+ unknownKeys: string[];
467
+ invalidValues: Array<{ path: string; value: unknown }>;
468
+ legacyShapes: string[];
469
+ };
470
+
471
+ function flattenConfig(value: unknown, prefix = ""): Array<[string, unknown]> {
472
+ if (prefix && ALL_SETTING_PATHS.includes(prefix as SettingPath)) return [[prefix, value]];
473
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return prefix ? [[prefix, value]] : [];
474
+ return Object.entries(value).flatMap(([key, child]) => flattenConfig(child, prefix ? `${prefix}.${key}` : key));
475
+ }
476
+
477
+ function matchesSettingType(path: SettingPath, value: unknown): boolean {
478
+ const definition = SETTINGS_SCHEMA[path];
479
+ switch (definition.type) {
480
+ case "string":
481
+ case "enum":
482
+ return (
483
+ typeof value === "string" && (definition.type !== "enum" || getEnumValues(path)?.includes(value) === true)
484
+ );
485
+ case "number":
486
+ return typeof value === "number" && Number.isFinite(value);
487
+ case "boolean":
488
+ return typeof value === "boolean";
489
+ case "array":
490
+ return Array.isArray(value);
491
+ case "record":
492
+ return value !== null && typeof value === "object" && !Array.isArray(value);
493
+ }
494
+ }
495
+
496
+ export async function inspectConfigFile(configPath: string): Promise<ConfigDoctorReport> {
497
+ const report: ConfigDoctorReport = { unknownKeys: [], invalidValues: [], legacyShapes: [] };
498
+ try {
499
+ const raw = YAML.parse(await fs.readFile(configPath, "utf8"));
500
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
501
+ report.legacyShapes.push("config root is not a mapping");
502
+ return report;
503
+ }
504
+ for (const [settingPath, value] of flattenConfig(raw)) {
505
+ if (!ALL_SETTING_PATHS.includes(settingPath as SettingPath)) report.unknownKeys.push(settingPath);
506
+ else if (!matchesSettingType(settingPath as SettingPath, value))
507
+ report.invalidValues.push({ path: settingPath, value: redactConfigValue(settingPath, value) });
508
+ }
509
+ } catch (error) {
510
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT")
511
+ report.legacyShapes.push(`unable to parse config: ${String(error)}`);
512
+ }
513
+ return report;
514
+ }
515
+
446
516
  // =============================================================================
447
517
  // Help
448
518
  // =============================================================================
@@ -455,8 +525,10 @@ ${chalk.bold("Commands:")}
455
525
  get <key> Get a specific setting value
456
526
  set <key> <value> Set a setting value
457
527
  reset <key> Reset a setting to its default value
528
+ doctor Report unknown, invalid, and pending settings migrations
458
529
  path Print the config directory path
459
530
  init-xdg Initialize XDG Base Directory structure
531
+ doctor Report unknown, invalid, and legacy config entries
460
532
 
461
533
  ${chalk.bold("Options:")}
462
534
  --json Output as JSON
@@ -472,6 +544,7 @@ ${chalk.bold("Examples:")}
472
544
  ${APP_NAME} config list --json
473
545
  ${APP_NAME} config get auth.broker.token --show-secrets
474
546
  ${APP_NAME} config init-xdg
547
+ ${APP_NAME} config doctor --json
475
548
 
476
549
  ${chalk.bold("Boolean Values:")}
477
550
  true, false, yes, no, on, off, 1, 0
@@ -75,6 +75,7 @@ export interface NotifyCommandDeps {
75
75
  tokenPrompt?: () => Promise<string>;
76
76
  setExitCode?: (code: number) => void;
77
77
  exitProcess?: (code: number) => void;
78
+ valuePrompt?: (label: string, masked: boolean) => Promise<string>;
78
79
  /** Optional daemon ownership facts collected by an embedding host. */
79
80
  setupPreflight?: TelegramSetupPreflight;
80
81
  /** Injectable timers and cancellation for setup pairing. */
@@ -99,6 +100,27 @@ export function parseNotifyArgs(args: string[]): NotifyCommandArgs | undefined {
99
100
  const i = rest.indexOf(name);
100
101
  return i >= 0 ? rest[i + 1] : undefined;
101
102
  };
103
+ const valueFlags = [
104
+ "--token",
105
+ "--chat-id",
106
+ "--discord-bot-token",
107
+ "--discord-application-id",
108
+ "--discord-guild-id",
109
+ "--discord-parent-channel-id",
110
+ "--slack-bot-token",
111
+ "--slack-app-token",
112
+ "--slack-workspace-id",
113
+ "--slack-channel-id",
114
+ "--slack-authorized-user-id",
115
+ ];
116
+ if (
117
+ valueFlags.some(name => {
118
+ const index = rest.indexOf(name);
119
+ const value = index >= 0 ? rest[index + 1] : undefined;
120
+ return index >= 0 && (!value || value.startsWith("--"));
121
+ })
122
+ )
123
+ return undefined;
102
124
  const provider = rest[0]?.startsWith("--") ? undefined : rest[0];
103
125
  if (provider !== undefined && provider !== "telegram" && provider !== "discord" && provider !== "slack") {
104
126
  return undefined;
@@ -144,7 +166,7 @@ export function parseNotifyArgs(args: string[]): NotifyCommandArgs | undefined {
144
166
  };
145
167
  }
146
168
 
147
- return { action: "status", rawArgs: args.slice(1) };
169
+ return undefined;
148
170
  }
149
171
 
150
172
  export async function runNotifyCommand(cmd: NotifyCommandArgs, deps: NotifyCommandDeps = {}): Promise<void> {
@@ -221,22 +243,41 @@ async function runSetup(cmd: NotifyCommandArgs, deps: NotifyCommandDeps): Promis
221
243
 
222
244
  function requiredSetupValue(value: string | undefined, flag: string): string {
223
245
  if (!value?.trim()) throw new Error(`${flag} is required for non-interactive setup.`);
246
+ if (value.trim().startsWith("--")) throw new Error(`${flag} must not start with --.`);
224
247
  return value.trim();
225
248
  }
226
249
 
250
+ async function promptSetupValue(
251
+ value: string | undefined,
252
+ flag: string,
253
+ masked: boolean,
254
+ deps: NotifyCommandDeps,
255
+ ): Promise<string> {
256
+ if (value?.trim()) return requiredSetupValue(value, flag);
257
+ if (!resolveSetupInteractive(deps)) return requiredSetupValue(value, flag);
258
+ return requiredSetupValue(await (deps.valuePrompt ?? promptForValue)(`${flag.slice(2)}: `, masked), flag);
259
+ }
260
+
227
261
  async function runDiscordSetup(cmd: NotifyCommandArgs, deps: NotifyCommandDeps): Promise<void> {
228
- const botToken = requiredSetupValue(cmd.discordBotToken, "--discord-bot-token");
229
- const applicationId = requiredSetupValue(cmd.discordApplicationId, "--discord-application-id");
230
- const guildId = requiredSetupValue(cmd.discordGuildId, "--discord-guild-id");
231
- const parentChannelId = requiredSetupValue(cmd.discordParentChannelId, "--discord-parent-channel-id");
262
+ const botToken = await promptSetupValue(cmd.discordBotToken, "--discord-bot-token", true, deps);
263
+ const applicationId = await promptSetupValue(cmd.discordApplicationId, "--discord-application-id", false, deps);
264
+ const guildId = await promptSetupValue(cmd.discordGuildId, "--discord-guild-id", false, deps);
265
+ const parentChannelId = await promptSetupValue(
266
+ cmd.discordParentChannelId,
267
+ "--discord-parent-channel-id",
268
+ false,
269
+ deps,
270
+ );
232
271
  const settings = await getSettings(deps);
233
- settings.set("notifications.discord.botToken", botToken);
234
- settings.set("notifications.discord.applicationId", applicationId);
235
- settings.set("notifications.discord.guildId", guildId);
236
- settings.set("notifications.discord.parentChannelId", parentChannelId);
237
- settings.set("notifications.enabled", true);
238
- if (cmd.redact) settings.set("notifications.redact", true);
239
- await settings.flushOrThrow();
272
+ const patches: SettingsAtomicPatch[] = [
273
+ { path: "notifications.discord.botToken", op: "set", value: botToken },
274
+ { path: "notifications.discord.applicationId", op: "set", value: applicationId },
275
+ { path: "notifications.discord.guildId", op: "set", value: guildId },
276
+ { path: "notifications.discord.parentChannelId", op: "set", value: parentChannelId },
277
+ { path: "notifications.enabled", op: "set", value: true },
278
+ ];
279
+ if (cmd.redact) patches.push({ path: "notifications.redact", op: "set", value: true });
280
+ await settings.commitAtomicBatch(patches);
240
281
  const daemon = await ensureConfiguredProviderDaemon("discord", settings, deps);
241
282
  process.stdout.write(
242
283
  `Discord notifications enabled. botToken=${maskToken(botToken)} applicationId=${applicationId} guildId=${guildId} parentChannelId=${parentChannelId} daemon=${daemon}\n`,
@@ -244,20 +285,24 @@ async function runDiscordSetup(cmd: NotifyCommandArgs, deps: NotifyCommandDeps):
244
285
  }
245
286
 
246
287
  async function runSlackSetup(cmd: NotifyCommandArgs, deps: NotifyCommandDeps): Promise<void> {
247
- const botToken = requiredSetupValue(cmd.slackBotToken, "--slack-bot-token");
248
- const appToken = requiredSetupValue(cmd.slackAppToken, "--slack-app-token");
249
- const workspaceId = requiredSetupValue(cmd.slackWorkspaceId, "--slack-workspace-id");
250
- const channelId = requiredSetupValue(cmd.slackChannelId, "--slack-channel-id");
288
+ const botToken = await promptSetupValue(cmd.slackBotToken, "--slack-bot-token", true, deps);
289
+ const appToken = await promptSetupValue(cmd.slackAppToken, "--slack-app-token", true, deps);
290
+ const workspaceId = await promptSetupValue(cmd.slackWorkspaceId, "--slack-workspace-id", false, deps);
291
+ const channelId = await promptSetupValue(cmd.slackChannelId, "--slack-channel-id", false, deps);
251
292
  const authorizedUserId = cmd.slackAuthorizedUserId?.trim() || undefined;
252
293
  const settings = await getSettings(deps);
253
- settings.set("notifications.slack.botToken", botToken);
254
- settings.set("notifications.slack.appToken", appToken);
255
- settings.set("notifications.slack.workspaceId", workspaceId);
256
- settings.set("notifications.slack.channelId", channelId);
257
- settings.set("notifications.slack.authorizedUserId", authorizedUserId);
258
- settings.set("notifications.enabled", true);
259
- if (cmd.redact) settings.set("notifications.redact", true);
260
- await settings.flushOrThrow();
294
+ const patches: SettingsAtomicPatch[] = [
295
+ { path: "notifications.slack.botToken", op: "set", value: botToken },
296
+ { path: "notifications.slack.appToken", op: "set", value: appToken },
297
+ { path: "notifications.slack.workspaceId", op: "set", value: workspaceId },
298
+ { path: "notifications.slack.channelId", op: "set", value: channelId },
299
+ authorizedUserId === undefined
300
+ ? { path: "notifications.slack.authorizedUserId", op: "unset" }
301
+ : { path: "notifications.slack.authorizedUserId", op: "set", value: authorizedUserId },
302
+ { path: "notifications.enabled", op: "set", value: true },
303
+ ];
304
+ if (cmd.redact) patches.push({ path: "notifications.redact", op: "set", value: true });
305
+ await settings.commitAtomicBatch(patches);
261
306
  const daemon = await ensureConfiguredProviderDaemon("slack", settings, deps);
262
307
  process.stdout.write(
263
308
  `Slack notifications enabled. botToken=${maskToken(botToken)} appToken=${maskToken(appToken)} workspaceId=${workspaceId} channelId=${channelId} authorizedUserId=${authorizedUserId ?? "(unset; inbound denied)"} daemon=${daemon}\n`,
@@ -437,7 +482,8 @@ type TokenPromptInput = NodeJS.ReadStream & {
437
482
 
438
483
  type TokenPromptOutput = Pick<NodeJS.WriteStream, "write">;
439
484
 
440
- export async function promptForToken(
485
+ async function promptForMaskedValue(
486
+ label: string,
441
487
  input: TokenPromptInput = process.stdin,
442
488
  output: TokenPromptOutput = process.stdout,
443
489
  ): Promise<string> {
@@ -448,7 +494,7 @@ export async function promptForToken(
448
494
  throw new Error("notify setup requires a TTY with raw input support unless setupToken is injected.");
449
495
  }
450
496
 
451
- output.write("Telegram BotFather token: ");
497
+ output.write(label);
452
498
  const wasRaw = input.isRaw === true;
453
499
  input.setRawMode(true);
454
500
 
@@ -503,6 +549,26 @@ export async function promptForToken(
503
549
  });
504
550
  }
505
551
 
552
+ export async function promptForToken(
553
+ input: TokenPromptInput = process.stdin,
554
+ output: TokenPromptOutput = process.stdout,
555
+ ): Promise<string> {
556
+ return await promptForMaskedValue("Telegram BotFather token: ", input, output);
557
+ }
558
+
559
+ async function promptForValue(label: string, masked: boolean): Promise<string> {
560
+ if (masked) return await promptForMaskedValue(label);
561
+ if (!process.stdin.isTTY) {
562
+ throw new Error("notify setup requires an interactive TTY unless all setup values are supplied as flags.");
563
+ }
564
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
565
+ try {
566
+ return (await rl.question(label)).trim();
567
+ } finally {
568
+ rl.close();
569
+ }
570
+ }
571
+
506
572
  function resolveSetupInteractive(deps: NotifyCommandDeps): boolean {
507
573
  if (deps.setupInteractive !== undefined) return deps.setupInteractive;
508
574
  return Boolean(process.stdin.isTTY) && !deps.setupChatId?.trim();
@@ -1,13 +1,30 @@
1
+ import * as path from "node:path";
1
2
  import { ProcessTerminal, TUI } from "@gajae-code/tui";
3
+ import { pathIsWithin } from "@gajae-code/utils";
2
4
  import { type SessionSelectionResult, SessionSelectorComponent } from "../modes/components/session-selector";
3
5
  import { type SessionInfo, SessionManager } from "../session/session-manager";
4
6
  import { FileSessionStorage } from "../session/session-storage";
5
7
 
8
+ export async function deleteSessionPickerCandidate(sessionPath: string, explicitSessionDir?: string): Promise<void> {
9
+ if (!explicitSessionDir) {
10
+ await SessionManager.deleteManagedCandidate(sessionPath);
11
+ return;
12
+ }
13
+ const root = path.resolve(explicitSessionDir);
14
+ const target = path.resolve(sessionPath);
15
+ if (!pathIsWithin(root, target))
16
+ throw new Error("Explicit session deletion escaped the configured session directory.");
17
+ await new FileSessionStorage().deleteSessionWithArtifacts(target);
18
+ }
19
+
6
20
  /** Show the read-only TUI session picker and return the user's consent intent. */
7
- export async function selectSession(sessions: SessionInfo[]): Promise<SessionSelectionResult> {
21
+ export async function selectSession(
22
+ sessions: SessionInfo[],
23
+ explicitSessionDir?: string,
24
+ ): Promise<SessionSelectionResult> {
8
25
  const { promise, resolve } = Promise.withResolvers<SessionSelectionResult>();
9
26
  const ui = new TUI(new ProcessTerminal());
10
- const storage = new FileSessionStorage();
27
+
11
28
  let settled = false;
12
29
  const settle = (selection: SessionSelectionResult): void => {
13
30
  if (settled) return;
@@ -21,7 +38,7 @@ export async function selectSession(sessions: SessionInfo[]): Promise<SessionSel
21
38
  () => settle({ kind: "cancelled" }),
22
39
  () => settle({ kind: "cancelled" }),
23
40
  async session => {
24
- await storage.deleteSessionWithArtifacts(session.path);
41
+ await deleteSessionPickerCandidate(session.path, explicitSessionDir);
25
42
  return true;
26
43
  },
27
44
  SessionManager.inspectSessionTailReadOnly,
@@ -564,7 +564,7 @@ async function confirmImport(count: number): Promise<boolean> {
564
564
  */
565
565
  export interface CredentialsSetupDependencies {
566
566
  openStore?: typeof SqliteAuthCredentialStore.open;
567
- createAuthStorage?: (store: Awaited<ReturnType<typeof SqliteAuthCredentialStore.open>>) => AuthStorage;
567
+ createAuthStorage?: (store: SqliteAuthCredentialStore) => AuthStorage;
568
568
  discover?: Parameters<typeof runExternalCredentialAutoImport>[0]["discover"];
569
569
  }
570
570
 
package/src/cli.ts CHANGED
@@ -9,7 +9,6 @@ import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@g
9
9
  import { APP_NAME, formatBunRuntimeError, MIN_BUN_VERSION, VERSION } from "@gajae-code/utils/dirs";
10
10
  import { runFixtureReport } from "./cli/fixture-report";
11
11
  import { isTmuxOwnerIsolationCliArgv, runTmuxOwnerIsolationCliFromStdin } from "./gjc-runtime/tmux-owner-isolation-cli";
12
- import { runChatDaemonInternal } from "./sdk/bus/chat-daemon-cli";
13
12
 
14
13
  if (Bun.semver.order(Bun.version, MIN_BUN_VERSION) < 0) {
15
14
  process.stderr.write(
@@ -134,6 +133,7 @@ async function runChatDaemonInternalFastPath(argv: string[]): Promise<void> {
134
133
  if (action !== "discord-internal" && action !== "slack-internal") {
135
134
  throw new Error("invalid chat daemon internal fast path");
136
135
  }
136
+ const { runChatDaemonInternal } = await import("./sdk/bus/chat-daemon-cli");
137
137
  await runChatDaemonInternal(action === "discord-internal" ? "discord" : "slack", argv.slice(2));
138
138
  }
139
139
 
@@ -5,7 +5,7 @@ import { Args, Command, Flags } from "@gajae-code/utils/cli";
5
5
  import { type ConfigAction, type ConfigCommandArgs, runConfigCommand } from "../cli/config-cli";
6
6
  import { initTheme } from "../modes/theme/theme";
7
7
 
8
- const ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path", "init-xdg"];
8
+ const ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path", "init-xdg", "doctor"];
9
9
 
10
10
  export default class Config extends Command {
11
11
  static description = "Manage configuration settings";
@@ -25,16 +25,25 @@ import {
25
25
  classifyCgroup,
26
26
  isExactScopedBootstrapSuccessReceipt,
27
27
  isOwnerGenerationBaselineCurrentSync,
28
+ isSafeServerProof,
28
29
  type OwnerGenerationBaseline,
29
30
  type OwnerIsolationProbe,
30
31
  observeOwnerTerminal,
31
32
  ownerProcessStartTime,
32
33
  planTmuxOwnerIsolation,
33
34
  replaceOwnerGenerationSync,
35
+ sameServerIdentity,
34
36
  type TmuxServerProof,
35
37
  } from "../gjc-runtime/tmux-owner-isolation";
36
38
  import { classifyRecovery } from "../harness-control-plane/classifier";
37
39
  import { callEndpoint, EndpointUnreachableError } from "../harness-control-plane/control-endpoint";
40
+ import {
41
+ type CompletedTerminalEvent,
42
+ isOwnerLivenessBlocker,
43
+ needsVanishedOwnerBlock,
44
+ OWNER_STARTUP_BLOCKER,
45
+ reconcileOwnerLifecycle,
46
+ } from "../harness-control-plane/lifecycle-reconciliation";
38
47
  import { type ResolvedOwner, RuntimeOwner, resolveOwner, resolveOwnerLive } from "../harness-control-plane/owner";
39
48
  import { preserveDirtyWorktree } from "../harness-control-plane/preserve";
40
49
  import { RECEIPT_SPOOL_DIR_ENV } from "../harness-control-plane/receipt-spool";
@@ -252,12 +261,6 @@ function pushUnique(out: string[], value: unknown): void {
252
261
  if (typeof value === "string" && !out.includes(value)) out.push(value);
253
262
  }
254
263
 
255
- interface CompletedTerminalEvent {
256
- cursor: number;
257
- createdAt: string;
258
- kind: string;
259
- }
260
-
261
264
  function completedTerminalEvent(events: EventEnvelope[]): CompletedTerminalEvent | null {
262
265
  for (const event of [...events].reverse()) {
263
266
  const signal = (event.evidence as { signal?: unknown } | undefined)?.signal;
@@ -461,73 +464,21 @@ function updateStateWithRestoredOwner(state: SessionState, leasePath: string, re
461
464
  state.updatedAt = nowIso();
462
465
  }
463
466
 
464
- function isOwnerLivenessBlocker(blocker: string): boolean {
465
- return blocker === "detached-owner-not-live" || blocker.startsWith("owner-vanished:");
466
- }
467
-
468
- async function reconcileCompletedOwnerExited(
467
+ async function reconcileLifecycle(
469
468
  root: string,
470
469
  state: SessionState,
471
470
  observation: Observation,
472
471
  completedTerminal: CompletedTerminalEvent | null,
472
+ startupBlocked = false,
473
473
  ): Promise<SessionState> {
474
- if (!completedTerminal || observation.ownerLive || observation.gitDelta !== "clean") return state;
475
- if (state.lifecycle === "completed" || state.lifecycle === "retired") return state;
476
- state.lifecycle = "completed";
477
- state.blockers = state.blockers.filter(blocker => !isOwnerLivenessBlocker(blocker));
478
- state.updatedAt = nowIso();
479
- await writeSessionState(root, state);
480
- return state;
481
- }
482
-
483
- function needsVanishedOwnerBlock(
484
- state: SessionState,
485
- observation: Observation,
486
- completedTerminal: CompletedTerminalEvent | null,
487
- ): boolean {
488
- if (observation.ownerLive || state.lifecycle !== "observing") return false;
489
- if (completedTerminal || observation.observedSignals.includes("completed")) return false;
490
- return observation.observedSignals.some(
491
- signal => signal === "prompt-accepted" || signal === "tool-call" || signal === "streaming",
492
- );
493
- }
494
-
495
- async function markVanishedOwnerBlocked(
496
- root: string,
497
- state: SessionState,
498
- observation: Observation,
499
- completedTerminal: CompletedTerminalEvent | null,
500
- ): Promise<SessionState> {
501
- if (!needsVanishedOwnerBlock(state, observation, completedTerminal)) return state;
502
- const blocker = `owner-vanished:${observation.gitDelta}`;
503
- state.lifecycle = "blocked";
504
- state.blockers = state.blockers.includes(blocker) ? state.blockers : [...state.blockers, blocker];
505
- state.updatedAt = nowIso();
506
- await writeSessionState(root, state);
507
- return state;
508
- }
509
-
510
- const OWNER_STARTUP_BLOCKER = "owner-died-before-first-prompt";
511
-
512
- /**
513
- * Persist an explicit startup blocker when an owner started, reported live, but died before
514
- * accepting the first prompt. This makes the failure an actionable lifecycle state instead of a
515
- * silent `owner-not-live` gate, so observe/recover surface it and recover can respawn the owner.
516
- */
517
- async function markStartupOwnerBlocked(
518
- root: string,
519
- state: SessionState,
520
- ownerExit: OwnerExitEvidence,
521
- ): Promise<SessionState> {
522
- if (!ownerExit.startupBlocker) return state;
523
- if (state.lifecycle === "completed" || state.lifecycle === "retired") return state;
524
- state.lifecycle = "blocked";
525
- state.blockers = state.blockers.includes(OWNER_STARTUP_BLOCKER)
526
- ? state.blockers
527
- : [...state.blockers, OWNER_STARTUP_BLOCKER];
528
- state.updatedAt = nowIso();
529
- await writeSessionState(root, state);
530
- return state;
474
+ return reconcileOwnerLifecycle({
475
+ state,
476
+ observation,
477
+ completedTerminal,
478
+ startupBlocked,
479
+ persist: async nextState => writeSessionState(root, nextState),
480
+ nowIso,
481
+ });
531
482
  }
532
483
 
533
484
  function resolveRetryBudget(input: Record<string, unknown>): RetryBudget {
@@ -562,29 +513,6 @@ function isBoundedNoServerDiagnostic(stderr: Uint8Array): boolean {
562
513
  );
563
514
  }
564
515
 
565
- function sameServerIdentity(left: TmuxServerProof, right: TmuxServerProof): boolean {
566
- return (
567
- left.pid === right.pid &&
568
- left.startTime === right.startTime &&
569
- left.cgroup?.classification === right.cgroup?.classification &&
570
- left.cgroup?.scope === right.cgroup?.scope &&
571
- left.cgroup?.diagnostic === right.cgroup?.diagnostic
572
- );
573
- }
574
-
575
- function isSafeServerProof(proof: TmuxServerProof): boolean {
576
- return (
577
- proof.state === "safe" &&
578
- typeof proof.pid === "number" &&
579
- Number.isSafeInteger(proof.pid) &&
580
- proof.pid > 0 &&
581
- typeof proof.startTime === "string" &&
582
- proof.startTime.length > 0 &&
583
- (proof.cgroup?.classification === "safe" ||
584
- (process.platform !== "linux" && proof.cgroup?.classification === "not_applicable"))
585
- );
586
- }
587
-
588
516
  function exactNativeTmuxSessionId(stdout: Uint8Array): string | null {
589
517
  const value = new TextDecoder().decode(stdout);
590
518
  const line = value.endsWith("\n") ? value.slice(0, -1) : value;
@@ -985,7 +913,9 @@ export default class Harness extends Command {
985
913
  proof: TmuxServerProof | null,
986
914
  probeServer: OwnerIsolationProbe["probeServer"],
987
915
  ): Promise<void> {
988
- if (!nativeSessionId || !proof || !isSafeServerProof(proof)) throw new Error("tmux-owner-cleanup_uncertain");
916
+ if (!nativeSessionId || !proof || !isSafeServerProof(proof, ownerIsolationPlatform()))
917
+ throw new Error("tmux-owner-cleanup_uncertain");
918
+
989
919
  if (!(await this.#nativeSessionBoundToName(tmuxCommand, socketKey, nativeSessionId, sessionName)))
990
920
  throw new Error("tmux-owner-cleanup_uncertain");
991
921
  const current = await probeServer(socketKey, [tmuxCommand, "-L", socketKey]);
@@ -1141,7 +1071,7 @@ export default class Harness extends Command {
1141
1071
  const postSpawnServer = await probeServer(socketKey, [tmuxCommand, "-L", socketKey]);
1142
1072
  cleanupProof = postSpawnServer;
1143
1073
  if (postSpawnServer.state === "unsafe") return fail("tmux-owner-server_unsafe");
1144
- if (!isSafeServerProof(postSpawnServer)) return fail("tmux-owner-server_unverifiable");
1074
+ if (!isSafeServerProof(postSpawnServer, ownerIsolationPlatform())) return fail("tmux-owner-server_unverifiable");
1145
1075
  if (
1146
1076
  scopedReceipt &&
1147
1077
  (scopedReceipt.sessionName !== sessionName ||
@@ -1441,14 +1371,10 @@ export default class Harness extends Command {
1441
1371
  let state = await loadState(root, sessionId);
1442
1372
  const ownerLive = ownerLiveFor(state);
1443
1373
  const { observation, completedTerminalEvent } = await buildObservation(root, state, ownerLive);
1444
- state = await reconcileCompletedOwnerExited(root, state, observation, completedTerminalEvent);
1445
1374
  const vanishedOwnerBlock = needsVanishedOwnerBlock(state, observation, completedTerminalEvent);
1446
- state = await markVanishedOwnerBlocked(root, state, observation, completedTerminalEvent);
1447
- // Build owner-exit evidence whenever the owner is gone so a startup death (owner started,
1448
- // reported live, then died before the first prompt) is detectable, not just vanish/completion.
1449
1375
  const ownerExit = !ownerLive ? await buildOwnerExitEvidence(root, state) : null;
1450
1376
  const startupBlocked = ownerExit?.startupBlocker ?? false;
1451
- if (ownerExit && startupBlocked) state = await markStartupOwnerBlocked(root, state, ownerExit);
1377
+ state = await reconcileLifecycle(root, state, observation, completedTerminalEvent, startupBlocked);
1452
1378
  const includeOwnerExit = Boolean(ownerExit && (vanishedOwnerBlock || completedTerminalEvent || startupBlocked));
1453
1379
  writeJson(
1454
1380
  buildResponse(state, ownerLive, {
@@ -1482,12 +1408,7 @@ export default class Harness extends Command {
1482
1408
  if (!observation) {
1483
1409
  const built = await buildObservation(root, stateView, ownerLive);
1484
1410
  observation = built.observation;
1485
- stateView = await markVanishedOwnerBlocked(
1486
- root,
1487
- stateView,
1488
- built.observation,
1489
- built.completedTerminalEvent,
1490
- );
1411
+ stateView = await reconcileLifecycle(root, stateView, built.observation, built.completedTerminalEvent);
1491
1412
  }
1492
1413
  }
1493
1414
  if (!observation) throw new Error("classify_requires_observation_or_session");
@@ -1548,7 +1469,14 @@ export default class Harness extends Command {
1548
1469
  const ownerExit = await buildOwnerExitEvidence(root, state);
1549
1470
  // An owner that started, reported live, then died before accepting the first prompt is a
1550
1471
  // startup blocker, not a healthy `owner-not-live` gate — persist it and report it as such.
1551
- if (ownerExit.startupBlocker) state = await markStartupOwnerBlocked(root, state, ownerExit);
1472
+ const built = await buildObservation(root, state, false);
1473
+ state = await reconcileLifecycle(
1474
+ root,
1475
+ state,
1476
+ built.observation,
1477
+ built.completedTerminalEvent,
1478
+ ownerExit.startupBlocker,
1479
+ );
1552
1480
  const reason = ownerExit.startupBlocker ? ownerExit.reason : "owner-not-live";
1553
1481
  writeJson(
1554
1482
  buildResponse(
@@ -1614,7 +1542,7 @@ export default class Harness extends Command {
1614
1542
  let state = await loadState(root, sessionId);
1615
1543
  const beforeExit = await buildOwnerExitEvidence(root, state);
1616
1544
  const { observation, completedTerminalEvent } = await buildObservation(root, state, false);
1617
- state = await markVanishedOwnerBlocked(root, state, observation, completedTerminalEvent);
1545
+ state = await reconcileLifecycle(root, state, observation, completedTerminalEvent);
1618
1546
  const decision = classifyRecovery({
1619
1547
  observation: { ...observation, lifecycle: state.lifecycle },
1620
1548
  retryBudget: budget,
@@ -9,7 +9,7 @@ import { APP_NAME, setProjectDir } from "@gajae-code/utils";
9
9
  import { Args, Command, Flags } from "@gajae-code/utils/cli";
10
10
  import { parseArgs } from "../cli/args";
11
11
  import { launchDefaultTmuxIfNeeded } from "../gjc-runtime/launch-tmux";
12
- import { prepareLaunchWorktree } from "../gjc-runtime/launch-worktree";
12
+ import { type PreparedLaunchWorktree, prepareLaunchWorktree } from "../gjc-runtime/launch-worktree";
13
13
  import {
14
14
  GJC_COORDINATOR_SESSION_ID_ENV,
15
15
  GJC_COORDINATOR_SESSION_STATE_FILE_ENV,
@@ -128,7 +128,8 @@ export default class Index extends Command {
128
128
  description: "Resume a session (by ID prefix, path, or picker if omitted)",
129
129
  }),
130
130
  "session-dir": Flags.string({
131
- description: "Directory for session storage and lookup",
131
+ description:
132
+ "Explicit session storage directory and lookup override (default uses managed v2 workspace scope)",
132
133
  }),
133
134
  "no-session": Flags.boolean({
134
135
  description: "Don't save session (ephemeral)",
@@ -199,7 +200,8 @@ export default class Index extends Command {
199
200
  `# Pin a stored credential for this session\n ${APP_NAME} --credential email:me@example.com`,
200
201
  `# Activate a model profile for this session\n ${APP_NAME} --mpreset codex-medium`,
201
202
  `# Persist a model profile as the default\n ${APP_NAME} --mpreset opencodego --default`,
202
- `# Export a session file to HTML\n ${APP_NAME} --export ~/.gjc/agent/sessions/--path--/session.jsonl`,
203
+ `# Export a session file to HTML\n ${APP_NAME} --export ~/.gjc/agent/sessions/v2-<scope>/session.jsonl`,
204
+ `# Use an explicit session storage directory\n ${APP_NAME} --session-dir ./sessions`,
203
205
  ];
204
206
 
205
207
  static strict = false;
@@ -212,7 +214,7 @@ export default class Index extends Command {
212
214
  return;
213
215
  }
214
216
 
215
- let launch: ReturnType<typeof prepareLaunchWorktree>;
217
+ let launch: PreparedLaunchWorktree;
216
218
  try {
217
219
  launch = prepareLaunchWorktree(process.cwd(), args);
218
220
  } catch (error) {