@gajae-code/coding-agent 0.10.2 → 0.11.0

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 (465) hide show
  1. package/CHANGELOG.md +50 -2
  2. package/dist/types/append-only-mode.d.ts +1 -1
  3. package/dist/types/cli/args.d.ts +4 -5
  4. package/dist/types/cli/daemon-cli.d.ts +12 -1
  5. package/dist/types/cli/notify-cli.d.ts +21 -1
  6. package/dist/types/cli.d.ts +3 -0
  7. package/dist/types/commands/daemon.d.ts +39 -0
  8. package/dist/types/commands/launch.d.ts +3 -0
  9. package/dist/types/commands/notify.d.ts +28 -1
  10. package/dist/types/commands/sdk.d.ts +33 -0
  11. package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
  12. package/dist/types/config/model-profile-activation.d.ts +35 -15
  13. package/dist/types/config/model-profiles.d.ts +11 -5
  14. package/dist/types/config/model-registry.d.ts +3 -3
  15. package/dist/types/config/model-resolver.d.ts +39 -6
  16. package/dist/types/config/model-selector-value.d.ts +17 -0
  17. package/dist/types/config/models-config-schema.d.ts +10 -6
  18. package/dist/types/config/settings-schema.d.ts +133 -6
  19. package/dist/types/config/settings.d.ts +63 -35
  20. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
  21. package/dist/types/coordinator-mcp/server.d.ts +6 -49
  22. package/dist/types/daemon/builtin.d.ts +6 -5
  23. package/dist/types/daemon/control-types.d.ts +1 -1
  24. package/dist/types/discovery/mcp-json.d.ts +12 -1
  25. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  26. package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
  27. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
  28. package/dist/types/extensibility/extensions/loader.d.ts +10 -1
  29. package/dist/types/extensibility/extensions/runner.d.ts +3 -3
  30. package/dist/types/extensibility/extensions/types.d.ts +141 -16
  31. package/dist/types/extensibility/hooks/types.d.ts +1 -1
  32. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
  33. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
  35. package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
  36. package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
  37. package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
  38. package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
  39. package/dist/types/harness-control-plane/operate.d.ts +5 -5
  40. package/dist/types/harness-control-plane/owner.d.ts +18 -4
  41. package/dist/types/harness-control-plane/receipts.d.ts +0 -1
  42. package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
  43. package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
  44. package/dist/types/harness-control-plane/types.d.ts +3 -4
  45. package/dist/types/lsp/index.d.ts +1 -1
  46. package/dist/types/main.d.ts +32 -38
  47. package/dist/types/modes/acp/acp-agent.d.ts +224 -53
  48. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
  49. package/dist/types/modes/acp/acp-mode.d.ts +6 -15
  50. package/dist/types/modes/acp/permission-mode.d.ts +4 -0
  51. package/dist/types/modes/acp/startup-options.d.ts +6 -0
  52. package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
  53. package/dist/types/modes/components/model-selector.d.ts +1 -0
  54. package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
  55. package/dist/types/modes/components/settings-selector.d.ts +6 -4
  56. package/dist/types/modes/components/welcome.d.ts +1 -1
  57. package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
  58. package/dist/types/modes/index.d.ts +0 -4
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/runtime-init.d.ts +2 -2
  61. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
  62. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
  63. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
  64. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
  65. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
  66. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +169 -30
  67. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
  68. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
  69. package/dist/types/modes/types.d.ts +5 -0
  70. package/dist/types/runtime-mcp/client.d.ts +2 -0
  71. package/dist/types/runtime-mcp/config.d.ts +5 -1
  72. package/dist/types/runtime-mcp/manager.d.ts +8 -1
  73. package/dist/types/runtime-mcp/types.d.ts +4 -0
  74. package/dist/types/sdk/acp/adapter.d.ts +67 -0
  75. package/dist/types/sdk/acp/index.d.ts +2 -0
  76. package/dist/types/sdk/broker/broker.d.ts +53 -0
  77. package/dist/types/sdk/broker/discovery.d.ts +30 -0
  78. package/dist/types/sdk/broker/ensure.d.ts +33 -0
  79. package/dist/types/sdk/broker/identity.d.ts +3 -0
  80. package/dist/types/sdk/broker/index.d.ts +8 -0
  81. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
  82. package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
  83. package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
  84. package/dist/types/sdk/broker/runtime.d.ts +33 -0
  85. package/dist/types/sdk/broker/session-index.d.ts +57 -0
  86. package/dist/types/sdk/broker/state-version.d.ts +8 -0
  87. package/dist/types/sdk/broker/transport.d.ts +9 -0
  88. package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
  89. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
  90. package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
  91. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
  92. package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
  93. package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
  94. package/dist/types/sdk/bus/config.d.ts +274 -0
  95. package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
  96. package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
  97. package/dist/types/sdk/bus/discord-daemon.d.ts +45 -0
  98. package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
  99. package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
  100. package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
  101. package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
  102. package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
  103. package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
  104. package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
  105. package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
  106. package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
  107. package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
  108. package/dist/types/sdk/bus/session-control.d.ts +83 -0
  109. package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
  110. package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
  111. package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
  112. package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
  113. package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
  114. package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
  115. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
  116. package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
  117. package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +55 -17
  118. package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
  119. package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
  120. package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
  121. package/dist/types/sdk/cli/index.d.ts +1 -0
  122. package/dist/types/sdk/cli/session-cli.d.ts +19 -0
  123. package/dist/types/sdk/client/client.d.ts +1 -0
  124. package/dist/types/sdk/client/discovery.d.ts +27 -0
  125. package/dist/types/sdk/client/index.d.ts +2 -0
  126. package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
  127. package/dist/types/sdk/host/control/index.d.ts +2 -0
  128. package/dist/types/sdk/host/control/operations.d.ts +65 -0
  129. package/dist/types/sdk/host/events.d.ts +35 -0
  130. package/dist/types/sdk/host/host.d.ts +35 -0
  131. package/dist/types/sdk/host/index.d.ts +4 -0
  132. package/dist/types/sdk/host/query/cursor.d.ts +62 -0
  133. package/dist/types/sdk/host/query/handlers.d.ts +70 -0
  134. package/dist/types/sdk/host/query/index.d.ts +3 -0
  135. package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
  136. package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
  137. package/dist/types/sdk/host/types.d.ts +32 -0
  138. package/dist/types/sdk/index.d.ts +6 -0
  139. package/dist/types/sdk/lifecycle-session.d.ts +14 -0
  140. package/dist/types/sdk/mcp/index.d.ts +1 -0
  141. package/dist/types/sdk/mcp/server.d.ts +37 -0
  142. package/dist/types/sdk/models.d.ts +44 -0
  143. package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
  144. package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
  145. package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
  146. package/dist/types/sdk/startup-capability.d.ts +50 -0
  147. package/dist/types/session/agent-session.d.ts +114 -21
  148. package/dist/types/session/default-model-selection.d.ts +25 -0
  149. package/dist/types/session/fallback-chain-controller.d.ts +52 -0
  150. package/dist/types/session/session-manager.d.ts +58 -1
  151. package/dist/types/setup/credential-auto-import.d.ts +36 -6
  152. package/dist/types/tools/ask-answer-registry.d.ts +1 -1
  153. package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
  154. package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
  155. package/dist/types/tools/browser/launch.d.ts +22 -0
  156. package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
  157. package/dist/types/tools/browser/profile-posture.d.ts +37 -0
  158. package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
  159. package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
  160. package/dist/types/tools/browser/registry.d.ts +19 -0
  161. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  162. package/dist/types/tools/index.d.ts +8 -8
  163. package/package.json +34 -13
  164. package/scripts/build-sdk-package-smoke.ts +94 -0
  165. package/scripts/capture-notifications-settings-showcase.ts +435 -0
  166. package/scripts/compile-args.ts +4 -2
  167. package/scripts/g005-daemon-smoke.ts +4 -5
  168. package/scripts/g011-daemon-path-smoke.ts +5 -5
  169. package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
  170. package/scripts/generate-sdk-operation-inventory.ts +867 -0
  171. package/scripts/generate-telegram-baseline-manifest.ts +128 -0
  172. package/scripts/manifest-schema.ts +124 -0
  173. package/scripts/run-test-manifest.ts +153 -0
  174. package/scripts/run-threat-model-check.ts +157 -0
  175. package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
  176. package/scripts/verify-gjc-sdk-rename.ts +90 -0
  177. package/src/append-only-mode.ts +1 -1
  178. package/src/cli/args.ts +43 -12
  179. package/src/cli/auth-gateway-cli.ts +2 -2
  180. package/src/cli/config-cli.ts +2 -1
  181. package/src/cli/daemon-cli.ts +63 -14
  182. package/src/cli/list-models.ts +1 -1
  183. package/src/cli/notify-cli.ts +288 -286
  184. package/src/cli.ts +21 -2
  185. package/src/commands/daemon.ts +50 -6
  186. package/src/commands/harness.ts +93 -36
  187. package/src/commands/launch.ts +5 -2
  188. package/src/commands/mcp-serve.ts +19 -12
  189. package/src/commands/notify.ts +36 -3
  190. package/src/commands/sdk.ts +426 -0
  191. package/src/commit/agentic/agent.ts +1 -0
  192. package/src/config/atomic-yaml-patch.ts +423 -0
  193. package/src/config/model-profile-activation.ts +240 -89
  194. package/src/config/model-profiles.ts +18 -15
  195. package/src/config/model-registry.ts +51 -26
  196. package/src/config/model-resolver.ts +138 -51
  197. package/src/config/model-selector-value.ts +50 -0
  198. package/src/config/models-config-schema.ts +14 -21
  199. package/src/config/settings-schema.ts +128 -10
  200. package/src/config/settings.ts +1120 -158
  201. package/src/coordinator-mcp/model-preset.ts +74 -20
  202. package/src/coordinator-mcp/server.ts +1597 -3052
  203. package/src/daemon/builtin.ts +8 -5
  204. package/src/daemon/control-types.ts +1 -1
  205. package/src/defaults/gjc/skills/team/SKILL.md +4 -4
  206. package/src/discovery/mcp-json.ts +327 -20
  207. package/src/extensibility/custom-tools/types.ts +1 -1
  208. package/src/extensibility/extensions/compact-handler.ts +3 -3
  209. package/src/extensibility/extensions/get-commands-handler.ts +2 -2
  210. package/src/extensibility/extensions/loader.ts +69 -5
  211. package/src/extensibility/extensions/runner.ts +112 -4
  212. package/src/extensibility/extensions/types.ts +176 -17
  213. package/src/extensibility/hooks/types.ts +1 -1
  214. package/src/gjc-runtime/launch-tmux.ts +170 -86
  215. package/src/gjc-runtime/psmux-detect.ts +82 -4
  216. package/src/gjc-runtime/session-state-sidecar.ts +41 -31
  217. package/src/gjc-runtime/team-runtime.ts +54 -35
  218. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
  219. package/src/gjc-runtime/tmux-owner-isolation.ts +1 -1
  220. package/src/gjc-runtime/tmux-sessions.ts +19 -13
  221. package/src/gjc-runtime/ultragoal-guard.ts +53 -6
  222. package/src/gjc-runtime/ultragoal-runtime.ts +155 -3
  223. package/src/gjc-runtime/windows-powershell-command.ts +58 -0
  224. package/src/harness-control-plane/operate.ts +9 -9
  225. package/src/harness-control-plane/owner.ts +283 -39
  226. package/src/harness-control-plane/receipts.ts +0 -1
  227. package/src/harness-control-plane/sdk-transport.ts +395 -0
  228. package/src/harness-control-plane/session-transport.ts +94 -0
  229. package/src/harness-control-plane/types.ts +1 -1
  230. package/src/internal-urls/docs-index.generated.ts +34 -35
  231. package/src/lsp/client.ts +42 -6
  232. package/src/lsp/index.ts +2 -2
  233. package/src/main.ts +299 -204
  234. package/src/modes/DESIGN.md +258 -0
  235. package/src/modes/acp/acp-agent.ts +1228 -2612
  236. package/src/modes/acp/acp-client-bridge.ts +2 -20
  237. package/src/modes/acp/acp-event-mapper.ts +18 -2
  238. package/src/modes/acp/acp-mode.ts +8 -36
  239. package/src/modes/acp/permission-mode.ts +25 -0
  240. package/src/modes/acp/startup-options.ts +6 -0
  241. package/src/modes/components/agent-dashboard.ts +37 -17
  242. package/src/modes/components/model-selector.ts +93 -35
  243. package/src/modes/components/notifications-settings-editor.ts +1379 -0
  244. package/src/modes/components/settings-selector.ts +60 -11
  245. package/src/modes/components/welcome.ts +4 -4
  246. package/src/modes/controllers/command-controller.ts +1 -2
  247. package/src/modes/controllers/event-controller.ts +11 -16
  248. package/src/modes/controllers/extension-ui-controller.ts +348 -4
  249. package/src/modes/controllers/selector-controller.ts +674 -36
  250. package/src/modes/index.ts +0 -22
  251. package/src/modes/interactive-mode.ts +59 -20
  252. package/src/modes/runtime-init.ts +298 -6
  253. package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
  254. package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
  255. package/src/modes/shared/agent-wire/event-contract.ts +7 -8
  256. package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
  257. package/src/modes/shared/agent-wire/event-observation.ts +16 -7
  258. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1254 -87
  259. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
  260. package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
  261. package/src/modes/types.ts +5 -0
  262. package/src/runtime-mcp/client.ts +94 -19
  263. package/src/runtime-mcp/config.ts +27 -15
  264. package/src/runtime-mcp/manager.ts +343 -105
  265. package/src/runtime-mcp/oauth-flow.ts +42 -12
  266. package/src/runtime-mcp/transports/http.ts +49 -19
  267. package/src/runtime-mcp/transports/stdio.ts +47 -28
  268. package/src/runtime-mcp/types.ts +10 -0
  269. package/src/sdk/acp/adapter.ts +418 -0
  270. package/src/sdk/acp/index.ts +2 -0
  271. package/src/sdk/broker/broker.ts +642 -0
  272. package/src/sdk/broker/discovery.ts +94 -0
  273. package/src/sdk/broker/ensure.ts +249 -0
  274. package/src/sdk/broker/identity.ts +42 -0
  275. package/src/sdk/broker/index.ts +8 -0
  276. package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
  277. package/src/sdk/broker/internal-source.bunfig.toml +1 -0
  278. package/src/sdk/broker/lifecycle-ledger.ts +286 -0
  279. package/src/sdk/broker/lifecycle.ts +2157 -0
  280. package/src/sdk/broker/process-incarnation.ts +159 -0
  281. package/src/sdk/broker/runtime.ts +164 -0
  282. package/src/sdk/broker/session-index.ts +318 -0
  283. package/src/sdk/broker/state-version.ts +23 -0
  284. package/src/sdk/broker/transport.ts +125 -0
  285. package/src/sdk/bus/chat-command-policy.ts +107 -0
  286. package/src/sdk/bus/chat-daemon-cli.ts +214 -0
  287. package/src/sdk/bus/chat-daemon-control.ts +713 -0
  288. package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
  289. package/src/sdk/bus/chat-effect-journal.ts +353 -0
  290. package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
  291. package/src/sdk/bus/config.ts +433 -0
  292. package/src/sdk/bus/conversation-store.ts +391 -0
  293. package/src/sdk/bus/discord-conversation.ts +85 -0
  294. package/src/sdk/bus/discord-daemon.ts +2275 -0
  295. package/src/sdk/bus/discord-live-provider.ts +521 -0
  296. package/src/sdk/bus/discord-provider.ts +56 -0
  297. package/src/{notifications → sdk/bus}/html-format.ts +2 -2
  298. package/src/sdk/bus/index.ts +3943 -0
  299. package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
  300. package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
  301. package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
  302. package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
  303. package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
  304. package/src/sdk/bus/natives-augment.d.ts +38 -0
  305. package/src/sdk/bus/notification-orchestration.ts +535 -0
  306. package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
  307. package/src/sdk/bus/session-control.ts +333 -0
  308. package/src/sdk/bus/slack-conversation.ts +74 -0
  309. package/src/sdk/bus/slack-daemon.ts +2144 -0
  310. package/src/sdk/bus/slack-live-provider.ts +380 -0
  311. package/src/sdk/bus/slack-provider.ts +76 -0
  312. package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
  313. package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
  314. package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
  315. package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
  316. package/src/{notifications → sdk/bus}/telegram-daemon.ts +502 -54
  317. package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
  318. package/src/sdk/bus/telegram-setup.ts +663 -0
  319. package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
  320. package/src/sdk/cli/index.ts +1 -0
  321. package/src/sdk/cli/session-cli.ts +275 -0
  322. package/src/sdk/client/client.ts +1 -0
  323. package/src/sdk/client/discovery.ts +108 -0
  324. package/src/sdk/client/index.ts +2 -0
  325. package/src/sdk/host/control/dispatch.ts +367 -0
  326. package/src/sdk/host/control/index.ts +10 -0
  327. package/src/sdk/host/control/operations.ts +63 -0
  328. package/src/sdk/host/events.ts +91 -0
  329. package/src/sdk/host/host.ts +296 -0
  330. package/src/sdk/host/index.ts +4 -0
  331. package/src/sdk/host/query/cursor.ts +189 -0
  332. package/src/sdk/host/query/handlers.ts +533 -0
  333. package/src/sdk/host/query/index.ts +3 -0
  334. package/src/sdk/host/query/revision-store.ts +1126 -0
  335. package/src/sdk/host/reverse-leases.ts +325 -0
  336. package/src/sdk/host/types.ts +29 -0
  337. package/src/sdk/index.ts +13 -0
  338. package/src/sdk/lifecycle-session.ts +39 -0
  339. package/src/sdk/mcp/index.ts +1 -0
  340. package/src/sdk/mcp/server.ts +332 -0
  341. package/src/sdk/models.ts +168 -0
  342. package/src/sdk/protocol/adapter-validation.ts +48 -0
  343. package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
  344. package/src/sdk/protocol/operation-registry.ts +312 -0
  345. package/src/{sdk.ts → sdk/session.ts} +339 -212
  346. package/src/sdk/startup-capability.ts +189 -0
  347. package/src/session/agent-session.ts +2453 -894
  348. package/src/session/default-model-selection.ts +93 -0
  349. package/src/session/fallback-chain-controller.ts +136 -0
  350. package/src/session/session-manager.ts +438 -11
  351. package/src/session/session-storage.ts +8 -1
  352. package/src/setup/credential-auto-import.ts +290 -35
  353. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
  354. package/src/slash-commands/builtin-registry.ts +2 -1
  355. package/src/task/executor.ts +62 -13
  356. package/src/tools/ask-answer-registry.ts +1 -1
  357. package/src/tools/ask.ts +13 -8
  358. package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
  359. package/src/tools/browser/benchmark/detector-report.ts +157 -0
  360. package/src/tools/browser/benchmark/run-suite.ts +39 -0
  361. package/src/tools/browser/launch.ts +124 -19
  362. package/src/tools/browser/profile-discovery.ts +63 -0
  363. package/src/tools/browser/profile-posture.ts +59 -0
  364. package/src/tools/browser/profile-reuse.ts +102 -0
  365. package/src/tools/browser/profile-warmup.ts +109 -0
  366. package/src/tools/browser/registry.ts +87 -11
  367. package/src/tools/browser/tab-protocol.ts +1 -0
  368. package/src/tools/browser/tab-supervisor.ts +1 -0
  369. package/src/tools/browser/tab-worker.ts +1 -1
  370. package/src/tools/browser.ts +6 -0
  371. package/src/tools/index.ts +8 -8
  372. package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
  373. package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
  374. package/src/tools/telegram-send.ts +2 -2
  375. package/vendor/insane-search/MANIFEST.json +2 -1
  376. package/vendor/insane-search/engine/bias_check.py +4 -1
  377. package/vendor/insane-search/engine/phase0.py +26 -1
  378. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  379. package/dist/types/modes/bridge/auth.d.ts +0 -12
  380. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  381. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  382. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  383. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  384. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  385. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  386. package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
  387. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
  388. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  389. package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
  390. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  391. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  392. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  393. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  394. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  395. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  396. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  397. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  398. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  399. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  400. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  401. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  402. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  403. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  404. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
  405. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
  406. package/dist/types/notifications/config.d.ts +0 -132
  407. package/src/harness-control-plane/rpc-adapter.ts +0 -290
  408. package/src/modes/bridge/auth.ts +0 -41
  409. package/src/modes/bridge/bridge-client-bridge.ts +0 -47
  410. package/src/modes/bridge/bridge-mode.ts +0 -675
  411. package/src/modes/bridge/bridge-ui-context.ts +0 -200
  412. package/src/modes/bridge/event-stream.ts +0 -70
  413. package/src/modes/rpc/host-tools.ts +0 -1
  414. package/src/modes/rpc/host-uris.ts +0 -1
  415. package/src/modes/rpc/rpc-client.ts +0 -1166
  416. package/src/modes/rpc/rpc-mode.ts +0 -1029
  417. package/src/modes/rpc/rpc-socket-security.ts +0 -115
  418. package/src/modes/rpc/rpc-types.ts +0 -637
  419. package/src/modes/shared/agent-wire/command-contract.ts +0 -18
  420. package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
  421. package/src/modes/shared/agent-wire/command-validation.ts +0 -181
  422. package/src/modes/shared/agent-wire/handshake.ts +0 -157
  423. package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
  424. package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
  425. package/src/modes/shared/agent-wire/protocol.ts +0 -46
  426. package/src/modes/shared/agent-wire/responses.ts +0 -17
  427. package/src/modes/shared/agent-wire/scopes.ts +0 -94
  428. package/src/modes/shared/agent-wire/session-registry.ts +0 -109
  429. package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
  430. package/src/modes/shared/agent-wire/ui-result.ts +0 -48
  431. package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
  432. package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
  433. package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
  434. package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
  435. package/src/notifications/config.ts +0 -219
  436. package/src/notifications/index.ts +0 -1835
  437. package/src/notifications/telegram-daemon-cli.ts +0 -243
  438. package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
  439. /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
  440. /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
  441. /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
  442. /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
  443. /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
  444. /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
  445. /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
  446. /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
  447. /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
  448. /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
  449. /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
  450. /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
  451. /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
  452. /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
  453. /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
  454. /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
  455. /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
  456. /package/src/{notifications → sdk/bus}/engine.ts +0 -0
  457. /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
  458. /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
  459. /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
  460. /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
  461. /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
  462. /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
  463. /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
  464. /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
  465. /package/src/{notifications → sdk/bus}/threaded-render.ts +0 -0
