@gajae-code/coding-agent 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/dist/types/async/job-manager.d.ts +46 -7
  3. package/dist/types/cli/config-cli.d.ts +11 -1
  4. package/dist/types/cli/notify-cli.d.ts +1 -0
  5. package/dist/types/cli/session-picker.d.ts +2 -1
  6. package/dist/types/cli/setup-cli.d.ts +1 -1
  7. package/dist/types/commands/mcp-serve.d.ts +33 -0
  8. package/dist/types/commands/mcp.d.ts +1 -1
  9. package/dist/types/commands/notify.d.ts +0 -2
  10. package/dist/types/commands/sdk.d.ts +1 -1
  11. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  12. package/dist/types/config/file-lock.d.ts +14 -4
  13. package/dist/types/config/keybindings.d.ts +42 -1
  14. package/dist/types/config/model-auth.d.ts +3 -0
  15. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  16. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  17. package/dist/types/config/model-equivalence.d.ts +20 -0
  18. package/dist/types/config/model-registry.d.ts +16 -15
  19. package/dist/types/config/model-resolver.d.ts +28 -18
  20. package/dist/types/config/models-config-schema.d.ts +11 -0
  21. package/dist/types/config/settings-schema.d.ts +56 -1
  22. package/dist/types/config/settings.d.ts +14 -11
  23. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  24. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  26. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  27. package/dist/types/discovery/helpers.d.ts +0 -6
  28. package/dist/types/edit/index.d.ts +3 -0
  29. package/dist/types/eval/py/env.d.ts +9 -0
  30. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  31. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  32. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  33. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  34. package/dist/types/extensibility/shared-events.d.ts +2 -0
  35. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  36. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  37. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  38. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  39. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  40. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  41. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  42. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  43. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  44. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  45. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  46. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  47. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  48. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  49. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  50. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  51. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  52. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  53. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +11 -0
  54. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  55. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  56. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  57. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  58. package/dist/types/goals/runtime.d.ts +1 -0
  59. package/dist/types/goals/state.d.ts +8 -0
  60. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  61. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  62. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  63. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  64. package/dist/types/hindsight/state.d.ts +11 -4
  65. package/dist/types/lsp/lspmux.d.ts +5 -3
  66. package/dist/types/lsp/path-trust.d.ts +1 -0
  67. package/dist/types/main.d.ts +3 -2
  68. package/dist/types/memories/storage.d.ts +1 -0
  69. package/dist/types/modes/action-registry.d.ts +29 -0
  70. package/dist/types/modes/components/chrome.d.ts +14 -0
  71. package/dist/types/modes/components/command-palette.d.ts +27 -0
  72. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  73. package/dist/types/modes/components/index.d.ts +2 -1
  74. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  75. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  76. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  77. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  78. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  79. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  80. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  81. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  82. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  83. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  84. package/dist/types/modes/components/status-line.d.ts +1 -94
  85. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  86. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  87. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  88. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  89. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  90. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  91. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -0
  92. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  93. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  94. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  95. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  96. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  97. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  98. package/dist/types/modes/interactive-mode.d.ts +21 -19
  99. package/dist/types/modes/jobs-observer.d.ts +2 -0
  100. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  101. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +21 -2
  102. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  103. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  104. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  105. package/dist/types/modes/types.d.ts +22 -15
  106. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  107. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  108. package/dist/types/sdk/broker/broker.d.ts +78 -12
  109. package/dist/types/sdk/broker/ensure.d.ts +9 -0
  110. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  111. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  112. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  113. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  114. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  115. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  116. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  117. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  118. package/dist/types/sdk/bus/config.d.ts +11 -0
  119. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  120. package/dist/types/sdk/bus/discord-daemon.d.ts +5 -0
  121. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  122. package/dist/types/sdk/bus/index.d.ts +27 -0
  123. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  124. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  125. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  126. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  127. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  128. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  129. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  130. package/dist/types/sdk/bus/telegram-daemon.d.ts +69 -3
  131. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  132. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  133. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  134. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  135. package/dist/types/sdk/host/host.d.ts +2 -0
  136. package/dist/types/sdk/index.d.ts +1 -0
  137. package/dist/types/sdk/session-directory.d.ts +56 -0
  138. package/dist/types/session/agent-session.d.ts +82 -47
  139. package/dist/types/session/agent-storage.d.ts +7 -0
  140. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  141. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  142. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  143. package/dist/types/session/messages.d.ts +16 -0
  144. package/dist/types/session/session-manager-internal.d.ts +14 -0
  145. package/dist/types/session/session-manager.d.ts +82 -12
  146. package/dist/types/session/session-storage.d.ts +43 -3
  147. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  148. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  149. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  150. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  151. package/dist/types/system-prompt.d.ts +2 -11
  152. package/dist/types/task/executor.d.ts +3 -1
  153. package/dist/types/thinking.d.ts +7 -0
  154. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  155. package/dist/types/tools/ask.d.ts +64 -2
  156. package/dist/types/tools/bash.d.ts +3 -0
  157. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  158. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  159. package/dist/types/tools/cron.d.ts +2 -0
  160. package/dist/types/tools/fetch.d.ts +1 -0
  161. package/dist/types/tools/index.d.ts +30 -15
  162. package/dist/types/tools/output-meta.d.ts +1 -0
  163. package/dist/types/tools/read.d.ts +3 -0
  164. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  165. package/dist/types/tools/subagent-render.d.ts +1 -0
  166. package/dist/types/tools/subagent.d.ts +6 -1
  167. package/dist/types/utils/event-bus.d.ts +1 -1
  168. package/dist/types/web/search/index.d.ts +3 -1
  169. package/package.json +21 -9
  170. package/scripts/build-sdk-package-smoke.ts +39 -1
  171. package/scripts/generate-hotkeys-docs.ts +74 -0
  172. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  173. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  174. package/src/async/job-manager.ts +455 -69
  175. package/src/cli/config-cli.ts +75 -2
  176. package/src/cli/notify-cli.ts +92 -26
  177. package/src/cli/session-picker.ts +20 -3
  178. package/src/cli/setup-cli.ts +1 -1
  179. package/src/cli.ts +1 -1
  180. package/src/commands/config.ts +1 -1
  181. package/src/commands/harness.ts +34 -106
  182. package/src/commands/launch.ts +6 -4
  183. package/src/commands/mcp-serve.ts +85 -8
  184. package/src/commands/mcp.ts +2 -2
  185. package/src/commands/notify.ts +8 -4
  186. package/src/commands/sdk.ts +61 -40
  187. package/src/commands/ultragoal.ts +4 -1
  188. package/src/config/atomic-yaml-patch.ts +80 -13
  189. package/src/config/file-lock.ts +105 -43
  190. package/src/config/keybindings.ts +64 -20
  191. package/src/config/model-auth.ts +9 -0
  192. package/src/config/model-bindings-applier.ts +139 -0
  193. package/src/config/model-discovery-manager.ts +224 -0
  194. package/src/config/model-equivalence.ts +66 -0
  195. package/src/config/model-registry.ts +169 -302
  196. package/src/config/model-resolver.ts +361 -369
  197. package/src/config/models-config-schema.ts +5 -4
  198. package/src/config/settings-schema.ts +159 -2
  199. package/src/config/settings.ts +458 -482
  200. package/src/coordinator-mcp/policy.ts +76 -11
  201. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  202. package/src/coordinator-mcp/question-state.ts +778 -0
  203. package/src/coordinator-mcp/server.ts +1702 -238
  204. package/src/debug/index.ts +1 -1
  205. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  206. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  207. package/src/discovery/helpers.ts +1 -50
  208. package/src/edit/index.ts +64 -0
  209. package/src/eval/py/env.ts +26 -0
  210. package/src/eval/py/kernel.ts +5 -3
  211. package/src/extensibility/extensions/loader.ts +9 -1
  212. package/src/extensibility/extensions/runner.ts +12 -3
  213. package/src/extensibility/extensions/types.ts +39 -1
  214. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  215. package/src/extensibility/hooks/runner.ts +2 -2
  216. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  217. package/src/extensibility/shared-events.ts +2 -0
  218. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  219. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  220. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  221. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  222. package/src/gjc-runtime/launch-tmux.ts +2 -5
  223. package/src/gjc-runtime/launch-worktree.ts +5 -5
  224. package/src/gjc-runtime/linux-proc.ts +71 -0
  225. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  226. package/src/gjc-runtime/session-resolution.ts +18 -3
  227. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  228. package/src/gjc-runtime/state-migrations.ts +1 -1
  229. package/src/gjc-runtime/state-runtime.ts +320 -249
  230. package/src/gjc-runtime/state-writer.ts +36 -40
  231. package/src/gjc-runtime/team-launch.ts +319 -0
  232. package/src/gjc-runtime/team-notify.ts +326 -0
  233. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  234. package/src/gjc-runtime/team-store.ts +828 -0
  235. package/src/gjc-runtime/team-workers.ts +962 -0
  236. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  237. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  238. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  239. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  240. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  241. package/src/gjc-runtime/ultragoal-guard.ts +83 -12
  242. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  243. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +102 -1
  244. package/src/gjc-runtime/ultragoal-runtime.ts +156 -1319
  245. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  246. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  247. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  248. package/src/goals/continuation-timeout-guard.ts +79 -0
  249. package/src/goals/runtime.ts +7 -5
  250. package/src/goals/state.ts +18 -0
  251. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  252. package/src/harness-control-plane/phase-rollup.ts +4 -7
  253. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  254. package/src/harness-control-plane/receipt-spool.ts +51 -7
  255. package/src/harness-control-plane/session-lease.ts +12 -130
  256. package/src/hindsight/backend.ts +26 -27
  257. package/src/hindsight/state.ts +101 -32
  258. package/src/hooks/skill-state.ts +17 -11
  259. package/src/internal-urls/docs-index.generated.ts +21 -18
  260. package/src/lsp/client.ts +1 -1
  261. package/src/lsp/config.ts +145 -26
  262. package/src/lsp/index.ts +1 -1
  263. package/src/lsp/lspmux.ts +39 -10
  264. package/src/lsp/path-trust.ts +81 -0
  265. package/src/main.ts +28 -8
  266. package/src/memories/index.ts +1 -0
  267. package/src/memories/storage.ts +6 -2
  268. package/src/modes/action-registry.ts +165 -0
  269. package/src/modes/components/chrome.ts +40 -0
  270. package/src/modes/components/command-palette.ts +168 -0
  271. package/src/modes/components/custom-editor.ts +1 -1
  272. package/src/modes/components/custom-provider-wizard.ts +51 -14
  273. package/src/modes/components/index.ts +2 -1
  274. package/src/modes/components/pet-selector.ts +11 -17
  275. package/src/modes/components/plan-preview-overlay.ts +295 -0
  276. package/src/modes/components/queue-mode-selector.ts +10 -27
  277. package/src/modes/components/queue-pane.ts +79 -0
  278. package/src/modes/components/read-tool-group.ts +13 -0
  279. package/src/modes/components/session-observer-overlay.ts +207 -772
  280. package/src/modes/components/sessions-dashboard.ts +181 -0
  281. package/src/modes/components/settings-selector.ts +1 -1
  282. package/src/modes/components/show-images-selector.ts +10 -24
  283. package/src/modes/components/status-line/types.ts +1 -1
  284. package/src/modes/components/status-line.ts +1 -969
  285. package/src/modes/components/tasks-pane.ts +73 -0
  286. package/src/modes/components/theme-selector.ts +11 -31
  287. package/src/modes/components/tool-execution.ts +22 -0
  288. package/src/modes/components/tool-status-header.ts +1056 -0
  289. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  290. package/src/modes/controllers/btw-controller.ts +1 -0
  291. package/src/modes/controllers/command-controller.ts +9 -15
  292. package/src/modes/controllers/event-controller.ts +3 -2
  293. package/src/modes/controllers/extension-ui-controller.ts +72 -25
  294. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  295. package/src/modes/controllers/input-controller.ts +764 -229
  296. package/src/modes/controllers/mode-gate.ts +24 -0
  297. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  298. package/src/modes/controllers/selector-controller.ts +225 -10
  299. package/src/modes/controllers/stt-controller.ts +125 -0
  300. package/src/modes/interactive-mode.ts +302 -1151
  301. package/src/modes/jobs-observer.ts +3 -0
  302. package/src/modes/runtime-init.ts +8 -0
  303. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  304. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +78 -19
  305. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  306. package/src/modes/tasks-aggregator.ts +199 -0
  307. package/src/modes/transcript-item-registry.ts +229 -0
  308. package/src/modes/types.ts +47 -20
  309. package/src/modes/utils/ui-helpers.ts +17 -9
  310. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  311. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  312. package/src/prompts/agents/architect.md +2 -8
  313. package/src/prompts/agents/critic.md +2 -8
  314. package/src/prompts/agents/executor.md +3 -0
  315. package/src/prompts/agents/planner.md +4 -12
  316. package/src/prompts/system/custom-system-prompt.md +1 -23
  317. package/src/prompts/system/plan-mode-approved.md +8 -0
  318. package/src/prompts/system/system-prompt.md +17 -96
  319. package/src/prompts/tools/bash.md +4 -4
  320. package/src/prompts/tools/browser.md +15 -52
  321. package/src/prompts/tools/find.md +2 -2
  322. package/src/prompts/tools/hashline.md +13 -55
  323. package/src/prompts/tools/read.md +3 -6
  324. package/src/prompts/tools/search-tool-bm25.md +0 -2
  325. package/src/prompts/tools/search.md +2 -4
  326. package/src/prompts/tools/skill-discovery.md +1 -2
  327. package/src/prompts/tools/task.md +1 -1
  328. package/src/runtime-mcp/manager.ts +1 -1
  329. package/src/sdk/broker/broker.ts +206 -78
  330. package/src/sdk/broker/discovery.ts +21 -2
  331. package/src/sdk/broker/ensure.ts +85 -25
  332. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  333. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  334. package/src/sdk/broker/lifecycle.ts +1399 -142
  335. package/src/sdk/broker/process-incarnation.ts +5 -13
  336. package/src/sdk/broker/session-index.ts +145 -56
  337. package/src/sdk/broker/state-version.ts +21 -0
  338. package/src/sdk/broker/transport.ts +1 -1
  339. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  340. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  341. package/src/sdk/bus/chat-effect-journal.ts +14 -4
  342. package/src/sdk/bus/config-commands.ts +5 -4
  343. package/src/sdk/bus/config.ts +98 -0
  344. package/src/sdk/bus/conversation-store.ts +5 -29
  345. package/src/sdk/bus/daemon-paths.ts +2 -0
  346. package/src/sdk/bus/discord-daemon.ts +25 -8
  347. package/src/sdk/bus/html-format.ts +20 -3
  348. package/src/sdk/bus/index.ts +834 -113
  349. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  350. package/src/sdk/bus/native-runtime-compatibility.ts +1 -1
  351. package/src/sdk/bus/natives-augment.d.ts +26 -1
  352. package/src/sdk/bus/notification-service.ts +168 -20
  353. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  354. package/src/sdk/bus/recent-activity.ts +188 -74
  355. package/src/sdk/bus/rich-document.ts +258 -0
  356. package/src/sdk/bus/rich-render.ts +62 -70
  357. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  358. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  359. package/src/sdk/bus/telegram-daemon.ts +1491 -247
  360. package/src/sdk/bus/threaded-inbound.ts +8 -3
  361. package/src/sdk/bus/threaded-render.ts +57 -1
  362. package/src/sdk/bus/topic-registry.ts +120 -13
  363. package/src/sdk/host/control/dispatch.ts +14 -2
  364. package/src/sdk/host/control/operations.ts +6 -1
  365. package/src/sdk/host/host.ts +31 -1
  366. package/src/sdk/host/query/handlers.ts +20 -22
  367. package/src/sdk/host/query/revision-store.ts +80 -57
  368. package/src/sdk/index.ts +1 -0
  369. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  370. package/src/sdk/session-directory.ts +141 -0
  371. package/src/sdk/session.ts +458 -479
  372. package/src/session/agent-session.ts +1901 -637
  373. package/src/session/agent-storage.ts +18 -0
  374. package/src/session/cache-economics.ts +25 -8
  375. package/src/session/fallback-chain-controller.ts +53 -3
  376. package/src/session/internal/managed-session-scope.ts +2226 -0
  377. package/src/session/internal/managed-session-storage.ts +453 -0
  378. package/src/session/messages.ts +91 -2
  379. package/src/session/session-manager-internal.ts +33 -0
  380. package/src/session/session-manager.ts +1112 -333
  381. package/src/session/session-storage.ts +444 -39
  382. package/src/session/volatile-context-pruning.ts +78 -0
  383. package/src/setup/credential-auto-import.ts +2 -1
  384. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  385. package/src/setup/provider-onboarding.ts +36 -61
  386. package/src/setup/provider-presets.json +5 -0
  387. package/src/skill-state/active-state.ts +3 -13
  388. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  389. package/src/skill-state/workflow-state-contract.ts +6 -41
  390. package/src/slash-commands/builtin-registry.ts +46 -11
  391. package/src/system-prompt.ts +37 -44
  392. package/src/task/agents.ts +19 -1
  393. package/src/task/executor.ts +90 -6
  394. package/src/task/index.ts +62 -37
  395. package/src/thinking.ts +21 -6
  396. package/src/tool-discovery/tool-index.ts +2 -134
  397. package/src/tools/ask.ts +157 -21
  398. package/src/tools/ast-edit.ts +1 -1
  399. package/src/tools/bash.ts +38 -0
  400. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  401. package/src/tools/browser/tab-supervisor.ts +282 -36
  402. package/src/tools/computer.ts +10 -3
  403. package/src/tools/cron.ts +30 -16
  404. package/src/tools/eval.ts +1 -2
  405. package/src/tools/fetch.ts +11 -4
  406. package/src/tools/index.ts +76 -24
  407. package/src/tools/output-meta.ts +13 -5
  408. package/src/tools/read.ts +37 -1
  409. package/src/tools/resource-gc.ts +2 -2
  410. package/src/tools/search-tool-bm25.ts +11 -74
  411. package/src/tools/subagent-render.ts +19 -8
  412. package/src/tools/subagent.ts +84 -32
  413. package/src/utils/event-bus.ts +9 -4
  414. package/src/web/search/index.ts +3 -3
  415. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  416. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  417. package/src/discovery/plugin-dir-roots.ts +0 -28
  418. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -63,7 +63,7 @@ export type ThreadedInboundDecision =
