@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,6 +1,6 @@
1
1
  import { dlopen, ptr } from "bun:ffi";
2
- import * as fs from "node:fs";
3
2
  import { Process } from "@gajae-code/natives";
3
+ import { readLinuxProcStartTimeSync } from "../../gjc-runtime/linux-proc";
4
4
 
5
5
  const DARWIN_PROC_PIDTBSDINFO = 3;
6
6
  const DARWIN_PROC_BSDINFO_SIZE = 136;
@@ -95,7 +95,8 @@ export function parseDarwinProcessIncarnation(info: Uint8Array): string | undefi
95
95
  }
96
96
  }
97
97
 
98
- function isProcessIncarnation(value: unknown): value is string {
98
+ /** Whether `value` is a canonical process-incarnation string (`linux:`/`darwin:`/`windows:`). */
99
+ export function isProcessIncarnation(value: unknown): value is string {
99
100
  return (
100
101
  typeof value === "string" &&
101
102
  (/^(?:linux:\d+|darwin:[1-9]\d*:\d+)$/.test(value) ||
@@ -116,17 +117,8 @@ export function processIncarnation(pid: number, options: ProcessIncarnationOptio
116
117
  }
117
118
  }
118
119
  if (platform === "linux") {
119
- try {
120
- const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
121
- const close = stat.lastIndexOf(")");
122
- const startTicks = stat
123
- .slice(close + 2)
124
- .trim()
125
- .split(/\s+/)[19];
126
- return startTicks ? `linux:${startTicks}` : undefined;
127
- } catch {
128
- return undefined;
129
- }
120
+ const startTicks = readLinuxProcStartTimeSync(pid);
121
+ return startTicks ? `linux:${startTicks}` : undefined;
130
122
  }
131
123
  if (platform === "darwin") {
132
124
  const info = new Uint8Array(DARWIN_PROC_BSDINFO_SIZE);
@@ -2,7 +2,13 @@ import { createHash } from "node:crypto";
2
2
  import * as fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { withFileLock } from "../../config/file-lock";
5
- import { assertSupportedStateVersion, SDK_STATE_VERSION, UnsupportedStateVersionError } from "./state-version";
5
+ import {
6
+ assertSupportedSnapshotVersion,
7
+ assertSupportedStateVersion,
8
+ SDK_STATE_VERSION,
9
+ SESSION_INDEX_SNAPSHOT_VERSION,
10
+ UnsupportedStateVersionError,
11
+ } from "./state-version";
6
12
 
7
13
  export type SessionIndexEventType =
8
14
  | "host_registered"
@@ -48,15 +54,94 @@ export const sessionIndexChecksum = (event: Omit<SessionIndexEvent, "checksum">)
48
54
  const dirFor = (agentDir: string) => path.join(agentDir, "sdk", "sessions");
49
55
  const logFor = (agentDir: string) => path.join(dirFor(agentDir), "index.jsonl");
50
56
  const snapshotFor = (agentDir: string) => path.join(dirFor(agentDir), "index.snapshot.json");
57
+ const ROTATE_BYTES = 4 * 1024 * 1024;
58
+ function isValidSnapshot(snapshot: unknown): snapshot is { indexSeq: number; events: SessionIndexEvent[] } {
59
+ if (!snapshot || typeof snapshot !== "object") return false;
60
+ const { indexSeq, events } = snapshot as { indexSeq?: unknown; events?: unknown };
61
+ if (typeof indexSeq !== "number" || !Number.isSafeInteger(indexSeq) || indexSeq < 0) return false;
62
+ if (!Array.isArray(events)) return false;
63
+ if (events.length === 0) return indexSeq === 0;
64
+ // Accept strictly-increasing indexSeq (gaps allowed after compaction), preserving
65
+ // each event's original checksum. The old contiguous 1..N format is a special case.
66
+ let previous = 0;
67
+ for (const event of events) {
68
+ if (!event || typeof event !== "object") return false;
69
+ const { checksum, ...unsigned } = event as SessionIndexEvent;
70
+ if (typeof event.indexSeq !== "number" || !Number.isSafeInteger(event.indexSeq)) return false;
71
+ if (event.indexSeq <= previous) return false;
72
+ if (checksum !== sessionIndexChecksum(unsigned)) return false;
73
+ previous = event.indexSeq;
74
+ }
75
+ return previous === indexSeq;
76
+ }
77
+
78
+ // Compact the event history for a snapshot without renumbering: clients hold indexSeq
79
+ // across calls, so retained events keep their original indexSeq and checksum. Drops
80
+ // terminal+dead sessions entirely, collapses superseded heartbeats to the latest per
81
+ // surviving session, and always retains the global-max indexSeq as the chain anchor.
82
+ function compactEvents(events: SessionIndexEvent[]): SessionIndexEvent[] {
83
+ if (events.length === 0) return events;
84
+ const maxIndexSeq = events[events.length - 1]!.indexSeq;
85
+ const latestBySession = new Map<string, SessionIndexEvent>();
86
+ for (const event of events) latestBySession.set(event.sessionId, event);
87
+ const deadTerminal = new Set<string>();
88
+ for (const [sessionId, latest] of latestBySession) {
89
+ const terminal = latest.type === "host_unregistered" || latest.type === "session_closed";
90
+ if (terminal && !alive(latest.pid)) deadTerminal.add(sessionId);
91
+ }
92
+ const latestHeartbeatSeq = new Map<string, number>();
93
+ for (const event of events) {
94
+ if (event.type === "host_heartbeat" && !deadTerminal.has(event.sessionId)) {
95
+ latestHeartbeatSeq.set(event.sessionId, event.indexSeq);
96
+ }
97
+ }
98
+ const kept: SessionIndexEvent[] = [];
99
+ for (const event of events) {
100
+ if (event.indexSeq === maxIndexSeq) {
101
+ kept.push(event);
102
+ continue;
103
+ }
104
+ if (deadTerminal.has(event.sessionId)) continue;
105
+ if (event.type === "host_heartbeat" && latestHeartbeatSeq.get(event.sessionId) !== event.indexSeq) continue;
106
+ kept.push(event);
107
+ }
108
+ return kept;
109
+ }
110
+
51
111
  async function appendSync(file: string, value: string): Promise<void> {
52
112
  const h = await fs.open(file, "a", 0o600);
53
113
  try {
54
- await h.write(`${value}\n`);
114
+ const data = Buffer.from(`${value}\n`);
115
+ for (let offset = 0; offset < data.length; ) {
116
+ const { bytesWritten } = await h.write(data, offset, data.length - offset);
117
+ if (bytesWritten <= 0) throw new Error("Unable to append session index entry");
118
+ offset += bytesWritten;
119
+ }
55
120
  await h.sync();
56
121
  } finally {
57
122
  await h.close();
58
123
  }
59
124
  }
125
+
126
+ async function syncDirectory(file: string): Promise<void> {
127
+ let handle: fs.FileHandle;
128
+ try {
129
+ handle = await fs.open(path.dirname(file), "r");
130
+ } catch (error) {
131
+ const code = (error as NodeJS.ErrnoException).code;
132
+ if (process.platform === "win32" && (code === "EPERM" || code === "EACCES")) return;
133
+ throw error;
134
+ }
135
+ try {
136
+ await handle.sync();
137
+ } catch (error) {
138
+ const code = (error as NodeJS.ErrnoException).code;
139
+ if (process.platform !== "win32" || (code !== "EPERM" && code !== "EACCES")) throw error;
140
+ } finally {
141
+ await handle.close();
142
+ }
143
+ }
144
+
60
145
  function alive(pid: number): boolean {
61
146
  try {
62
147
  process.kill(pid, 0);
@@ -70,20 +155,21 @@ export class SessionIndex {
70
155
  #events: SessionIndexEvent[] = [];
71
156
  #warnings: string[] = [];
72
157
  #logOffset = 0;
158
+ #corruptSuffix = false;
73
159
  constructor(agentDir: string) {
74
160
  this.#agentDir = agentDir;
75
161
  }
76
162
  async open(): Promise<this> {
77
- await this.assertSupportedStateVersions();
78
163
  await fs.mkdir(dirFor(this.#agentDir), { recursive: true, mode: 0o700 });
79
164
  await fs.chmod(dirFor(this.#agentDir), 0o700);
80
- await this.replay();
165
+ await withFileLock(logFor(this.#agentDir), () => this.replay());
81
166
  return this;
82
167
  }
83
168
  async replay(): Promise<void> {
84
169
  this.#events = [];
85
170
  this.#warnings = [];
86
171
  this.#logOffset = 0;
172
+ this.#corruptSuffix = false;
87
173
  let snapshotSeq = 0;
88
174
  try {
89
175
  const snapshot = JSON.parse(await fs.readFile(snapshotFor(this.#agentDir), "utf8")) as {
@@ -91,35 +177,25 @@ export class SessionIndex {
91
177
  events?: SessionIndexEvent[];
92
178
  indexSeq?: number;
93
179
  };
94
- assertSupportedStateVersion(snapshotFor(this.#agentDir), snapshot);
95
- const snapshotIndexSeq = snapshot.indexSeq;
96
- if (
97
- typeof snapshotIndexSeq !== "number" ||
98
- !Number.isSafeInteger(snapshotIndexSeq) ||
99
- snapshotIndexSeq < 0 ||
100
- !Array.isArray(snapshot.events) ||
101
- snapshot.events.some(event => {
102
- const { checksum, ...unsigned } = event;
103
- return event.indexSeq > snapshotIndexSeq || checksum !== sessionIndexChecksum(unsigned);
104
- })
105
- )
106
- throw new Error("invalid snapshot");
180
+ assertSupportedSnapshotVersion(snapshotFor(this.#agentDir), snapshot);
181
+ if (!isValidSnapshot(snapshot)) throw new Error("invalid snapshot");
107
182
  this.#events = snapshot.events;
108
- snapshotSeq = snapshotIndexSeq;
183
+ snapshotSeq = snapshot.indexSeq;
109
184
  } catch (e) {
110
185
  if (e instanceof UnsupportedStateVersionError) throw e;
111
186
  if ((e as NodeJS.ErrnoException).code !== "ENOENT") this.#warnings.push("Invalid session index snapshot");
112
187
  }
113
- await this.#tail(snapshotSeq);
188
+ await this.#tail(snapshotSeq, false);
114
189
  }
115
- async #tail(snapshotSeq = this.indexSeq): Promise<void> {
190
+ async #tail(snapshotSeq = this.indexSeq, allowResync = true): Promise<void> {
116
191
  let data: Buffer;
117
192
  try {
118
193
  const handle = await fs.open(logFor(this.#agentDir), "r");
119
194
  try {
120
195
  const stat = await handle.stat();
121
196
  if (stat.size < this.#logOffset) {
122
- this.#warnings.push("Session index log was truncated");
197
+ if (allowResync) await this.replay();
198
+ else this.#warn("Session index log was truncated");
123
199
  return;
124
200
  }
125
201
  data = Buffer.alloc(stat.size - this.#logOffset);
@@ -132,9 +208,10 @@ export class SessionIndex {
132
208
  throw e;
133
209
  }
134
210
  const lastNewline = data.lastIndexOf(0x0a);
135
- if (lastNewline < 0) return;
136
211
  const consumed = data.subarray(0, lastNewline + 1);
137
212
  this.#logOffset += consumed.length;
213
+ const hasUnterminatedSuffix = data.length > consumed.length;
214
+ let corrupt = false;
138
215
  for (const line of consumed.toString("utf8").split("\n")) {
139
216
  if (!line) continue;
140
217
  let event: SessionIndexEvent;
@@ -143,18 +220,25 @@ export class SessionIndex {
143
220
  assertSupportedStateVersion(logFor(this.#agentDir), event);
144
221
  } catch (error) {
145
222
  if (error instanceof UnsupportedStateVersionError) throw error;
146
- this.#warnings.push("Corrupt session index entry; replay truncated");
147
- return;
223
+ corrupt = true;
224
+ continue;
148
225
  }
149
- if (event.indexSeq <= snapshotSeq) continue;
226
+ if (corrupt || event.indexSeq <= snapshotSeq) continue;
150
227
  const { checksum, ...unsigned } = event;
151
- if (checksum !== sessionIndexChecksum(unsigned) || event.indexSeq !== this.indexSeq + 1) {
152
- this.#warnings.push("Corrupt session index entry; replay truncated");
153
- return;
154
- }
155
- this.#events.push(event);
228
+ if (checksum !== sessionIndexChecksum(unsigned) || event.indexSeq !== this.indexSeq + 1) corrupt = true;
229
+ else this.#events.push(event);
230
+ }
231
+ if (hasUnterminatedSuffix) corrupt = true;
232
+ if (corrupt) {
233
+ this.#corruptSuffix = true;
234
+ this.#warn("Corrupt session index entry; replay truncated");
235
+ if (allowResync) await this.replay();
156
236
  }
157
237
  }
238
+ #warn(message: string): void {
239
+ if (!this.#warnings.includes(message)) this.#warnings.push(message);
240
+ }
241
+
158
242
  async refresh(): Promise<void> {
159
243
  await this.#tail();
160
244
  }
@@ -167,7 +251,8 @@ export class SessionIndex {
167
251
  ): Promise<SessionIndexEvent> {
168
252
  await fs.mkdir(dirFor(this.#agentDir), { recursive: true, mode: 0o700 });
169
253
  return withFileLock(logFor(this.#agentDir), async () => {
170
- await this.refresh();
254
+ await this.replay();
255
+ if (this.#corruptSuffix) throw new Error("Cannot append to corrupt session index log");
171
256
  const unsigned: Omit<SessionIndexEvent, "checksum"> = {
172
257
  ...input,
173
258
  version: SDK_STATE_VERSION,
@@ -177,16 +262,34 @@ export class SessionIndex {
177
262
  const event: SessionIndexEvent = { ...unsigned, checksum: sessionIndexChecksum(unsigned) };
178
263
  await appendSync(logFor(this.#agentDir), JSON.stringify(event));
179
264
  await this.refresh();
265
+ if ((await fs.stat(logFor(this.#agentDir))).size >= ROTATE_BYTES) await this.#rotate();
180
266
  return event;
181
267
  });
182
268
  }
183
269
  async snapshot(): Promise<void> {
270
+ await withFileLock(logFor(this.#agentDir), () => this.#snapshotUnderLock());
271
+ }
272
+ async #snapshotUnderLock(): Promise<void> {
273
+ await this.replay();
184
274
  const file = snapshotFor(this.#agentDir);
275
+ let current: unknown;
276
+ try {
277
+ current = JSON.parse(await fs.readFile(file, "utf8"));
278
+ } catch (error) {
279
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT" && !(error instanceof SyntaxError)) throw error;
280
+ }
281
+ if (isValidSnapshot(current) && current.indexSeq > this.indexSeq) return;
185
282
  const tmp = `${file}.${process.pid}.tmp`;
186
283
  await fs.writeFile(
187
284
  tmp,
188
- JSON.stringify({ version: SDK_STATE_VERSION, indexSeq: this.indexSeq, events: this.#events }),
189
- { mode: 0o600 },
285
+ JSON.stringify({
286
+ version: SESSION_INDEX_SNAPSHOT_VERSION,
287
+ indexSeq: this.indexSeq,
288
+ events: compactEvents(this.#events),
289
+ }),
290
+ {
291
+ mode: 0o600,
292
+ },
190
293
  );
191
294
  const h = await fs.open(tmp, "r");
192
295
  try {
@@ -195,30 +298,16 @@ export class SessionIndex {
195
298
  await h.close();
196
299
  }
197
300
  await fs.rename(tmp, file);
301
+ await syncDirectory(file);
198
302
  }
199
- async assertSupportedStateVersions(): Promise<void> {
200
- const files = [snapshotFor(this.#agentDir), logFor(this.#agentDir)];
201
- for (const file of files) {
202
- let source: string;
203
- try {
204
- source = await fs.readFile(file, "utf8");
205
- } catch (error) {
206
- if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
207
- throw error;
208
- }
209
- if (file.endsWith(".json")) {
210
- assertSupportedStateVersion(file, JSON.parse(source));
211
- continue;
212
- }
213
- for (const line of source.split("\n")) {
214
- if (!line) continue;
215
- try {
216
- assertSupportedStateVersion(file, JSON.parse(line));
217
- } catch (error) {
218
- if (error instanceof UnsupportedStateVersionError) throw error;
219
- }
220
- }
221
- }
303
+ async #rotate(): Promise<void> {
304
+ await this.#snapshotUnderLock();
305
+ const file = logFor(this.#agentDir);
306
+ const temporary = `${file}.${process.pid}.tmp`;
307
+ await fs.writeFile(temporary, "", { mode: 0o600 });
308
+ await fs.rename(temporary, file);
309
+ await syncDirectory(file);
310
+ this.#logOffset = 0;
222
311
  }
223
312
 
224
313
  listSessions(): SessionList {
@@ -1,5 +1,12 @@
1
1
  export const SDK_STATE_VERSION = 1;
2
2
 
3
+ // The session-index snapshot carries its own format version, independent of the shared
4
+ // SDK_STATE_VERSION used by discovery, the lifecycle ledger, and per-event records. Version 2
5
+ // snapshots relax the on-disk schema to allow gaps in indexSeq (compaction drops terminal+dead
6
+ // sessions and superseded heartbeats), so an older broker must hard-fail rather than silently
7
+ // fall back to a truncated log. Bumping only this constant keeps the broker protocol untouched.
8
+ export const SESSION_INDEX_SNAPSHOT_VERSION = 2;
9
+
3
10
  export class UnsupportedStateVersionError extends Error {
4
11
  readonly code = "unsupported_state_version";
5
12
 
@@ -21,3 +28,17 @@ export function assertSupportedStateVersion(file: string, value: unknown): void
21
28
  }
22
29
  }
23
30
  }
31
+
32
+ // Fences the session-index snapshot format independently of SDK_STATE_VERSION. An older broker
33
+ // (whose SESSION_INDEX_SNAPSHOT_VERSION is 1) trips assertSupportedStateVersion on a version-2
34
+ // snapshot and refuses to start; a current broker accepts both the legacy contiguous format
35
+ // (version 1 or absent) and the gapped version-2 format.
36
+ export function assertSupportedSnapshotVersion(file: string, value: unknown): void {
37
+ if (!value || typeof value !== "object") return;
38
+ const record = value as { version?: unknown; stateVersion?: unknown };
39
+ for (const version of [record.version, record.stateVersion]) {
40
+ if (typeof version === "number" && Number.isFinite(version) && version > SESSION_INDEX_SNAPSHOT_VERSION) {
41
+ throw new UnsupportedStateVersionError(file, version);
42
+ }
43
+ }
44
+ }
@@ -43,7 +43,7 @@ export class BrokerTransport {
43
43
  readonly #broker: Broker;
44
44
  readonly #token: string;
45
45
  readonly #requestedPort: number;
46
- #server: ReturnType<typeof Bun.serve> | null = null;
46
+ #server: Bun.Server<undefined> | null = null;
47
47
  #port = 0;
48
48
  constructor(broker: Broker, token: string, port = 0) {
49
49
  this.#broker = broker;
@@ -66,6 +66,7 @@ async function loadConfig(agentDir: string, kind: ChatDaemonKind): Promise<ChatD
66
66
  rich: { enabled: true },
67
67
  richDraft: { enabled: false },
68
68
  topics: { nameTemplate: undefined },
69
+ btw: { enabled: true },
69
70
  })
70
71
  ) {
71
72
  throw new Error("Discord notifications are enabled but configuration is incomplete");
@@ -98,6 +99,7 @@ async function loadConfig(agentDir: string, kind: ChatDaemonKind): Promise<ChatD
98
99
  rich: { enabled: true },
99
100
  richDraft: { enabled: false },
100
101
  topics: { nameTemplate: undefined },
102
+ btw: { enabled: true },
101
103
  })
102
104
  ) {
103
105
  throw new Error("Slack notifications are enabled but configuration is incomplete");
@@ -1,4 +1,4 @@
1
- import { spawn as childProcessSpawn, spawnSync } from "node:child_process";
1
+ import { spawn as childProcessSpawn } from "node:child_process";
2
2
  import * as crypto from "node:crypto";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
@@ -12,6 +12,7 @@ import type {
12
12
  DaemonStatus,
13
13
  } from "../../daemon/control-types";
14
14
  import { resolveGjcRuntimeSpawnInfo } from "../../daemon/runtime";
15
+ import { isProcessIncarnation, processIncarnation } from "../broker/process-incarnation";
15
16
  import { getNotificationConfig, isDiscordConfigured, isSlackConfigured } from "./config";
16
17
 
17
18
  export type ChatDaemonKind = "discord" | "slack";
@@ -121,28 +122,7 @@ function defaultSignal(pid: number, signal: NodeJS.Signals): void {
121
122
  process.kill(pid, signal);
122
123
  } catch {}
123
124
  }
124
- function defaultPidIncarnation(pid: number): string | undefined {
125
- if (!Number.isSafeInteger(pid) || pid <= 0) return undefined;
126
- if (process.platform === "linux") {
127
- try {
128
- const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
129
- return `linux:${
130
- stat
131
- .slice(stat.lastIndexOf(")") + 2)
132
- .trim()
133
- .split(/\s+/)[19]
134
- }`;
135
- } catch {
136
- return undefined;
137
- }
138
- }
139
- if (process.platform === "darwin") {
140
- const result = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8" });
141
- const startedAt = result.status === 0 ? result.stdout.trim() : "";
142
- return startedAt ? `darwin:${startedAt}` : undefined;
143
- }
144
- return undefined;
145
- }
125
+
146
126
  function runtimeInfo(execPath?: string): DaemonRuntimeInfo {
147
127
  const rt = resolveGjcRuntimeSpawnInfo(execPath ?? process.execPath);
148
128
  return {
@@ -377,7 +357,7 @@ export class ChatDaemonController implements BuiltInDaemonController {
377
357
  );
378
358
  }
379
359
  private incarnation(pid: number): string | undefined {
380
- return (this.deps.pidIncarnation ?? defaultPidIncarnation)(pid);
360
+ return (this.deps.pidIncarnation ?? processIncarnation)(pid);
381
361
  }
382
362
  private isLiveState(state: ChatDaemonState): boolean {
383
363
  return (
@@ -541,7 +521,7 @@ export async function acquireChatDaemonOwnership(input: {
541
521
  const pid = input.pid ?? process.pid;
542
522
  const probe: ChatDaemonOwnershipProbe = {
543
523
  pidAlive: input.pidAlive ?? defaultPidAlive,
544
- pidIncarnation: input.pidIncarnation ?? defaultPidIncarnation,
524
+ pidIncarnation: input.pidIncarnation ?? processIncarnation,
545
525
  };
546
526
  const incarnation = input.incarnation ?? probe.pidIncarnation(pid) ?? "unavailable";
547
527
  await fs.promises.mkdir(paths.dir, { recursive: true, mode: 0o700 });
@@ -631,9 +611,9 @@ async function isStaleChatDaemonLock(lock: string, probe: ChatDaemonOwnershipPro
631
611
  const currentIncarnation = probe.pidIncarnation(owner.pid);
632
612
  return (
633
613
  !probe.pidAlive(owner.pid) ||
634
- (currentIncarnation !== undefined &&
635
- owner.incarnation !== "unavailable" &&
636
- currentIncarnation !== owner.incarnation)
614
+ (owner.incarnation !== "unavailable" &&
615
+ (!isProcessIncarnation(owner.incarnation) ||
616
+ (currentIncarnation !== undefined && currentIncarnation !== owner.incarnation)))
637
617
  );
638
618
  }
639
619
  const stat = await fs.promises.stat(lock).catch(() => undefined);
@@ -118,6 +118,7 @@ function canClaim(effect: ChatEffect, now: number): boolean {
118
118
  export class ChatEffectJournal {
119
119
  readonly #store: ConversationStore<ChatEffect>;
120
120
  readonly #mappings: ConversationStore<EffectReferenceMapping>;
121
+ readonly #maxTerminalEffects: number;
121
122
 
122
123
  readonly #now: () => number;
123
124
 
@@ -131,10 +132,17 @@ export class ChatEffectJournal {
131
132
  pidIncarnation?: (pid: number) => string | undefined;
132
133
  sleep?: (ms: number) => Promise<void>;
133
134
  lockTimeoutMs?: number;
135
+ maxTerminalEffects?: number;
134
136
  }) {
135
137
  this.#store = new ConversationStore<ChatEffect>({ ...input, kind: input.transport, fileName: "effects.json" });
136
138
  this.#mappings = new ConversationStore<EffectReferenceMapping>({ ...input, kind: input.transport });
137
139
 
140
+ if (
141
+ input.maxTerminalEffects !== undefined &&
142
+ (!Number.isSafeInteger(input.maxTerminalEffects) || input.maxTerminalEffects < 1)
143
+ )
144
+ throw new Error("Chat effect terminal retention must be a positive safe integer");
145
+ this.#maxTerminalEffects = input.maxTerminalEffects ?? MAX_TERMINAL_CHAT_EFFECTS;
138
146
  this.#now = input.now ?? Date.now;
139
147
  }
140
148
 
@@ -343,12 +351,12 @@ export class ChatEffectJournal {
343
351
  const terminal = (await this.list())
344
352
  .filter(effect => effect.state === "terminal")
345
353
  .sort((left, right) => left.updatedAt - right.updatedAt);
346
- if (terminal.length <= MAX_TERMINAL_CHAT_EFFECTS) return;
354
+ if (terminal.length <= this.#maxTerminalEffects) return;
347
355
 
348
356
  const referenced = new Set<string>();
349
357
  for (const mapping of Object.values((await this.#mappings.load()).conversations))
350
358
  collectEffectReferences(mapping, referenced);
351
- for (const effect of terminal.slice(0, terminal.length - MAX_TERMINAL_CHAT_EFFECTS)) {
359
+ for (const effect of terminal.slice(0, terminal.length - this.#maxTerminalEffects)) {
352
360
  if (!referenced.has(effect.id)) await this.#store.delete(effect.id, effect.generation);
353
361
  }
354
362
  }
@@ -5,7 +5,7 @@
5
5
  * removed), but the user can still adjust per-surface behaviour from inside a
6
6
  * session thread with small slash commands:
7
7
  *
8
- * - `/verbose` switch the mirror to verbose (full tool output + reasoning)
8
+ * - `/verbose` switch the mirror to bounded tool-owned summaries + provider-displayable reasoning summaries
9
9
  * - `/lean` switch back to lean (assistant text + tool names)
10
10
  * - `/verbosity lean|verbose`
11
11
  * - `/redact on|off` toggle redaction of streamed content
@@ -133,13 +133,14 @@ export function parseInThreadConfigCommand(text: string): ConfigCommandChange |
133
133
 
134
134
  switch (command) {
135
135
  case "verbose":
136
- return { verbosity: "verbose" };
136
+ return rest.length === 0 ? { verbosity: "verbose" } : undefined;
137
137
  case "lean":
138
- return { verbosity: "lean" };
138
+ return rest.length === 0 ? { verbosity: "lean" } : undefined;
139
139
  case "verbosity":
140
- if (arg === "lean" || arg === "verbose") return { verbosity: arg };
140
+ if (rest.length === 1 && (arg === "lean" || arg === "verbose")) return { verbosity: arg };
141
141
  return undefined;
142
142
  case "redact":
143
+ if (rest.length !== 1) return undefined;
143
144
  if (arg === "on" || arg === "true" || arg === "1") return { redact: true };
144
145
  if (arg === "off" || arg === "false" || arg === "0") return { redact: false };
145
146
  return undefined;
@@ -55,6 +55,9 @@ export interface NotificationSettingsSnapshot {
55
55
  botToken?: string;
56
56
  chatId?: string;
57
57
  activation?: Record<string, unknown>;
58
+ btw: {
59
+ enabled: boolean;
60
+ };
58
61
  rich: {
59
62
  enabled: boolean;
60
63
  };
@@ -93,6 +96,97 @@ export interface NotificationSettingsReader {
93
96
  getNotificationSettingsSnapshot(): NotificationSettingsSnapshot;
94
97
  getAgentDir(): string;
95
98
  }
99
+ function notificationConfigurationError(): Error {
100
+ return new Error("gjc_notify_daemon_invalid_configuration");
101
+ }
102
+
103
+ function notificationSettingsObject(value: unknown): Record<string, unknown> {
104
+ if (value === undefined) return {};
105
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw notificationConfigurationError();
106
+ return value as Record<string, unknown>;
107
+ }
108
+
109
+ function notificationSettingsString(value: unknown): string | undefined {
110
+ if (value === undefined) return undefined;
111
+ if (typeof value === "string") return value;
112
+ throw notificationConfigurationError();
113
+ }
114
+
115
+ function notificationSettingsBoolean(value: unknown, fallback: boolean): boolean {
116
+ if (value === undefined) return fallback;
117
+ if (typeof value === "boolean") return value;
118
+ throw notificationConfigurationError();
119
+ }
120
+
121
+ function notificationSettingsChoice<T extends string>(value: unknown, fallback: T, choices: readonly T[]): T {
122
+ if (value === undefined) return fallback;
123
+ if (typeof value === "string" && choices.includes(value as T)) return value as T;
124
+ throw notificationConfigurationError();
125
+ }
126
+
127
+ function notificationIdleTimeoutMs(value: unknown): number {
128
+ if (value === undefined) return 60_000;
129
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
130
+ throw notificationConfigurationError();
131
+ }
132
+
133
+ /**
134
+ * Validate and snapshot the raw global notification configuration used by both
135
+ * the interactive host and the lightweight daemon process.
136
+ */
137
+ export function parseNotificationSettingsSnapshot(rawConfig?: unknown): NotificationSettingsSnapshot {
138
+ const root = notificationSettingsObject(rawConfig);
139
+ const notifications = notificationSettingsObject(root.notifications);
140
+ const telegram = notificationSettingsObject(notifications.telegram);
141
+ const btw = notificationSettingsObject(telegram.btw);
142
+ const rich = notificationSettingsObject(telegram.rich);
143
+ const richDraft = notificationSettingsObject(telegram.richDraft);
144
+ const topics = notificationSettingsObject(telegram.topics);
145
+ const activation = readTelegramActivationMarkers(notificationSettingsObject(telegram.activation));
146
+ const discord = notificationSettingsObject(notifications.discord);
147
+ const slack = notificationSettingsObject(notifications.slack);
148
+ const daemon = notificationSettingsObject(notifications.daemon);
149
+ return {
150
+ enabled: notificationSettingsBoolean(notifications.enabled, false),
151
+ telegram: {
152
+ botToken: notificationSettingsString(telegram.botToken),
153
+ chatId: notificationSettingsString(telegram.chatId),
154
+ ...(Object.keys(activation).length === 0 ? {} : { activation }),
155
+ btw: {
156
+ enabled: notificationSettingsBoolean(btw.enabled, true),
157
+ },
158
+ rich: {
159
+ enabled: notificationSettingsBoolean(rich.enabled, true),
160
+ },
161
+ richDraft: {
162
+ enabled: notificationSettingsBoolean(richDraft.enabled, false),
163
+ },
164
+ topics: {
165
+ nameTemplate: notificationSettingsString(topics.nameTemplate),
166
+ },
167
+ },
168
+ discord: {
169
+ botToken: notificationSettingsString(discord.botToken),
170
+ applicationId: notificationSettingsString(discord.applicationId),
171
+ guildId: notificationSettingsString(discord.guildId),
172
+ parentChannelId: notificationSettingsString(discord.parentChannelId),
173
+ },
174
+ slack: {
175
+ botToken: notificationSettingsString(slack.botToken),
176
+ appToken: notificationSettingsString(slack.appToken),
177
+ workspaceId: notificationSettingsString(slack.workspaceId),
178
+ channelId: notificationSettingsString(slack.channelId),
179
+ authorizedUserId: notificationSettingsString(slack.authorizedUserId),
180
+ },
181
+ redact: notificationSettingsBoolean(notifications.redact, false),
182
+ verbosity: notificationSettingsChoice<"lean" | "verbose">(notifications.verbosity, "lean", ["lean", "verbose"]),
183
+ sessionScope: notificationSettingsChoice<"all" | "primary">(notifications.sessionScope, "all", [
184
+ "all",
185
+ "primary",
186
+ ]),
187
+ idleTimeoutMs: notificationIdleTimeoutMs(daemon.idleTimeoutMs),
188
+ };
189
+ }
96
190
 
97
191
  export interface NotificationConfig {
98
192
  enabled: boolean;
@@ -121,6 +215,9 @@ export interface NotificationConfig {
121
215
  */
122
216
  sessionScope: "all" | "primary";
123
217
  idleTimeoutMs: number;
218
+ btw: {
219
+ enabled: boolean;
220
+ };
124
221
  rich: {
125
222
  enabled: boolean;
126
223
  };
@@ -154,6 +251,7 @@ export function getNotificationConfig(settings: NotificationSettingsReader): Not
154
251
  sessionScope: snapshot.sessionScope,
155
252
  idleTimeoutMs: snapshot.idleTimeoutMs,
156
253
  rich: snapshot.telegram.rich,
254
+ btw: snapshot.telegram.btw,
157
255
  richDraft: snapshot.telegram.richDraft,
158
256
  topics: snapshot.telegram.topics,
159
257
  };