@@ -0,0 +1,296 @@
1
+ import { type EventFrame, SessionEventStream } from "./events";
2
+ import { type ProviderLease, ReverseLeaseError, ReverseLeaseRuntime } from "./reverse-leases";
3
+ import type { BrokerIndexWriter, HostEndpointAdapters, SdkFrame } from "./types";
4
+
5
+ export type SdkRequestObserver = (kind: "control" | "query", connectionId: string, frame: SdkFrame) => void;
6
+
7
+ export interface SessionSdkHostOptions extends HostEndpointAdapters {
8
+ control?: (connectionId: string, frame: SdkFrame) => unknown | Promise<unknown>;
9
+ query?: (connectionId: string, frame: SdkFrame) => unknown | Promise<unknown>;
10
+ /** Best-effort diagnostic observation of accepted control/query frames. */
11
+ onRequest?: SdkRequestObserver;
12
+ /** Runs only after a successful control response has been sent to the client. */
13
+ afterControlResponse?: (connectionId: string, request: SdkFrame, response: SdkFrame) => void | Promise<void>;
14
+ installProviderDefinitions?: (capability: string, definitions: unknown) => void;
15
+ onProviderDefinitionsRemoved?: (capability: string) => void;
16
+ onReverseCancel?: (requestId: string, reason: "provider_disconnected" | "lease_released") => void;
17
+ }
18
+
19
+ /** SDK hosting is independent of notification configuration. Only root sessions host an endpoint. */
20
+ export function shouldHostSdk(_settings: unknown, isTopLevel: boolean, env: NodeJS.ProcessEnv = process.env): boolean {
21
+ return isTopLevel && env.GJC_SDK_DISABLE !== "1";
22
+ }
23
+
24
+ function errorFrame(connectionId: string, frame: SdkFrame, error: unknown): SdkFrame {
25
+ const candidate = error as { code?: unknown; message?: unknown };
26
+ const code =
27
+ error instanceof ReverseLeaseError
28
+ ? error.code
29
+ : typeof candidate?.code === "string"
30
+ ? candidate.code
31
+ : "internal";
32
+ const message = typeof candidate?.message === "string" ? candidate.message : "SDK host operation failed.";
33
+ return {
34
+ type: "reverse_response",
35
+ id: typeof frame.id === "string" ? frame.id : "",
36
+ connectionId,
37
+ leaseId: typeof frame.leaseId === "string" ? frame.leaseId : "",
38
+ ok: false,
39
+ error: { code, message },
40
+ };
41
+ }
42
+
43
+ function leaseState(id: unknown, lease: ProviderLease, active = lease.active): SdkFrame {
44
+ return {
45
+ type: "lease_state",
46
+ id: typeof id === "string" ? id : "",
47
+ connectionId: lease.connectionId,
48
+ capability: lease.capability,
49
+ leaseId: lease.leaseId,
50
+ leaseExpiresAt: new Date(lease.expiresAt).toISOString(),
51
+ active,
52
+ };
53
+ }
54
+
55
+ function registeredNames(definitions: unknown): string[] {
56
+ const entries = Array.isArray(definitions)
57
+ ? definitions
58
+ : definitions && typeof definitions === "object"
59
+ ? Object.values(definitions as Record<string, unknown>).flatMap(value => (Array.isArray(value) ? value : []))
60
+ : [];
61
+ return entries.flatMap(entry =>
62
+ entry && typeof entry === "object" && typeof (entry as Record<string, unknown>).name === "string"
63
+ ? [(entry as Record<string, string>).name]
64
+ : [],
65
+ );
66
+ }
67
+
68
+ function invalidFrame(message: string): Error {
69
+ return Object.assign(new Error(message), { code: "invalid_reverse_frame" });
70
+ }
71
+ function record(value: unknown): Record<string, unknown> | undefined {
72
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
73
+ }
74
+ function requiredString(frame: SdkFrame, field: string): string {
75
+ const value = frame[field];
76
+ if (typeof value !== "string") throw invalidFrame(`${field} must be a string.`);
77
+ return value;
78
+ }
79
+ function optionalString(frame: SdkFrame, field: string): string | undefined {
80
+ const value = frame[field];
81
+ if (value === undefined) return undefined;
82
+ if (typeof value !== "string") throw invalidFrame(`${field} must be a string.`);
83
+ return value;
84
+ }
85
+ function requireConnection(connectionId: string, frame: SdkFrame): void {
86
+ if (requiredString(frame, "connectionId") !== connectionId)
87
+ throw invalidFrame("connectionId does not match the transport connection.");
88
+ }
89
+ function has(frame: SdkFrame, field: string): boolean {
90
+ return Object.hasOwn(frame, field);
91
+ }
92
+
93
+ /** Adapter-based session host; bus wiring owns NotificationServer creation and transport framing. */
94
+ export class SessionSdkHost {
95
+ readonly events = new SessionEventStream();
96
+ readonly reverse: ReverseLeaseRuntime;
97
+ readonly #options: SessionSdkHostOptions;
98
+ #started = false;
99
+ #unsubscribe?: () => void;
100
+ #registration?: { writer: BrokerIndexWriter; generation: number };
101
+
102
+ constructor(options: SessionSdkHostOptions) {
103
+ this.#options = options;
104
+ this.reverse = new ReverseLeaseRuntime({
105
+ sendFrame: options.sendFrame,
106
+ installDefinitions: options.installProviderDefinitions,
107
+ onDefinitionsRemoved: options.onProviderDefinitionsRemoved,
108
+ onCancel: options.onReverseCancel,
109
+ });
110
+ }
111
+
112
+ get started(): boolean {
113
+ return this.#started;
114
+ }
115
+ get generation(): number {
116
+ return this.events.generation;
117
+ }
118
+ /** Current installed definitions for a live provider capability. */
119
+ getProviderDefinitions(capability: string): unknown | undefined {
120
+ return this.reverse.getInstalledDefinitions(capability);
121
+ }
122
+ /** Release reverse leases after the transport reports a WebSocket disconnect. */
123
+ handleDisconnect(connectionId: string): void {
124
+ this.reverse.disconnect(connectionId);
125
+ }
126
+
127
+ /** Adds an event to the resumable event ring. Transport delivery is owned by bus wiring. */
128
+ emitEvent(frame: SdkFrame): EventFrame {
129
+ return this.events.emit(frame);
130
+ }
131
+
132
+ async start(): Promise<"started" | "already"> {
133
+ if (this.#started) return "already";
134
+ this.events.restart();
135
+ this.emitEvent({ name: "session_ready", sessionId: this.#options.sessionId, generation: this.events.generation });
136
+ const disposer = this.#options.onFrame((connectionId, frame) => {
137
+ void this.#onFrame(connectionId, frame);
138
+ });
139
+ this.#unsubscribe = typeof disposer === "function" ? disposer : undefined;
140
+ this.#started = true;
141
+ if (this.#registration)
142
+ await this.#registration.writer.register({
143
+ sessionId: this.#options.sessionId,
144
+ stateRoot: this.#options.stateRoot,
145
+ endpointGeneration: this.events.generation,
146
+ });
147
+ return "started";
148
+ }
149
+
150
+ async stop(): Promise<"stopped" | "already"> {
151
+ if (!this.#started) return "already";
152
+ this.#unsubscribe?.();
153
+ this.#unsubscribe = undefined;
154
+ this.#started = false;
155
+ if (this.#registration?.writer.unregister)
156
+ await this.#registration.writer.unregister({
157
+ sessionId: this.#options.sessionId,
158
+ stateRoot: this.#options.stateRoot,
159
+ endpointGeneration: this.events.generation,
160
+ });
161
+ return "stopped";
162
+ }
163
+
164
+ async registerWithBroker(writer: BrokerIndexWriter): Promise<void> {
165
+ this.#registration = { writer, generation: this.events.generation };
166
+ if (this.#started)
167
+ await writer.register({
168
+ sessionId: this.#options.sessionId,
169
+ stateRoot: this.#options.stateRoot,
170
+ endpointGeneration: this.events.generation,
171
+ });
172
+ }
173
+
174
+ async #send(connectionId: string, frame: SdkFrame): Promise<void> {
175
+ await this.#options.sendFrame(connectionId, frame);
176
+ }
177
+
178
+ async #onFrame(connectionId: string, frame: SdkFrame): Promise<void> {
179
+ try {
180
+ switch (frame.type) {
181
+ case "control_request": {
182
+ this.#observeRequest("control", connectionId, frame);
183
+ const result = await this.#options.control?.(connectionId, frame);
184
+ if (result !== undefined) {
185
+ const response = { type: "control_response", ...(result as SdkFrame) };
186
+ await this.#send(connectionId, response);
187
+ await this.#options.afterControlResponse?.(connectionId, frame, response);
188
+ }
189
+ break;
190
+ }
191
+ case "event_replay": {
192
+ const id = requiredString(frame, "id");
193
+ const rawGeneration =
194
+ frame.sinceGeneration === undefined ? this.events.generation : frame.sinceGeneration;
195
+ const rawSeq = frame.sinceSeq === undefined ? 0 : frame.sinceSeq;
196
+ if (typeof rawGeneration !== "number" || !Number.isSafeInteger(rawGeneration) || rawGeneration < 0)
197
+ throw invalidFrame("sinceGeneration must be a non-negative integer.");
198
+ if (typeof rawSeq !== "number" || !Number.isSafeInteger(rawSeq) || rawSeq < 0)
199
+ throw invalidFrame("sinceSeq must be a non-negative integer.");
200
+ const sinceGeneration = rawGeneration;
201
+ const sinceSeq = rawSeq;
202
+ const replay = this.events.replay(sinceSeq, sinceGeneration);
203
+ await this.#send(connectionId, {
204
+ type: "event_replay_result",
205
+ id,
206
+ ok: true,
207
+ ...replay,
208
+ generation: this.events.generation,
209
+ lastSeq: this.events.sequence,
210
+ });
211
+ break;
212
+ }
213
+ case "query_request": {
214
+ this.#observeRequest("query", connectionId, frame);
215
+ const result = await this.#options.query?.(connectionId, frame);
216
+ if (result !== undefined)
217
+ await this.#send(connectionId, { type: "query_response", ...(result as SdkFrame) });
218
+ break;
219
+ }
220
+ case "register_provider": {
221
+ requiredString(frame, "id");
222
+ requireConnection(connectionId, frame);
223
+ const capability = requiredString(frame, "capability");
224
+ if (!has(frame, "definitions")) throw invalidFrame("definitions is required.");
225
+ const lease = this.reverse.registerProvider(
226
+ connectionId,
227
+ capability,
228
+ frame.definitions,
229
+ optionalString(frame, "expectedLeaseId"),
230
+ optionalString(frame, "idempotencyKey"),
231
+ );
232
+ await this.#send(connectionId, {
233
+ id: frame.id,
234
+ type: "register_provider_result",
235
+ leaseId: lease.leaseId,
236
+ leaseExpiresAt: new Date(lease.expiresAt).toISOString(),
237
+ registeredNames: registeredNames(frame.definitions),
238
+ });
239
+ break;
240
+ }
241
+ case "provider_heartbeat": {
242
+ requireConnection(connectionId, frame);
243
+ const lease = this.reverse.heartbeat(connectionId, requiredString(frame, "leaseId"));
244
+ await this.#send(connectionId, leaseState(undefined, lease));
245
+ break;
246
+ }
247
+ case "lease_release": {
248
+ requireConnection(connectionId, frame);
249
+ const handoffTo = optionalString(frame, "handoffTo");
250
+ const lease = this.reverse.release(connectionId, requiredString(frame, "leaseId"), handoffTo);
251
+ await this.#send(connectionId, leaseState(undefined, lease));
252
+ break;
253
+ }
254
+ case "reverse_response": {
255
+ const id = requiredString(frame, "id");
256
+ requireConnection(connectionId, frame);
257
+ const leaseId = requiredString(frame, "leaseId");
258
+ if (typeof frame.ok !== "boolean") throw invalidFrame("ok must be a boolean.");
259
+ const responseError = record(frame.error);
260
+ if (frame.ok) {
261
+ if (!has(frame, "result") || has(frame, "error"))
262
+ throw invalidFrame("Successful reverse responses require result and no error.");
263
+ this.reverse.respond(connectionId, id, leaseId, frame.result);
264
+ } else {
265
+ if (
266
+ has(frame, "result") ||
267
+ !responseError ||
268
+ typeof responseError.code !== "string" ||
269
+ typeof responseError.message !== "string"
270
+ )
271
+ throw invalidFrame("Failed reverse responses require a structured error and no result.");
272
+ this.reverse.respond(connectionId, id, leaseId, undefined, {
273
+ code: responseError.code,
274
+ message: responseError.message,
275
+ });
276
+ }
277
+ break;
278
+ }
279
+ default:
280
+ // Unknown/future frame types are tolerated silently per the v3
281
+ // forward-compatibility contract; only malformed frames of KNOWN
282
+ // types produce structured errors (thrown above).
283
+ return;
284
+ }
285
+ } catch (error) {
286
+ await this.#send(connectionId, errorFrame(connectionId, frame, error));
287
+ }
288
+ }
289
+ #observeRequest(kind: "control" | "query", connectionId: string, frame: SdkFrame): void {
290
+ try {
291
+ this.#options.onRequest?.(kind, connectionId, frame);
292
+ } catch {
293
+ // Diagnostic observers must not change request handling.
294
+ }
295
+ }
296
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./events";
2
+ export * from "./host";
3
+ export * from "./reverse-leases";
4
+ export * from "./types";
@@ -0,0 +1,189 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ import { MAX_PINNED_REVISIONS, type RevisionStore, RevisionStoreError, SNAPSHOT_TTL_MS } from "./revision-store.js";
3
+
4
+ export const CURSOR_TTL_MS = SNAPSHOT_TTL_MS;
5
+ export const MAX_CURSORS_PER_CONNECTION = 32;
6
+ export const MAX_CURSORS_PER_SESSION = MAX_PINNED_REVISIONS;
7
+
8
+ export interface CursorEnvelope {
9
+ cursorVersion: 1;
10
+ protocolMajor: 3;
11
+ sessionId: string;
12
+ resource: string;
13
+ revision: string;
14
+ highWatermark?: unknown;
15
+ position: unknown;
16
+ direction: string;
17
+ pageShape: unknown;
18
+ }
19
+
20
+ export interface CursorSelector {
21
+ queryId: string;
22
+ entryId?: string;
23
+ field?: string;
24
+ fileId?: string;
25
+ hunkId?: string;
26
+ resourceKind?: string;
27
+ resourceId?: string;
28
+ itemId?: string;
29
+ }
30
+
31
+ export interface CursorPosition {
32
+ offset?: number;
33
+ byteOffset?: number;
34
+ selector?: CursorSelector;
35
+ }
36
+
37
+ export function cursorSelector(position: unknown): CursorSelector | undefined {
38
+ if (!position || typeof position !== "object") return undefined;
39
+ const selector = (position as CursorPosition).selector;
40
+ return selector && typeof selector.queryId === "string" ? selector : undefined;
41
+ }
42
+
43
+ export function assertCursorSelector(
44
+ cursor: CursorSelector | undefined,
45
+ requested: Partial<CursorSelector>,
46
+ ): CursorSelector {
47
+ if (!cursor) throw new CursorError("invalid_input", false, "cursor does not match query");
48
+ for (const [key, value] of Object.entries(requested)) {
49
+ if (value !== undefined && cursor[key as keyof CursorSelector] !== value)
50
+ throw new CursorError("invalid_input", false, "cursor does not match query");
51
+ }
52
+ return cursor;
53
+ }
54
+
55
+ export interface CursorQueryContext {
56
+ sessionId: string;
57
+ resource?: string;
58
+ resourceId?: string;
59
+ direction: string;
60
+ pageShape: unknown;
61
+ }
62
+
63
+ export class CursorError extends Error {
64
+ constructor(
65
+ readonly code:
66
+ | "cursor_expired"
67
+ | "invalid_cursor"
68
+ | "invalid_input"
69
+ | "resource_gone"
70
+ | "snapshot_capacity_exceeded",
71
+ readonly restartQuery = code === "cursor_expired",
72
+ message: string = code,
73
+ ) {
74
+ super(message);
75
+ }
76
+ }
77
+
78
+ function canonicalise(value: unknown): unknown {
79
+ if (Array.isArray(value)) return value.map(canonicalise);
80
+ if (value && typeof value === "object")
81
+ return Object.fromEntries(
82
+ Object.entries(value)
83
+ .sort(([a], [b]) => a.localeCompare(b))
84
+ .map(([key, entry]) => [key, canonicalise(entry)]),
85
+ );
86
+ return value;
87
+ }
88
+
89
+ function canonicalJson(value: unknown): string {
90
+ return JSON.stringify(canonicalise(value));
91
+ }
92
+ export function canonicalCursorJson(envelope: CursorEnvelope): string {
93
+ return canonicalJson(envelope);
94
+ }
95
+ export function cursorMac(envelope: CursorEnvelope, sessionToken: string | Uint8Array): string {
96
+ return createHmac("sha256", sessionToken).update(canonicalCursorJson(envelope)).digest("hex");
97
+ }
98
+ export function signCursor(envelope: CursorEnvelope, sessionToken: string | Uint8Array): string {
99
+ return JSON.stringify({ envelope, mac: cursorMac(envelope, sessionToken) });
100
+ }
101
+ export function verifyCursor(cursor: string, sessionToken: string | Uint8Array): CursorEnvelope | undefined {
102
+ try {
103
+ const signed = JSON.parse(cursor) as { envelope: CursorEnvelope; mac: string };
104
+ const expected = Buffer.from(cursorMac(signed.envelope, sessionToken), "hex");
105
+ const actual = Buffer.from(signed.mac, "hex");
106
+ if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) return undefined;
107
+ const envelope = signed.envelope;
108
+ return envelope.cursorVersion === 1 && envelope.protocolMajor === 3 ? envelope : undefined;
109
+ } catch {
110
+ return undefined;
111
+ }
112
+ }
113
+
114
+ interface ActiveCursor {
115
+ connectionId: string;
116
+ resourceId: string;
117
+ expiresAt: number;
118
+ }
119
+
120
+ /** Tracks cursor budgets and binds snapshot pins to authenticated cursor strings. */
121
+ export class CursorRegistry {
122
+ readonly #active = new Map<string, ActiveCursor>();
123
+ constructor(
124
+ private readonly sessionToken: string | Uint8Array,
125
+ private readonly revisions: RevisionStore,
126
+ private readonly now: () => number = Date.now,
127
+ ) {}
128
+
129
+ async grant(
130
+ connectionId: string,
131
+ envelope: CursorEnvelope,
132
+ resourceKind: string,
133
+ resourceId: string,
134
+ ): Promise<string> {
135
+ this.sweep();
136
+ if (
137
+ this.#active.size >= MAX_CURSORS_PER_SESSION ||
138
+ [...this.#active.values()].filter(item => item.connectionId === connectionId).length >=
139
+ MAX_CURSORS_PER_CONNECTION
140
+ )
141
+ throw new CursorError("snapshot_capacity_exceeded", false);
142
+ const cursor = signCursor(envelope, this.sessionToken);
143
+ try {
144
+ await this.revisions.pin(cursor, resourceKind, resourceId, envelope.revision);
145
+ } catch (error) {
146
+ if (error instanceof RevisionStoreError) throw new CursorError(error.code, false);
147
+ throw error;
148
+ }
149
+ this.#active.set(cursor, { connectionId, resourceId, expiresAt: this.now() + CURSOR_TTL_MS });
150
+ return cursor;
151
+ }
152
+
153
+ consume(cursor: string, connectionId: string, expected: CursorQueryContext): CursorEnvelope {
154
+ const envelope = verifyCursor(cursor, this.sessionToken);
155
+ if (!envelope) throw new CursorError("invalid_cursor", false);
156
+ const active = this.#active.get(cursor);
157
+ if (!active || active.connectionId !== connectionId || active.expiresAt <= this.now()) {
158
+ this.release(cursor);
159
+ throw new CursorError("cursor_expired");
160
+ }
161
+ if (
162
+ envelope.sessionId !== expected.sessionId ||
163
+ (expected.resource !== undefined && envelope.resource !== expected.resource) ||
164
+ envelope.direction !== expected.direction ||
165
+ canonicalJson(envelope.pageShape) !== canonicalJson(expected.pageShape) ||
166
+ (expected.resourceId !== undefined && active.resourceId !== expected.resourceId)
167
+ ) {
168
+ throw new CursorError("invalid_input", false, "cursor does not match query");
169
+ }
170
+ this.release(cursor);
171
+ return envelope;
172
+ }
173
+
174
+ release(cursor: string): void {
175
+ this.#active.delete(cursor);
176
+ this.revisions.unpin(cursor);
177
+ }
178
+
179
+ close(): void {
180
+ for (const cursor of this.#active.keys()) this.release(cursor);
181
+ }
182
+ sweep(): void {
183
+ for (const [cursor, active] of this.#active) if (active.expiresAt <= this.now()) this.release(cursor);
184
+ this.revisions.sweep();
185
+ }
186
+ get size(): number {
187
+ return this.#active.size;
188
+ }
189
+ }