63
63
  text: string;
64
64
  updateId: number;
65
65
  threadId: string;
66
- messageId?: number;
66
+ messageId: number;
67
67
  attachment?: InboundAttachment;
68
68
  }
69
69
  | { kind: "duplicate"; updateId: number }
@@ -118,7 +118,9 @@ export function decideThreadedInbound(update: InboundUpdate, ctx: ThreadedInboun
118
118
  const sessionId = ctx.topicToSession(threadId);
119
119
  if (sessionId === undefined) return { kind: "ignore", reason: "unknown_topic" };
120
120
 
121
- if (typeof update.update_id !== "number") return { kind: "ignore", reason: "missing_update_id" };
121
+ if (typeof update.update_id !== "number" || !Number.isSafeInteger(update.update_id) || update.update_id < 0) {
122
+ return { kind: "ignore", reason: "missing_update_id" };
123
+ }
122
124
  const updateId = update.update_id;
123
125
  if (ctx.isDuplicate(updateId)) return { kind: "duplicate", updateId };
124
126
 
@@ -131,6 +133,9 @@ export function decideThreadedInbound(update: InboundUpdate, ctx: ThreadedInboun
131
133
  const attachment = extractAttachment(message);
132
134
  if (!text && attachment === undefined) return { kind: "ignore", reason: "empty_text" };
133
135
 
134
- const messageId = typeof message.message_id === "number" ? message.message_id : undefined;
136
+ if (typeof message.message_id !== "number" || !Number.isSafeInteger(message.message_id) || message.message_id <= 0) {
137
+ return { kind: "ignore", reason: "missing_message_id" };
138
+ }
139
+ const messageId = message.message_id;
135
140
  return { kind: "inject", sessionId, text, updateId, threadId, messageId, attachment };
136
141
  }
@@ -45,6 +45,13 @@ export interface ThreadedSend {
45
45
  richMarkdown?: string;
46
46
  /** Live-turn raw markdown for opt-in draft streaming (set ONLY on non-finalized turn frames; never triggers rich-final promotion, which requires `lane === "finalized"`). */
47
47
  richDraftMarkdown?: string;
48
+ /**
49
+ * True for a terminal tool_activity frame (phase completed/failed/cancelled/
50
+ * unknown). The daemon evicts the tool's `tool:<toolCallId>` live-message entry
51
+ * after delivering it, so completed tools do not accumulate unbounded across a
52
+ * long session (concurrent in-flight `started` entries are retained).
53
+ */
54
+ terminal?: boolean;
48
55
  }
49
56
 
50
57
  interface ThreadedFrame {
@@ -63,11 +70,18 @@ interface ThreadedFrame {
63
70
  model?: unknown;
64
71
  diff?: unknown;
65
72
  cwd?: unknown;
66
- // turn_stream
73
+ // turn_stream / reasoning_summary
67
74
  phase?: unknown;
68
75
  finalAnswer?: boolean;
69
76
  text?: unknown;
70
77
  messageRef?: unknown;
78
+ turnRef?: unknown;
79
+ // tool_activity
80
+ toolCallId?: unknown;
81
+ toolName?: unknown;
82
+ argsSummary?: unknown;
83
+ resultSummary?: unknown;
84
+ isError?: unknown;
71
85
  // image_attachment / file_attachment
72
86
  source?: unknown;
73
87
  data?: unknown;
@@ -184,6 +198,48 @@ export function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefi
184
198
  richDraftMarkdown: finalized ? undefined : raw,
185
199
  };
186
200
  }
201
+ case "tool_activity": {
202
+ const toolCallId = str(frame.toolCallId);
203
+ if (!toolCallId) return undefined;
204
+ const phase = str(frame.phase);
205
+ const status =
206
+ phase === "started"
207
+ ? "started"
208
+ : phase === "completed" && frame.isError !== true
209
+ ? "ok"
210
+ : phase === "completed" || phase === "failed"
211
+ ? "error"
212
+ : phase === "cancelled"
213
+ ? "cancelled"
214
+ : "unknown";
215
+ const argsSummary = str(frame.argsSummary);
216
+ const resultSummary = str(frame.resultSummary);
217
+ const lines = [`⚙ ${escapeHtml(truncate(str(frame.toolName) ?? "tool", 300))} — ${status}`];
218
+ if (argsSummary) lines.push(`args:\n${pre(truncate(argsSummary, 1200))}`);
219
+ if (resultSummary) lines.push(`result:\n${pre(truncate(resultSummary, 1200))}`);
220
+ return {
221
+ method: "sendMessage",
222
+ lane: phase === "started" ? "live" : "finalized",
223
+ text: finalizeTelegramHtml(lines.join("\n")),
224
+ coalesceKey: `tool:${toolCallId}`,
225
+ editable: true,
226
+ terminal: phase !== "started",
227
+ };
228
+ }
229
+ case "reasoning_summary": {
230
+ const text = str(frame.text);
231
+ if (!text) return undefined;
232
+ const turnRef = str(frame.turnRef);
233
+ return {
234
+ method: "sendMessage",
235
+ lane: "finalized",
236
+ // markdownToTelegramHtml escapes raw markdown itself; pre-escaping here
237
+ // would turn entities such as < into visible &amp;lt; text.
238
+ text: finalizeTelegramHtml(markdownToTelegramHtml(text)),
239
+ coalesceKey: turnRef ? `reason:${turnRef}` : undefined,
240
+ editable: turnRef !== undefined,
241
+ };
242
+ }
187
243
  case "image_attachment": {
188
244
  const data = str(frame.data);
189
245
  if (!data) return undefined;
@@ -21,6 +21,8 @@ export interface TopicRecord {
21
21
  identitySent: boolean;
22
22
  /** Creation timestamp (ms epoch). */
23
23
  createdAt: number;
24
+ /** First positive observation that the owning endpoint is stale, dead, or missing. */
25
+ orphanedAt?: number;
24
26
  /** Last applied or observed Telegram topic title. */
25
27
  name?: string;
26
28
  /** Naming authority. Missing values are legacy daemon-owned records. */
@@ -35,12 +37,24 @@ export interface TopicRecord {
35
37
  replayGeneration?: number;
36
38
  /** Last SDK event sequence durably consumed within replayGeneration. */
37
39
  replaySeq?: number;
40
+ /** Serialized authority epoch; a late create may commit only in its starting epoch. */
41
+ authorityEpoch?: number;
42
+ /** An uncertain delete fences future creation and inbound routing. */
43
+ authorityState?: "active" | "delete_pending";
38
44
  }
39
45
 
40
46
  /** Serialisable shape persisted to disk. */
41
47
  export interface TopicRegistryState {
42
48
  /** sessionId -> record. */
43
49
  topics: Record<string, TopicRecord>;
50
+ /** Durable deletion epochs retained after a definite delete. */
51
+ fences?: Record<string, number>;
52
+ }
53
+
54
+ function isValidTopicId(value: unknown): value is string {
55
+ return (
56
+ typeof value === "string" && /^[1-9]\d*$/.test(value) && Number.isSafeInteger(Number(value)) && Number(value) > 0
57
+ );
44
58
  }
45
59
 
46
60
  export function emptyTopicRegistryState(): TopicRegistryState {
@@ -56,8 +70,12 @@ export class TopicRegistry {
56
70
  private readonly topics: Map<string, TopicRecord>;
57
71
  /** Maps topicId -> sessionId for fast inbound routing. */
58
72
  private readonly byTopic = new Map<string, string>();
73
+ /** Persisted collisions are ambiguous and must never authorize inbound routing. */
74
+ readonly #ambiguousTopicIds = new Set<string>();
59
75
  /** In-flight create promises, keyed by session, to dedupe concurrent creates. */
60
76
  private readonly inflight = new Map<string, Promise<TopicRecord>>();
77
+ /** Monotonic authority epochs, including deletion fences for absent records. */
78
+ private readonly epochs = new Map<string, number>();
61
79
 
62
80
  constructor(state: TopicRegistryState = emptyTopicRegistryState()) {
63
81
  this.topics = new Map();
@@ -66,8 +84,12 @@ export class TopicRegistry {
66
84
 
67
85
  /** Merge serialized state and normalize authority fields from older releases. */
68
86
  load(state: TopicRegistryState): void {
87
+ for (const [sessionId, epoch] of Object.entries(state.fences ?? {})) {
88
+ if (Number.isSafeInteger(epoch) && epoch >= 0) this.epochs.set(sessionId, epoch);
89
+ }
90
+
69
91
  for (const [sessionId, raw] of Object.entries(state.topics ?? {})) {
70
- if (!raw || typeof raw.topicId !== "string") continue;
92
+ if (!raw || !isValidTopicId(raw.topicId)) continue;
71
93
  const hasValidUserAuthority =
72
94
  raw.nameOwner === "user" &&
73
95
  typeof raw.name === "string" &&
@@ -87,14 +109,45 @@ export class TopicRegistry {
87
109
  identitySent: raw.identitySent === true,
88
110
  createdAt: typeof raw.createdAt === "number" ? raw.createdAt : 0,
89
111
  ...(typeof raw.name === "string" ? { name: raw.name } : {}),
112
+ ...(typeof raw.orphanedAt === "number" && Number.isFinite(raw.orphanedAt) && raw.orphanedAt >= 0
113
+ ? { orphanedAt: raw.orphanedAt }
114
+ : {}),
90
115
  ...(hasValidUserAuthority ? { nameOwner: "user" as const } : {}),
91
116
  ...(hasValidUserAuthority && raw.nameReconcilePending === true ? { nameReconcilePending: true } : {}),
92
117
  ...(hasValidUserAuthority ? { userNameUpdateId: raw.userNameUpdateId } : {}),
93
118
  ...(typeof raw.identityKey === "string" ? { identityKey: raw.identityKey } : {}),
94
119
  ...(hasValidReplayCursor ? { replayGeneration: raw.replayGeneration, replaySeq: raw.replaySeq } : {}),
120
+ ...(Number.isSafeInteger(raw.authorityEpoch) && raw.authorityEpoch! >= 0
121
+ ? { authorityEpoch: raw.authorityEpoch }
122
+ : {}),
123
+ ...(raw.authorityState === "delete_pending" ? { authorityState: "delete_pending" as const } : {}),
95
124
  };
125
+ this.epochs.set(sessionId, Math.max(this.epochs.get(sessionId) ?? 0, record.authorityEpoch ?? 0));
126
+
96
127
  this.topics.set(sessionId, record);
97
- this.byTopic.set(record.topicId, sessionId);
128
+ }
129
+ this.rebuildInboundRoutes();
130
+ }
131
+
132
+ private rebuildInboundRoutes(): void {
133
+ this.byTopic.clear();
134
+ this.#ambiguousTopicIds.clear();
135
+ const activeByTopic = new Map<string, string>();
136
+
137
+ for (const [sessionId, record] of this.topics) {
138
+ if (record.authorityState === "delete_pending") {
139
+ this.#ambiguousTopicIds.add(record.topicId);
140
+ continue;
141
+ }
142
+ if (activeByTopic.has(record.topicId)) {
143
+ this.#ambiguousTopicIds.add(record.topicId);
144
+ continue;
145
+ }
146
+ activeByTopic.set(record.topicId, sessionId);
147
+ }
148
+
149
+ for (const [topicId, sessionId] of activeByTopic) {
150
+ if (!this.#ambiguousTopicIds.has(topicId)) this.byTopic.set(topicId, sessionId);
98
151
  }
99
152
  }
100
153
 
@@ -119,22 +172,36 @@ export class TopicRegistry {
119
172
  */
120
173
  async getOrCreateTopic(
121
174
  sessionId: string,
122
- create: () => Promise<string>,
175
+ create: () => Promise<unknown>,
123
176
  now: () => number = Date.now,
124
177
  name?: string,
125
178
  ): Promise<TopicRecord> {
126
179
  const existing = this.topics.get(sessionId);
180
+ if (existing?.authorityState === "delete_pending") throw new Error("topic authority is deletion-fenced");
127
181
  if (existing) return existing;
128
- // Concurrency guard: many session frames (identity/idle/turn/ask) can race
129
- // to first-use the same session. Without this, each call passes the
130
- // `existing` check before `create()` resolves and creates a DUPLICATE
131
- // forum topic. Share a single in-flight create per session id.
132
182
  const pending = this.inflight.get(sessionId);
133
183
  if (pending) return pending;
184
+ const epoch = this.epochs.get(sessionId) ?? 0;
134
185
  const promise = (async () => {
135
186
  const topicId = await create();
136
- const record: TopicRecord = { topicId, name, identitySent: false, createdAt: now() };
187
+ if (!isValidTopicId(topicId)) throw new Error("createForumTopic: invalid message_thread_id");
188
+ const revoked = (this.epochs.get(sessionId) ?? 0) !== epoch;
189
+ const record: TopicRecord = {
190
+ topicId,
191
+ name,
192
+ identitySent: false,
193
+ createdAt: now(),
194
+ authorityEpoch: revoked ? (this.epochs.get(sessionId) ?? 0) : epoch,
195
+ ...(revoked ? { authorityState: "delete_pending" as const } : {}),
196
+ };
137
197
  this.topics.set(sessionId, record);
198
+ if (revoked) throw new Error("topic authority was revoked during creation");
199
+ if (this.#ambiguousTopicIds.has(topicId)) return record;
200
+ if (this.byTopic.has(topicId)) {
201
+ this.byTopic.delete(topicId);
202
+ this.#ambiguousTopicIds.add(topicId);
203
+ return record;
204
+ }
138
205
  this.byTopic.set(topicId, sessionId);
139
206
  return record;
140
207
  })();
@@ -165,6 +232,21 @@ export class TopicRegistry {
165
232
  record.identityKey = identityKey;
166
233
  return true;
167
234
  }
235
+ /** Start the orphan grace clock on the first positive liveness-loss observation. */
236
+ markOrphaned(sessionId: string, now: number): boolean {
237
+ const record = this.topics.get(sessionId);
238
+ if (!record || record.orphanedAt !== undefined) return false;
239
+ record.orphanedAt = now;
240
+ return true;
241
+ }
242
+
243
+ /** Clear a prior orphan observation after the endpoint is positively live again. */
244
+ clearOrphaned(sessionId: string): boolean {
245
+ const record = this.topics.get(sessionId);
246
+ if (!record || record.orphanedAt === undefined) return false;
247
+ delete record.orphanedAt;
248
+ return true;
249
+ }
168
250
 
169
251
  /** Last durably consumed SDK event cursor for reconnect replay. */
170
252
  replayCursor(sessionId: string): { generation: number; seq: number } | undefined {
@@ -241,17 +323,42 @@ export class TopicRegistry {
241
323
  record.nameReconcilePending = false;
242
324
  }
243
325
 
244
- /** Remove a session topic record after Telegram deletes the topic. */
245
- delete(sessionId: string): boolean {
326
+ /** Fence new work before the remote delete starts, including an absent in-flight create. */
327
+ beginDelete(sessionId: string): TopicRecord | undefined {
246
328
  const record = this.topics.get(sessionId);
247
- if (!record) return false;
329
+ const epoch = Math.max(this.epochs.get(sessionId) ?? 0, record?.authorityEpoch ?? 0) + 1;
330
+ this.epochs.set(sessionId, epoch);
331
+ if (!record) return undefined;
332
+ record.authorityEpoch = epoch;
333
+ record.authorityState = "delete_pending";
334
+ if (this.byTopic.get(record.topicId) === sessionId) this.byTopic.delete(record.topicId);
335
+ return record;
336
+ }
337
+
338
+ /** Wait for a revoked create to settle before admitting a later lifecycle epoch. */
339
+ async awaitInflight(sessionId: string): Promise<void> {
340
+ await this.inflight.get(sessionId)?.catch(() => undefined);
341
+ }
342
+
343
+ /** Remove only after a definite remote deletion; ambiguity deliberately retains its fence. */
344
+ settleDelete(sessionId: string, topicId: string): boolean {
345
+ const record = this.topics.get(sessionId);
346
+ if (!record || record.topicId !== topicId || record.authorityState !== "delete_pending") return false;
248
347
  this.topics.delete(sessionId);
249
- this.byTopic.delete(record.topicId);
250
348
  return true;
251
349
  }
252
350
 
351
+ /** Remove a topic record immediately for local/test cleanup compatibility. */
352
+ delete(sessionId: string): boolean {
353
+ const record = this.topics.get(sessionId);
354
+ if (!record) return false;
355
+ this.epochs.set(sessionId, Math.max(this.epochs.get(sessionId) ?? 0, record.authorityEpoch ?? 0) + 1);
356
+ if (this.byTopic.get(record.topicId) === sessionId) this.byTopic.delete(record.topicId);
357
+ return this.topics.delete(sessionId);
358
+ }
359
+
253
360
  /** Serialise for atomic persistence beside the daemon state. */
254
361
  serialize(): TopicRegistryState {
255
- return { topics: Object.fromEntries(this.topics) };
362
+ return { topics: Object.fromEntries(this.topics), fences: Object.fromEntries(this.epochs) };
256
363
  }
257
364
  }
@@ -133,6 +133,7 @@ function invoke(
133
133
  operation: string,
134
134
  input: ControlInput,
135
135
  confirm: boolean | undefined,
136
+ idempotencyKey: string | undefined,
136
137
  ): Promise<ControlValue> | ControlValue {
137
138
  switch (operation) {
138
139
  case "turn.prompt":
@@ -148,7 +149,12 @@ function invoke(
148
149
  case "ask.answer":
149
150
  return surface.answerAsk(text(input, "id"), input.answer);
150
151
  case "workflow.gate_answer":
151
- return surface.answerGate(text(input, "id"), input.response, input.expectedSessionId as string | undefined);
152
+ return surface.answerGate(
153
+ text(input, "id"),
154
+ input.response,
155
+ input.expectedSessionId as string | undefined,
156
+ idempotencyKey,
157
+ );
152
158
  case "workflow.plan_approve":
153
159
  return surface.approvePlan(text(input, "id"), input.choice, input.expectedSessionId as string | undefined);
154
160
  case "skill.invoke":
@@ -276,7 +282,13 @@ async function execute(surface: ControlSurface, row: Operation, request: Control
276
282
  return {
277
283
  id: request.id,
278
284
  ok: true,
279
- result: await invoke(surface, row.sdkId, request.input as ControlInput, request.confirm),
285
+ result: await invoke(
286
+ surface,
287
+ row.sdkId,
288
+ request.input as ControlInput,
289
+ request.confirm,
290
+ request.idempotencyKey,
291
+ ),
280
292
  };
281
293
  } catch (error) {
282
294
  return errorResponse(request.id, row, error);
@@ -12,7 +12,12 @@ export interface ControlSurface {
12
12
  abort(): Promise<ControlValue> | ControlValue;
13
13
  abortAndPrompt(text: string): Promise<ControlValue> | ControlValue;
14
14
  answerAsk(id: string, answer: ControlValue): Promise<ControlValue> | ControlValue;
15
- answerGate(id: string, response: ControlValue, expectedSessionId?: string): Promise<ControlValue> | ControlValue;
15
+ answerGate(
16
+ id: string,
17
+ response: ControlValue,
18
+ expectedSessionId?: string,
19
+ idempotencyKey?: string,
20
+ ): Promise<ControlValue> | ControlValue;
16
21
  approvePlan(id: string, choice: ControlValue, expectedSessionId?: string): Promise<ControlValue> | ControlValue;
17
22
  invokeSkill(name: string, args: ControlValue): Promise<ControlValue> | ControlValue;
18
23
  setPlanMode(on: boolean): Promise<ControlValue> | ControlValue;
@@ -14,8 +14,14 @@ export interface SessionSdkHostOptions extends HostEndpointAdapters {
14
14
  installProviderDefinitions?: (capability: string, definitions: unknown) => void;
15
15
  onProviderDefinitionsRemoved?: (capability: string) => void;
16
16
  onReverseCancel?: (requestId: string, reason: "provider_disconnected" | "lease_released") => void;
17
+ /** Best-effort capabilities mirrored from the native transport for out-of-band consumers. */
18
+ connectionCapabilities?: (connectionId: string) => ReadonlySet<string>;
17
19
  }
18
20
 
21
+ const TOOL_ACTIVITY_V1 = "tool_activity_v1";
22
+ const CAP_GATED_FRAME_KINDS = new Set(["tool_activity", "reasoning_summary"]);
23
+ const EMPTY_CAPABILITIES: ReadonlySet<string> = new Set();
24
+
19
25
  /** SDK hosting is independent of notification configuration. Only root sessions host an endpoint. */
20
26
  export function shouldHostSdk(_settings: unknown, isTopLevel: boolean, env: NodeJS.ProcessEnv = process.env): boolean {
21
27
  return isTopLevel && env.GJC_SDK_DISABLE !== "1";
@@ -175,6 +181,19 @@ export class SessionSdkHost {
175
181
  await this.#options.sendFrame(connectionId, frame);
176
182
  }
177
183
 
184
+ /**
185
+ * Best-effort delivery for structured error frames. When the original failure
186
+ * was already a disconnected/dead connection, a second send must not escape
187
+ * the fire-and-forget `#onFrame` callback as an unhandled rejection.
188
+ */
189
+ async #sendBestEffort(connectionId: string, frame: SdkFrame): Promise<void> {
190
+ try {
191
+ await this.#send(connectionId, frame);
192
+ } catch {
193
+ // Per-connection delivery only; never rethrow into fire-and-forget handlers.
194
+ }
195
+ }
196
+
178
197
  async #onFrame(connectionId: string, frame: SdkFrame): Promise<void> {
179
198
  try {
180
199
  switch (frame.type) {
@@ -200,11 +219,20 @@ export class SessionSdkHost {
200
219
  const sinceGeneration = rawGeneration;
201
220
  const sinceSeq = rawSeq;
202
221
  const replay = this.events.replay(sinceSeq, sinceGeneration);
222
+ const capabilities = Array.isArray(frame.capabilities)
223
+ ? new Set(
224
+ frame.capabilities.filter((capability): capability is string => typeof capability === "string"),
225
+ )
226
+ : (this.#options.connectionCapabilities?.(connectionId) ?? EMPTY_CAPABILITIES);
227
+ const events = replay.events.filter(
228
+ event => !CAP_GATED_FRAME_KINDS.has(String(event.kind)) || capabilities.has(TOOL_ACTIVITY_V1),
229
+ );
203
230
  await this.#send(connectionId, {
204
231
  type: "event_replay_result",
205
232
  id,
206
233
  ok: true,
207
234
  ...replay,
235
+ events,
208
236
  generation: this.events.generation,
209
237
  lastSeq: this.events.sequence,
210
238
  });
@@ -283,7 +311,9 @@ export class SessionSdkHost {
283
311
  return;
284
312
  }
285
313
  } catch (error) {
286
- await this.#send(connectionId, errorFrame(connectionId, frame, error));
314
+ // Structured error delivery is best-effort: if the client already
315
+ // disconnected, do not escalate a second send failure process-wide.
316
+ await this.#sendBestEffort(connectionId, errorFrame(connectionId, frame, error));
287
317
  }
288
318
  }
289
319
  #observeRequest(kind: "control" | "query", connectionId: string, frame: SdkFrame): void {
@@ -378,31 +378,24 @@ export class QueryHandlers {
378
378
  const input = request.input ?? {};
379
379
  const artifactId = String(input.artifactId ?? "");
380
380
  const start = Math.max(0, Number(input.offset ?? 0));
381
- const maxRawBytes = Math.floor(((RESPONSE_CEILING_BYTES - 4096) * 3) / 4);
381
+ const emptyResult = { artifactId, offset: start, bytes: "", complete: false };
382
+ const baseBytes = Buffer.byteLength(JSON.stringify({ id: request.id, ok: true, result: emptyResult }));
383
+ const maxRawBytes = Math.floor((RESPONSE_CEILING_BYTES - baseBytes) / 4) * 3;
382
384
  const requested = Math.max(0, Math.min(Number(input.length ?? TARGET_PAGE_BYTES), maxRawBytes));
383
385
  const artifact = await this.surface.getArtifactRange?.(artifactId, start, requested);
384
386
  if (!artifact) return this.#error(request, "resource_gone");
385
- let bytes = Buffer.from(artifact.bytes);
386
- let result = {
387
- artifactId,
388
- offset: start,
389
- bytes: bytes.toString("base64"),
390
- complete: start + bytes.length >= artifact.totalBytes,
391
- };
392
- while (
393
- Buffer.byteLength(JSON.stringify({ id: request.id, ok: true, result })) >= RESPONSE_CEILING_BYTES &&
394
- bytes.length > 0
395
- ) {
396
- bytes = bytes.subarray(0, bytes.length - Math.max(1, Math.ceil(bytes.length / 128)));
397
- result = {
387
+ const bytes = Buffer.from(artifact.bytes);
388
+ if (bytes.length === 0 && start < artifact.totalBytes) return this.#error(request, "item_too_large");
389
+ return {
390
+ id: request.id,
391
+ ok: true,
392
+ result: {
398
393
  artifactId,
399
394
  offset: start,
400
395
  bytes: bytes.toString("base64"),
401
396
  complete: start + bytes.length >= artifact.totalBytes,
402
- };
403
- }
404
- if (bytes.length === 0 && start < artifact.totalBytes) return this.#error(request, "item_too_large");
405
- return { id: request.id, ok: true, result };
397
+ },
398
+ };
406
399
  }
407
400
 
408
401
  async #paginate(
@@ -417,12 +410,17 @@ export class QueryHandlers {
417
410
  ): Promise<QueryResponse> {
418
411
  const values = Array.isArray(snapshot) ? snapshot : [snapshot];
419
412
  const items: unknown[] = [];
413
+ let itemsBytes = 2; // []
420
414
  let index = offset;
421
415
  while (index < values.length) {
422
- const candidate = [...items, values[index]];
423
- if (Buffer.byteLength(JSON.stringify(candidate)) > TARGET_PAGE_BYTES && items.length) break;
424
- if (Buffer.byteLength(JSON.stringify(candidate)) > RESPONSE_CEILING_BYTES) break;
425
- items.push(values[index++]);
416
+ const item = values[index]!;
417
+ const itemBytes = Buffer.byteLength(JSON.stringify(item) ?? "null");
418
+ const candidateBytes = itemsBytes + itemBytes + (items.length ? 1 : 0);
419
+ if (candidateBytes > TARGET_PAGE_BYTES && items.length) break;
420
+ if (candidateBytes > RESPONSE_CEILING_BYTES) break;
421
+ items.push(item);
422
+ itemsBytes = candidateBytes;
423
+ index++;
426
424
  }
427
425
  const complete = index >= values.length;
428
426
  const page: QueryPage = { items, complete, revision };