@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
@@ -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";
@@ -96,6 +102,7 @@ export class SessionSdkHost {
96
102
  readonly reverse: ReverseLeaseRuntime;
97
103
  readonly #options: SessionSdkHostOptions;
98
104
  #started = false;
105
+ #stopPromise?: Promise<"stopped">;
99
106
  #unsubscribe?: () => void;
100
107
  #registration?: { writer: BrokerIndexWriter; generation: number };
101
108
 
@@ -148,16 +155,27 @@ export class SessionSdkHost {
148
155
  }
149
156
 
150
157
  async stop(): Promise<"stopped" | "already"> {
158
+ if (this.#stopPromise) return this.#stopPromise;
151
159
  if (!this.#started) return "already";
160
+ const stopPromise = this.#stopStartedHost();
161
+ this.#stopPromise = stopPromise;
162
+ try {
163
+ return await stopPromise;
164
+ } finally {
165
+ if (this.#stopPromise === stopPromise) this.#stopPromise = undefined;
166
+ }
167
+ }
168
+
169
+ async #stopStartedHost(): Promise<"stopped"> {
152
170
  this.#unsubscribe?.();
153
171
  this.#unsubscribe = undefined;
154
- this.#started = false;
155
172
  if (this.#registration?.writer.unregister)
156
173
  await this.#registration.writer.unregister({
157
174
  sessionId: this.#options.sessionId,
158
175
  stateRoot: this.#options.stateRoot,
159
176
  endpointGeneration: this.events.generation,
160
177
  });
178
+ this.#started = false;
161
179
  return "stopped";
162
180
  }
163
181
 
@@ -175,6 +193,19 @@ export class SessionSdkHost {
175
193
  await this.#options.sendFrame(connectionId, frame);
176
194
  }
177
195
 
196
+ /**
197
+ * Best-effort delivery for structured error frames. When the original failure
198
+ * was already a disconnected/dead connection, a second send must not escape
199
+ * the fire-and-forget `#onFrame` callback as an unhandled rejection.
200
+ */
201
+ async #sendBestEffort(connectionId: string, frame: SdkFrame): Promise<void> {
202
+ try {
203
+ await this.#send(connectionId, frame);
204
+ } catch {
205
+ // Per-connection delivery only; never rethrow into fire-and-forget handlers.
206
+ }
207
+ }
208
+
178
209
  async #onFrame(connectionId: string, frame: SdkFrame): Promise<void> {
179
210
  try {
180
211
  switch (frame.type) {
@@ -200,11 +231,20 @@ export class SessionSdkHost {
200
231
  const sinceGeneration = rawGeneration;
201
232
  const sinceSeq = rawSeq;
202
233
  const replay = this.events.replay(sinceSeq, sinceGeneration);
234
+ const capabilities = Array.isArray(frame.capabilities)
235
+ ? new Set(
236
+ frame.capabilities.filter((capability): capability is string => typeof capability === "string"),
237
+ )
238
+ : (this.#options.connectionCapabilities?.(connectionId) ?? EMPTY_CAPABILITIES);
239
+ const events = replay.events.filter(
240
+ event => !CAP_GATED_FRAME_KINDS.has(String(event.kind)) || capabilities.has(TOOL_ACTIVITY_V1),
241
+ );
203
242
  await this.#send(connectionId, {
204
243
  type: "event_replay_result",
205
244
  id,
206
245
  ok: true,
207
246
  ...replay,
247
+ events,
208
248
  generation: this.events.generation,
209
249
  lastSeq: this.events.sequence,
210
250
  });
@@ -283,7 +323,9 @@ export class SessionSdkHost {
283
323
  return;
284
324
  }
285
325
  } catch (error) {
286
- await this.#send(connectionId, errorFrame(connectionId, frame, error));
326
+ // Structured error delivery is best-effort: if the client already
327
+ // disconnected, do not escalate a second send failure process-wide.
328
+ await this.#sendBestEffort(connectionId, errorFrame(connectionId, frame, error));
287
329
  }
288
330
  }
289
331
  #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 };