@gajae-code/coding-agent 0.13.3 → 0.14.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 (599) hide show
  1. package/CHANGELOG.md +184 -2
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +9 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +70 -6
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +1 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
  87. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  88. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  89. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  90. package/dist/types/modes/components/pet-capability.d.ts +11 -25
  91. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  92. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  93. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  94. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  95. package/dist/types/modes/interactive-mode.d.ts +1 -0
  96. package/dist/types/modes/types.d.ts +1 -0
  97. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  98. package/dist/types/runtime-mcp/config.d.ts +9 -0
  99. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  100. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  101. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  102. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  103. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  104. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  105. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  106. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  107. package/dist/types/runtime-mcp/types.d.ts +102 -10
  108. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  109. package/dist/types/sdk/broker/broker.d.ts +3 -55
  110. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  111. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  112. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  113. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  114. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  117. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  118. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  119. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  120. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  121. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  122. package/dist/types/sdk/bus/index.d.ts +85 -99
  123. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  124. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  125. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  126. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  127. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  128. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  129. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  130. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  131. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  132. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  133. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  135. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  136. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  137. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  138. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  139. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  140. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  141. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  142. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  143. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  144. package/dist/types/sdk/cli/rows.d.ts +2 -11
  145. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  146. package/dist/types/sdk/client/client.d.ts +97 -1
  147. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  148. package/dist/types/sdk/host/control/index.d.ts +1 -1
  149. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  150. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  151. package/dist/types/sdk/host/host.d.ts +15 -1
  152. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  153. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  154. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  155. package/dist/types/sdk/host/types.d.ts +1 -1
  156. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  157. package/dist/types/sdk/index.d.ts +2 -1
  158. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  159. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  160. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  161. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  162. package/dist/types/sdk/mcp/server.d.ts +9 -6
  163. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  164. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  165. package/dist/types/sdk/prompt-status.d.ts +10 -3
  166. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  167. package/dist/types/sdk/receipt-state.d.ts +17 -0
  168. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  169. package/dist/types/sdk/router/index.d.ts +1 -0
  170. package/dist/types/sdk/router/session-router.d.ts +152 -0
  171. package/dist/types/sdk/session-list.d.ts +22 -0
  172. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  173. package/dist/types/sdk/session.d.ts +34 -1
  174. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  175. package/dist/types/session/account-inventory.d.ts +86 -0
  176. package/dist/types/session/agent-session.d.ts +145 -6
  177. package/dist/types/session/agent-storage.d.ts +10 -0
  178. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  179. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  180. package/dist/types/session/messages.d.ts +1 -1
  181. package/dist/types/session/secure-token-file.d.ts +3 -0
  182. package/dist/types/session/session-manager.d.ts +40 -1
  183. package/dist/types/session/startup-auth-config.d.ts +44 -0
  184. package/dist/types/session/terminal-abort.d.ts +378 -0
  185. package/dist/types/session/yield-queue.d.ts +7 -0
  186. package/dist/types/session-import/claude.d.ts +26 -0
  187. package/dist/types/session-import/codex.d.ts +88 -0
  188. package/dist/types/session-import/command.d.ts +35 -0
  189. package/dist/types/session-import/detect.d.ts +22 -0
  190. package/dist/types/session-import/index.d.ts +7 -0
  191. package/dist/types/session-import/provider-codex.d.ts +22 -0
  192. package/dist/types/session-import/provider-service.d.ts +38 -0
  193. package/dist/types/session-import/redact.d.ts +23 -0
  194. package/dist/types/session-import/service.d.ts +47 -0
  195. package/dist/types/session-import/types.d.ts +120 -0
  196. package/dist/types/setup/paseo/check.d.ts +16 -0
  197. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  198. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  199. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  200. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  201. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  202. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  203. package/dist/types/setup/paseo/remove.d.ts +14 -0
  204. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  205. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  206. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  207. package/dist/types/skill-state/active-state.d.ts +5 -1
  208. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  209. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  210. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  211. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  212. package/dist/types/slash-commands/types.d.ts +7 -1
  213. package/dist/types/task/executor.d.ts +10 -0
  214. package/dist/types/task/index.d.ts +3 -1
  215. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  216. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  217. package/dist/types/tools/bash.d.ts +2 -6
  218. package/dist/types/tools/browser/attach.d.ts +11 -0
  219. package/dist/types/tools/browser/launch.d.ts +20 -0
  220. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  221. package/dist/types/tools/browser/render.d.ts +1 -0
  222. package/dist/types/tools/browser.d.ts +3 -1
  223. package/dist/types/tools/image-gen.d.ts +9 -2
  224. package/dist/types/tools/index.d.ts +8 -0
  225. package/dist/types/tools/monitor.d.ts +1 -1
  226. package/dist/types/tools/skill-discovery.d.ts +8 -1
  227. package/dist/types/tools/subagent.d.ts +2 -0
  228. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  229. package/dist/types/utils/herdr-pane.d.ts +76 -0
  230. package/package.json +19 -9
  231. package/scripts/build-binary.ts +2 -1
  232. package/scripts/build-sdk-package-smoke.ts +2 -13
  233. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  234. package/scripts/compile-args.ts +2 -0
  235. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  236. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  237. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  238. package/scripts/generate-tool-catalog.ts +6 -4
  239. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  240. package/src/async/job-manager.ts +352 -17
  241. package/src/async/support.ts +25 -0
  242. package/src/autoresearch/dashboard.ts +28 -13
  243. package/src/capability/index.ts +3 -1
  244. package/src/capability/mcp.ts +6 -0
  245. package/src/capability/types.ts +6 -0
  246. package/src/cli/accounts-cli.ts +395 -0
  247. package/src/cli/args.ts +23 -0
  248. package/src/cli/auth-broker-cli.ts +136 -66
  249. package/src/cli/auth-gateway-cli.ts +94 -70
  250. package/src/cli/config-cli.ts +27 -2
  251. package/src/cli/crash-cli.ts +78 -0
  252. package/src/cli/customize-doctor.ts +1605 -0
  253. package/src/cli/daemon-cli.ts +12 -3
  254. package/src/cli/fast-help.ts +1 -0
  255. package/src/cli/mcp-cli.ts +128 -60
  256. package/src/cli/notify-cli.ts +8 -14
  257. package/src/cli/root-flags.ts +8 -0
  258. package/src/cli/setup-cli.ts +145 -0
  259. package/src/cli/skills-cli.ts +38 -2
  260. package/src/cli/update-cli.ts +167 -12
  261. package/src/cli.ts +4 -0
  262. package/src/commands/accounts.ts +71 -0
  263. package/src/commands/crash.ts +35 -0
  264. package/src/commands/customize.ts +84 -0
  265. package/src/commands/daemon.ts +2 -2
  266. package/src/commands/deep-interview.ts +2 -0
  267. package/src/commands/launch.ts +1 -0
  268. package/src/commands/mcp.ts +12 -7
  269. package/src/commands/ralplan.ts +2 -0
  270. package/src/commands/sdk.ts +65 -32
  271. package/src/commands/setup.ts +5 -0
  272. package/src/commands/skills.ts +14 -4
  273. package/src/commands/ultragoal.ts +10 -1
  274. package/src/commands/update.ts +26 -15
  275. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  276. package/src/config/atomic-yaml-patch.ts +769 -29
  277. package/src/config/config-schema-version.ts +10 -0
  278. package/src/config/file-lock.ts +70 -1
  279. package/src/config/mcp-schema.json +52 -14
  280. package/src/config/model-profile-activation.ts +20 -1
  281. package/src/config/model-profiles.ts +29 -19
  282. package/src/config/model-registry.ts +132 -21
  283. package/src/config/models-config-schema.ts +1 -1
  284. package/src/config/settings-schema.ts +76 -6
  285. package/src/config/settings.ts +4038 -199
  286. package/src/config/skill-settings-defaults.ts +41 -3
  287. package/src/config/task-delegation.ts +87 -0
  288. package/src/config/update-channel.ts +23 -0
  289. package/src/coordinator-mcp/server.ts +377 -308
  290. package/src/crash/index-store.ts +723 -0
  291. package/src/crash/nudge.ts +89 -0
  292. package/src/crash/record-loader.ts +149 -0
  293. package/src/crash/report.ts +551 -0
  294. package/src/crash/sanitize.ts +129 -0
  295. package/src/cursor.ts +36 -10
  296. package/src/customization/import.ts +946 -0
  297. package/src/customization/index.ts +4 -0
  298. package/src/customization/inventory.ts +335 -0
  299. package/src/customization/mutations.ts +141 -0
  300. package/src/customization/types.ts +223 -0
  301. package/src/daemon/control-types.ts +8 -1
  302. package/src/daemon/operator-contract.ts +6 -6
  303. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  304. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  305. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  306. package/src/discovery/claude-hooks.ts +38 -0
  307. package/src/discovery/claude.ts +142 -209
  308. package/src/discovery/codex-hooks.ts +38 -0
  309. package/src/discovery/codex.ts +132 -198
  310. package/src/discovery/helpers.ts +8 -1
  311. package/src/discovery/index.ts +2 -0
  312. package/src/discovery/mcp-compat.ts +234 -0
  313. package/src/discovery/mcp-json.ts +6 -0
  314. package/src/edit/renderer.ts +40 -0
  315. package/src/edit/streaming.ts +4 -2
  316. package/src/exec/bash-executor.ts +24 -20
  317. package/src/export/html/index.ts +51 -2
  318. package/src/extensibility/extensions/loader.ts +9 -1
  319. package/src/extensibility/extensions/runner.ts +17 -4
  320. package/src/extensibility/extensions/types.ts +31 -6
  321. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  322. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  323. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  324. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  325. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  326. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  327. package/src/extensibility/hooks/loader.ts +154 -10
  328. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  329. package/src/extensibility/skill-management.ts +368 -0
  330. package/src/extensibility/skills.ts +52 -16
  331. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  332. package/src/gjc-runtime/launch-worktree.ts +56 -2
  333. package/src/gjc-runtime/linux-proc.ts +7 -1
  334. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  335. package/src/gjc-runtime/session-state-lock.ts +992 -0
  336. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  337. package/src/gjc-runtime/state-writer.ts +39 -14
  338. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  339. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  340. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  341. package/src/gjc-runtime/workflow-settings.ts +596 -0
  342. package/src/goals/tools/goal-tool.ts +6 -1
  343. package/src/hooks/events.ts +291 -0
  344. package/src/hooks/native-skill-hook.ts +4 -0
  345. package/src/hooks/normalize.ts +369 -0
  346. package/src/hooks/skill-state.ts +165 -46
  347. package/src/internal-urls/docs-index.generated.ts +40 -32
  348. package/src/main.ts +32 -5
  349. package/src/migrate/mcp-mapper.ts +25 -2
  350. package/src/modes/acp/abort-scope.ts +29 -0
  351. package/src/modes/acp/acp-agent.ts +423 -98
  352. package/src/modes/components/assistant-message.ts +6 -1
  353. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  354. package/src/modes/components/customization/import-wizard.ts +372 -0
  355. package/src/modes/components/customization/index.ts +1 -0
  356. package/src/modes/components/gajae-pet-widget.ts +539 -213
  357. package/src/modes/components/irc-sidebar.ts +11 -0
  358. package/src/modes/components/iterm-pet-transport.ts +585 -0
  359. package/src/modes/components/oauth-selector.ts +277 -86
  360. package/src/modes/components/pet-capability.ts +39 -40
  361. package/src/modes/components/settings-selector.ts +9 -0
  362. package/src/modes/components/status-line/gh.ts +45 -1
  363. package/src/modes/components/status-line/segments.ts +16 -1
  364. package/src/modes/components/tool-status-header.ts +13 -3
  365. package/src/modes/controllers/command-controller.ts +20 -8
  366. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  367. package/src/modes/controllers/input-controller.ts +10 -0
  368. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  369. package/src/modes/controllers/selector-controller.ts +177 -72
  370. package/src/modes/controllers/stt-controller.ts +3 -3
  371. package/src/modes/interactive-mode.ts +88 -16
  372. package/src/modes/print-mode.ts +35 -6
  373. package/src/modes/runtime-init.ts +21 -68
  374. package/src/modes/types.ts +1 -0
  375. package/src/modes/utils/injected-user-submission.ts +25 -0
  376. package/src/modes/utils/terminal-bell.ts +15 -1
  377. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  378. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  379. package/src/prompts/agents/critic.md +1 -1
  380. package/src/prompts/system/rlm-research.md +1 -1
  381. package/src/prompts/system/system-prompt.md +0 -2
  382. package/src/prompts/tools/bash.md +7 -1
  383. package/src/prompts/tools/browser.md +1 -1
  384. package/src/prompts/tools/cron.md +6 -6
  385. package/src/prompts/tools/read.md +0 -2
  386. package/src/prompts/tools/skill-discovery.md +4 -2
  387. package/src/runtime-mcp/client.ts +427 -23
  388. package/src/runtime-mcp/config.ts +37 -5
  389. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  390. package/src/runtime-mcp/manager.ts +52 -2
  391. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  392. package/src/runtime-mcp/oauth-flow.ts +46 -1
  393. package/src/runtime-mcp/pool-key.ts +5 -1
  394. package/src/runtime-mcp/pool.test.ts +9 -0
  395. package/src/runtime-mcp/protocol.test.ts +353 -0
  396. package/src/runtime-mcp/protocol.ts +624 -0
  397. package/src/runtime-mcp/scope-config.ts +78 -0
  398. package/src/runtime-mcp/tool-bridge.ts +44 -10
  399. package/src/runtime-mcp/transports/http.ts +84 -13
  400. package/src/runtime-mcp/types.ts +123 -12
  401. package/src/sdk/acp/adapter.ts +328 -97
  402. package/src/sdk/broker/broker.ts +160 -741
  403. package/src/sdk/broker/ensure.ts +9 -1
  404. package/src/sdk/broker/lifecycle.ts +510 -116
  405. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  406. package/src/sdk/broker/process-incarnation.ts +19 -0
  407. package/src/sdk/broker/session-index.ts +516 -117
  408. package/src/sdk/broker/startup-failure.ts +28 -5
  409. package/src/sdk/broker/transport.ts +1 -13
  410. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  411. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  412. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  413. package/src/sdk/bus/config.ts +13 -0
  414. package/src/sdk/bus/conversation-store.ts +59 -171
  415. package/src/sdk/bus/daemon-paths.ts +20 -0
  416. package/src/sdk/bus/discord-conversation.ts +2 -0
  417. package/src/sdk/bus/discord-daemon.ts +524 -126
  418. package/src/sdk/bus/discord-live-provider.ts +206 -79
  419. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  420. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  421. package/src/sdk/bus/index.ts +2480 -456
  422. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  423. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  424. package/src/sdk/bus/managed-daemon.ts +2 -145
  425. package/src/sdk/bus/notification-service.ts +196 -0
  426. package/src/sdk/bus/operator-runtime.ts +1 -1
  427. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  428. package/src/sdk/bus/reconciliation-store.ts +553 -24
  429. package/src/sdk/bus/session-control.ts +49 -3
  430. package/src/sdk/bus/slack-conversation.ts +4 -0
  431. package/src/sdk/bus/slack-daemon.ts +733 -134
  432. package/src/sdk/bus/slack-live-provider.ts +65 -15
  433. package/src/sdk/bus/slack-provider.ts +4 -0
  434. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  435. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  436. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  437. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  438. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  439. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  440. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  441. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  442. package/src/sdk/bus/telegram-reference.ts +1 -226
  443. package/src/sdk/bus/telegram-setup.ts +2 -2
  444. package/src/sdk/bus/threaded-render.ts +1 -0
  445. package/src/sdk/bus/topic-registry.ts +279 -146
  446. package/src/sdk/cli/rows.ts +2 -35
  447. package/src/sdk/cli/session-cli.ts +776 -700
  448. package/src/sdk/client/client.ts +892 -1
  449. package/src/sdk/host/control/dispatch.ts +93 -58
  450. package/src/sdk/host/control/index.ts +1 -1
  451. package/src/sdk/host/control/operations.ts +18 -10
  452. package/src/sdk/host/control/runtime-gate.ts +40 -0
  453. package/src/sdk/host/host.ts +63 -12
  454. package/src/sdk/host/query/handlers.ts +66 -7
  455. package/src/sdk/host/reverse-leases.ts +2 -2
  456. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  457. package/src/sdk/host/session-runtime.test.ts +2787 -34
  458. package/src/sdk/host/session-runtime.ts +1489 -101
  459. package/src/sdk/host/surface-policy.ts +12 -8
  460. package/src/sdk/host/types.ts +1 -1
  461. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  462. package/src/sdk/host/websocket-transport.ts +85 -50
  463. package/src/sdk/index.ts +2 -1
  464. package/src/sdk/lifecycle/broker-client.ts +53 -0
  465. package/src/sdk/lifecycle/client.ts +234 -0
  466. package/src/sdk/lifecycle/index.ts +3 -0
  467. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  468. package/src/sdk/lifecycle/service.ts +720 -0
  469. package/src/sdk/mcp/server.ts +226 -188
  470. package/src/sdk/prompt-deadline-lease.ts +55 -0
  471. package/src/sdk/prompt-deadline-manager.ts +148 -0
  472. package/src/sdk/prompt-status.ts +11 -3
  473. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  474. package/src/sdk/protocol/operation-registry.ts +71 -8
  475. package/src/sdk/receipt-state.ts +43 -0
  476. package/src/sdk/reconciliation-extensions.ts +14 -0
  477. package/src/sdk/router/index.ts +1 -0
  478. package/src/sdk/router/session-router.ts +1658 -0
  479. package/src/sdk/session-list.ts +93 -0
  480. package/src/sdk/session-reconnect.ts +45 -1
  481. package/src/sdk/session.ts +716 -120
  482. package/src/sdk/transport/serve-cli.ts +18 -34
  483. package/src/session/account-inventory.ts +603 -0
  484. package/src/session/agent-session.ts +2857 -582
  485. package/src/session/agent-storage.ts +35 -0
  486. package/src/session/blob-store.ts +32 -1
  487. package/src/session/coordinator-tool-label.ts +40 -0
  488. package/src/session/internal/managed-session-scope.ts +4 -0
  489. package/src/session/internal/managed-session-storage.ts +173 -12
  490. package/src/session/messages.ts +1 -1
  491. package/src/session/secure-token-file.ts +80 -0
  492. package/src/session/session-manager.ts +730 -58
  493. package/src/session/session-storage.ts +25 -16
  494. package/src/session/startup-auth-config.ts +297 -0
  495. package/src/session/terminal-abort.ts +1253 -0
  496. package/src/session/yield-queue.ts +57 -15
  497. package/src/session-import/claude.ts +334 -0
  498. package/src/session-import/codex.ts +1033 -0
  499. package/src/session-import/command.ts +77 -0
  500. package/src/session-import/detect.ts +133 -0
  501. package/src/session-import/index.ts +7 -0
  502. package/src/session-import/provider-codex.ts +327 -0
  503. package/src/session-import/provider-service.ts +457 -0
  504. package/src/session-import/redact.ts +128 -0
  505. package/src/session-import/service.ts +1264 -0
  506. package/src/session-import/types.ts +148 -0
  507. package/src/setup/paseo/check.ts +213 -0
  508. package/src/setup/paseo/install-saga.ts +293 -0
  509. package/src/setup/paseo/json-publisher.ts +263 -0
  510. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  511. package/src/setup/paseo/paseo-ownership.ts +224 -0
  512. package/src/setup/paseo/paseo-setup.ts +253 -0
  513. package/src/setup/paseo/provider-config.ts +139 -0
  514. package/src/setup/paseo/remove.ts +183 -0
  515. package/src/setup/paseo/result-types.ts +79 -0
  516. package/src/setup/paseo/setup-deps.ts +175 -0
  517. package/src/setup/paseo/skills-bridge.ts +260 -0
  518. package/src/skill-state/active-state.ts +21 -11
  519. package/src/slash-commands/acp-builtins.ts +9 -1
  520. package/src/slash-commands/builtin-registry.ts +98 -16
  521. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  522. package/src/slash-commands/helpers/mcp.ts +9 -3
  523. package/src/slash-commands/helpers/usage-report.ts +61 -56
  524. package/src/slash-commands/types.ts +7 -2
  525. package/src/task/executor.ts +30 -11
  526. package/src/task/index.ts +66 -7
  527. package/src/task/render.ts +23 -9
  528. package/src/tools/ask-answer-registry.ts +23 -7
  529. package/src/tools/ask.ts +8 -4
  530. package/src/tools/bash-sleep-advisory.ts +115 -0
  531. package/src/tools/bash.ts +98 -18
  532. package/src/tools/browser/attach.ts +71 -3
  533. package/src/tools/browser/launch.ts +86 -13
  534. package/src/tools/browser/profile-discovery.ts +57 -7
  535. package/src/tools/browser/render.ts +8 -1
  536. package/src/tools/browser.ts +130 -21
  537. package/src/tools/computer.enforcement.test.ts +1 -1
  538. package/src/tools/computer.ts +3 -2
  539. package/src/tools/cron.ts +12 -1
  540. package/src/tools/image-gen.ts +27 -16
  541. package/src/tools/index.ts +8 -0
  542. package/src/tools/job.ts +33 -6
  543. package/src/tools/monitor.ts +41 -2
  544. package/src/tools/skill-discovery.ts +21 -29
  545. package/src/tools/subagent.ts +142 -24
  546. package/src/tools/tool-catalog.generated.ts +6 -6
  547. package/src/tools/ultragoal-ask-guard.ts +10 -3
  548. package/src/utils/herdr-pane.ts +345 -0
  549. package/src/utils/title-generator.ts +2 -0
  550. package/src/web/scrapers/index.ts +6 -4
  551. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  552. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  553. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  554. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  555. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  556. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  557. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  558. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  559. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  560. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  561. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  562. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  563. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  564. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  565. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  566. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  567. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  568. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  569. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  570. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  571. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  572. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  573. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  574. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  575. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  576. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  577. package/dist/types/session/auth-broker-config.d.ts +0 -13
  578. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  579. package/dist/types/skills/skills.test.d.ts +0 -1
  580. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  581. package/dist/types/task/token-log.test.d.ts +0 -1
  582. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  583. package/dist/types/tools/descriptors.test.d.ts +0 -1
  584. package/scripts/g005-daemon-smoke.ts +0 -180
  585. package/scripts/g011-daemon-path-smoke.ts +0 -153
  586. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  587. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  588. package/src/sdk/bus/telegram-cli.ts +0 -212
  589. package/src/sdk/elevation/allowlist.ts +0 -54
  590. package/src/sdk/elevation/audit.ts +0 -208
  591. package/src/sdk/elevation/capability.ts +0 -41
  592. package/src/sdk/elevation/digest.ts +0 -129
  593. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  594. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  595. package/src/sdk/elevation/owner.ts +0 -109
  596. package/src/session/auth-broker-config.ts +0 -109
  597. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  598. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  599. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -1 +1,892 @@
1
- export * from "@gajae-code/bridge-client";
1
+ import { createHash, randomUUID } from "node:crypto";
2
+
3
+ export type SdkErrorCode =
4
+ | "invalid_input"
5
+ | "unknown_operation"
6
+ | "not_found"
7
+ | "unavailable"
8
+ | "timeout"
9
+ | "connection_closed"
10
+ | "uncertain_after_send"
11
+ | "endpoint_credential_forbidden"
12
+ | (string & {});
13
+
14
+ export class SdkClientError extends Error {
15
+ readonly code: SdkErrorCode;
16
+ readonly details: unknown;
17
+ /**
18
+ * Reconnect-cycle diagnostics, separate from `details` because `details` is an
19
+ * established contract: callers read the terminating transport error straight off
20
+ * it (`session-cli.ts` matches `details.code` against `ENOENT`/`ECONNREFUSED`, and
21
+ * lifecycle callers cast it to a sent record). Wrapping that value would silently
22
+ * break every such reader, so the new attribution rides alongside it instead.
23
+ */
24
+ readonly reconnect?: SdkReconnectExhaustedDetails;
25
+ constructor(code: SdkErrorCode, message: string, details?: unknown, reconnect?: SdkReconnectExhaustedDetails) {
26
+ super(message);
27
+ this.name = "SdkClientError";
28
+ this.code = code;
29
+ this.details = details;
30
+ if (reconnect) this.reconnect = reconnect;
31
+ }
32
+ }
33
+
34
+ export type SdkReconnectTerminationReason = "attempts_exhausted" | "deadline" | "cancelled";
35
+
36
+ /**
37
+ * Reconnect-cycle termination diagnostics. `attemptsConsumed` counts retry slots,
38
+ * not socket opens: the initial open is free, so the loop can open one more socket
39
+ * than `attemptBudget`. Both values are therefore directly comparable.
40
+ *
41
+ * `reason` is authoritative. `attemptsConsumed < attemptBudget` is corroborating
42
+ * evidence of truncation, not a classifier: a zero-attempt client that trips its
43
+ * deadline immediately reports `0 === 0` and is still deadline-terminated.
44
+ */
45
+ export interface SdkReconnectExhaustedDetails {
46
+ readonly attemptsConsumed: number;
47
+ readonly attemptBudget: number;
48
+ readonly elapsedMs: number;
49
+ readonly reason: SdkReconnectTerminationReason;
50
+ }
51
+
52
+ /**
53
+ * Deadline a one-shot request client waits for a reply. Long-lived session
54
+ * clients override it per request; see `SESSION_REQUEST_TIMEOUT_MS`.
55
+ */
56
+ export const DEFAULT_SDK_REQUEST_TIMEOUT_MS = 10_000;
57
+
58
+ export interface SdkClientOptions {
59
+ timeoutMs?: number;
60
+ /** Absolute wall-clock deadline shared by connect, hello, retry, and request work. */
61
+ deadline?: number;
62
+
63
+ reconnectAttempts?: number;
64
+ reconnectBackoffMs?: number;
65
+ /**
66
+ * Per-attempt ceiling for the exponential reconnect backoff. A long reconnect
67
+ * budget must keep probing frequently instead of sleeping for tens of seconds
68
+ * on its last attempts. Defaults to 2s.
69
+ */
70
+ reconnectMaxBackoffMs?: number;
71
+ }
72
+
73
+ export interface SdkRequestOptions {
74
+ timeoutMs?: number;
75
+ idempotencyKey?: string;
76
+ confirm?: boolean;
77
+ }
78
+
79
+ export type SdkFrame = Record<string, unknown>;
80
+
81
+ /** Request identity retained after an uncertain send for lifecycle reconciliation. */
82
+
83
+ export interface SdkSentRecord {
84
+ readonly id: string;
85
+ readonly operation?: string;
86
+ readonly idempotencyKey?: string;
87
+ readonly fingerprint: string;
88
+ }
89
+
90
+ export type SdkFrameHandler = (frame: SdkFrame) => void;
91
+ export type SdkReconnectHandler = () => void;
92
+ export type SdkReconnectFailedHandler = (error: SdkClientError) => void;
93
+
94
+ type Frame = SdkFrame;
95
+ type Cycle = {
96
+ readonly generation: number;
97
+ phase: "opening" | "backoff" | "complete" | "aborted";
98
+ candidate: Incarnation | null;
99
+ promise?: Promise<Incarnation>;
100
+ backoffTimer?: NodeJS.Timeout;
101
+ rejectBackoff?: (error: Error) => void;
102
+ };
103
+ type Incarnation = {
104
+ readonly generation: number;
105
+ readonly cycle: Cycle;
106
+ readonly socket: WebSocket;
107
+ phase: "opening" | "hello" | "active" | "retired";
108
+ tornDown: boolean;
109
+ openTimer?: NodeJS.Timeout;
110
+ failure?: Error;
111
+ helloTimer?: NodeJS.Timeout;
112
+ /** Hello frames that arrived before the open handler advanced phase to "hello". */
113
+ earlyHello?: Frame;
114
+ resolveOpen?: () => void;
115
+ rejectOpen?: (error: Error) => void;
116
+ resolveHello?: () => void;
117
+ rejectHello?: (error: Error) => void;
118
+ listeners: Array<["open" | "error" | "close" | "message", EventListener]>;
119
+ };
120
+ type Pending = {
121
+ readonly incarnation: Incarnation;
122
+ resolve: (value: unknown) => void;
123
+ reject: (error: Error) => void;
124
+ timer: NodeJS.Timeout;
125
+ sent: boolean;
126
+ onResponse?: () => void;
127
+ };
128
+
129
+ /**
130
+ * Transport facts attached to a request timeout before an outcome is known.
131
+ * A timeout after send is surfaced as `uncertain_after_send` with an
132
+ * {@link SdkSentRecord} in its details.
133
+ */
134
+ export interface SdkRequestTimeoutDetails {
135
+ requestId: string;
136
+ requestSent: boolean;
137
+ }
138
+
139
+ function errorFrom(frame: Frame): SdkClientError {
140
+ const error = frame.error;
141
+ if (error && typeof error === "object") {
142
+ const detail = error as { code?: unknown; message?: unknown };
143
+ return new SdkClientError(
144
+ typeof detail.code === "string" ? detail.code : "unavailable",
145
+ typeof detail.message === "string" ? detail.message : "SDK request failed",
146
+ error,
147
+ );
148
+ }
149
+ return new SdkClientError("unavailable", "SDK request failed", error);
150
+ }
151
+
152
+ function parseFrame(value: unknown): Frame {
153
+ try {
154
+ const frame = JSON.parse(String(value));
155
+ if (frame && typeof frame === "object" && !Array.isArray(frame)) return frame as Frame;
156
+ } catch (error) {
157
+ throw new SdkClientError("protocol_error", "SDK server sent malformed JSON.", error);
158
+ }
159
+ throw new SdkClientError("protocol_error", "SDK server sent a malformed frame.");
160
+ }
161
+
162
+ function lifecycleFingerprint(operation: string, input: unknown): string {
163
+ return createHash("sha256").update(JSON.stringify({ operation, input })).digest("hex");
164
+ }
165
+
166
+ function inputFingerprint(input: unknown): string {
167
+ return createHash("sha256").update(JSON.stringify(input)).digest("hex");
168
+ }
169
+
170
+ /** A transport-only v3 SDK WebSocket client with no host or session authority. */
171
+ export class SdkClient {
172
+ readonly #url: string;
173
+ readonly #token: string;
174
+ readonly #timeoutMs: number;
175
+ readonly #reconnectAttempts: number;
176
+ readonly #reconnectBackoffMs: number;
177
+ readonly #reconnectMaxBackoffMs: number;
178
+ /**
179
+ * Bounded grace for best-effort transport close, independent of the request
180
+ * deadline. Close teardown must never be gated by an already-elapsed operation
181
+ * deadline, or the socket leaks.
182
+ */
183
+ readonly #closeGraceMs: number;
184
+ readonly #deadline?: number;
185
+ #currentSocketRecord: Incarnation | null = null;
186
+ #opening: Cycle | null = null;
187
+ #cycleGeneration = 0;
188
+ #incarnationGeneration = 0;
189
+ #pending = new Map<string, Pending>();
190
+ #sentRecords = new Map<string, SdkSentRecord>();
191
+ #frameHandlers = new Set<SdkFrameHandler>();
192
+ #reconnectHandlers = new Set<SdkReconnectHandler>();
193
+ #reconnectFailedHandlers = new Set<SdkReconnectFailedHandler>();
194
+ #closePromise: Promise<void> | undefined;
195
+
196
+ #closed = false;
197
+ connectionId?: string;
198
+
199
+ constructor(url: string, token: string, options: SdkClientOptions = {}) {
200
+ this.#url = url;
201
+ this.#token = token;
202
+ this.#timeoutMs = options.timeoutMs ?? DEFAULT_SDK_REQUEST_TIMEOUT_MS;
203
+ this.#closeGraceMs = Math.max(1, Math.min(this.#timeoutMs, 1_000));
204
+ this.#deadline =
205
+ typeof options.deadline === "number" && Number.isFinite(options.deadline) ? options.deadline : undefined;
206
+
207
+ this.#reconnectAttempts = options.reconnectAttempts ?? 3;
208
+ this.#reconnectBackoffMs = options.reconnectBackoffMs ?? 25;
209
+ this.#reconnectMaxBackoffMs = Math.max(this.#reconnectBackoffMs, options.reconnectMaxBackoffMs ?? 2_000);
210
+ }
211
+
212
+ static async connect(url: string, token: string, options: SdkClientOptions = {}): Promise<SdkClient> {
213
+ const client = new SdkClient(url, token, options);
214
+ await client.connect();
215
+ return client;
216
+ }
217
+
218
+ async connect(): Promise<void> {
219
+ await this.#connect();
220
+ }
221
+
222
+ /** Resolves once the current WebSocket has received its server hello frame. */
223
+ async awaitHello(): Promise<void> {
224
+ await this.#connect();
225
+ }
226
+
227
+ onFrame(handler: SdkFrameHandler): () => void {
228
+ this.#frameHandlers.add(handler);
229
+ return () => this.#frameHandlers.delete(handler);
230
+ }
231
+
232
+ onReconnect(handler: SdkReconnectHandler): () => void {
233
+ this.#reconnectHandlers.add(handler);
234
+ return () => this.#reconnectHandlers.delete(handler);
235
+ }
236
+
237
+ onReconnectFailed(handler: SdkReconnectFailedHandler): () => void {
238
+ this.#reconnectFailedHandlers.add(handler);
239
+ return () => this.#reconnectFailedHandlers.delete(handler);
240
+ }
241
+
242
+ send(frame: SdkFrame): void {
243
+ if (this.#closed) throw new SdkClientError("connection_closed", "SDK client closed");
244
+ this.#throwIfDeadlineElapsed();
245
+ const current = this.#currentSocketRecord ?? this.#opening?.candidate;
246
+ const authoritative =
247
+ this.#isActive(current ?? null) ||
248
+ (!!current && current.phase === "hello" && this.#isCandidate(current.cycle, current));
249
+ if (!current || !authoritative || current.socket.readyState !== WebSocket.OPEN)
250
+ throw new SdkClientError("connection_closed", "SDK WebSocket is not connected");
251
+ try {
252
+ current.socket.send(JSON.stringify(frame));
253
+ } catch (error) {
254
+ throw new SdkClientError("unavailable", "SDK WebSocket send failed", error);
255
+ }
256
+ }
257
+
258
+ request(frame: SdkFrame, timeout?: number | { timeoutMs?: number; idempotencyKey?: string }): Promise<SdkFrame> {
259
+ const options = typeof timeout === "number" ? { timeoutMs: timeout } : (timeout ?? {});
260
+ return this.#request(frame, options) as Promise<SdkFrame>;
261
+ }
262
+
263
+ close(): Promise<void> {
264
+ this.#closePromise ??= this.#close();
265
+ return this.#closePromise;
266
+ }
267
+ async #close(): Promise<void> {
268
+ this.#closed = true;
269
+ const transports = new Set<Incarnation>();
270
+ const cycle = this.#opening;
271
+ if (cycle) {
272
+ cycle.phase = "aborted";
273
+ if (cycle.backoffTimer) clearTimeout(cycle.backoffTimer);
274
+ if (cycle.candidate) {
275
+ transports.add(cycle.candidate);
276
+ this.#retire(cycle.candidate, new SdkClientError("connection_closed", "SDK client closed"), false);
277
+ }
278
+ cycle.rejectBackoff?.(new SdkClientError("connection_closed", "SDK client closed"));
279
+ cycle.rejectBackoff = undefined;
280
+ if (this.#opening === cycle) this.#opening = null;
281
+ }
282
+ const current = this.#currentSocketRecord;
283
+ if (current) {
284
+ transports.add(current);
285
+ this.#retire(current, new SdkClientError("connection_closed", "SDK client closed"), false, true);
286
+ }
287
+ for (const [id, pending] of this.#pending)
288
+ this.#settlePending(id, pending, new SdkClientError("connection_closed", "SDK client closed"), true);
289
+ try {
290
+ await Promise.all([...transports].map(incarnation => this.#closeTransport(incarnation)));
291
+ } finally {
292
+ this.#sentRecords.clear();
293
+ }
294
+ }
295
+
296
+ async control(
297
+ operation: string,
298
+ input: Record<string, unknown> = {},
299
+ options: SdkRequestOptions = {},
300
+ ): Promise<unknown> {
301
+ return await this.#request(
302
+ {
303
+ type: "control_request",
304
+ operation,
305
+ input,
306
+ ...(options.confirm === undefined ? {} : { confirm: options.confirm }),
307
+ },
308
+ options,
309
+ );
310
+ }
311
+
312
+ async query(
313
+ query: string,
314
+ input: Record<string, unknown> = {},
315
+ cursor?: string,
316
+ options: SdkRequestOptions = {},
317
+ ): Promise<unknown> {
318
+ return await this.#request(
319
+ { type: "query_request", query, input, ...(cursor === undefined ? {} : { cursor }) },
320
+ options,
321
+ );
322
+ }
323
+
324
+ async global(
325
+ operation: string,
326
+ input: Record<string, unknown> = {},
327
+ options: SdkRequestOptions = {},
328
+ ): Promise<unknown> {
329
+ return await this.#request({ type: "broker_request", operation, input }, options);
330
+ }
331
+
332
+ getSentRecord(id: string): SdkSentRecord | undefined {
333
+ return this.#sentRecords.get(id);
334
+ }
335
+ #rememberSentRecord(record: SdkSentRecord): void {
336
+ this.#sentRecords.set(record.id, record);
337
+ while (this.#sentRecords.size > 256) {
338
+ const oldest = this.#sentRecords.keys().next().value;
339
+ if (oldest === undefined) return;
340
+ this.#sentRecords.delete(oldest);
341
+ }
342
+ }
343
+ async lookupLifecycle(record: SdkSentRecord, timeoutMs?: number): Promise<unknown> {
344
+ if (!record.operation || !record.idempotencyKey || !record.fingerprint)
345
+ throw new SdkClientError(
346
+ "invalid_input",
347
+ "A lifecycle sent record requires operation, idempotencyKey, and fingerprint.",
348
+ );
349
+ return await this.#request(
350
+ {
351
+ type: "broker_request",
352
+ operation: "broker.lookup_lifecycle",
353
+ input: { operation: record.operation, fingerprint: record.fingerprint },
354
+ },
355
+ { timeoutMs, idempotencyKey: record.idempotencyKey },
356
+ () => this.#sentRecords.delete(record.id),
357
+ );
358
+ }
359
+
360
+ async #request(frame: Frame, options: SdkRequestOptions, onResponse?: () => void): Promise<unknown> {
361
+ if (this.#closed) throw new SdkClientError("connection_closed", "SDK client closed");
362
+ this.#throwIfDeadlineElapsed();
363
+ const incarnation = await this.#connect();
364
+ const timeoutMs = this.#remainingTimeout(options.timeoutMs ?? this.#timeoutMs);
365
+ if (timeoutMs <= 0) throw this.#deadlineError();
366
+ const id = randomUUID();
367
+ const requestFrame = {
368
+ ...frame,
369
+ id,
370
+ ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
371
+ };
372
+ const serializedRequest = JSON.stringify(requestFrame);
373
+ const serializedFrame = JSON.parse(serializedRequest) as Frame;
374
+ const deferred = Promise.withResolvers<unknown>();
375
+ const pending: Pending = {
376
+ incarnation,
377
+ resolve: deferred.resolve,
378
+ reject: deferred.reject,
379
+ sent: false,
380
+ onResponse,
381
+ timer: setTimeout(
382
+ () =>
383
+ this.#settlePending(
384
+ id,
385
+ pending,
386
+ new SdkClientError("timeout", `SDK request timed out after ${timeoutMs}ms`, {
387
+ requestId: id,
388
+ requestSent: pending.sent,
389
+ } satisfies SdkRequestTimeoutDetails),
390
+ true,
391
+ ),
392
+ timeoutMs,
393
+ ),
394
+ };
395
+ this.#pending.set(id, pending);
396
+ if (!this.#isActive(incarnation) || incarnation.socket.readyState !== WebSocket.OPEN) {
397
+ this.#settlePending(id, pending, new SdkClientError("unavailable", "SDK WebSocket is not connected"));
398
+ return await deferred.promise;
399
+ }
400
+ try {
401
+ incarnation.socket.send(serializedRequest);
402
+ pending.sent = true;
403
+ this.#rememberSentRecord({
404
+ id,
405
+ operation: typeof serializedFrame.operation === "string" ? serializedFrame.operation : undefined,
406
+ idempotencyKey: options.idempotencyKey,
407
+ fingerprint:
408
+ typeof serializedFrame.operation === "string"
409
+ ? lifecycleFingerprint(serializedFrame.operation, serializedFrame.input ?? {})
410
+ : inputFingerprint(serializedFrame.input ?? {}),
411
+ });
412
+ } catch (error) {
413
+ this.#settlePending(
414
+ id,
415
+ pending,
416
+ error instanceof SdkClientError
417
+ ? error
418
+ : new SdkClientError("unavailable", "SDK WebSocket send failed", error),
419
+ );
420
+ }
421
+ return await deferred.promise;
422
+ }
423
+
424
+ #deadlineError(reconnect?: SdkReconnectExhaustedDetails): SdkClientError {
425
+ return new SdkClientError("timeout", "SDK client deadline elapsed.", undefined, reconnect);
426
+ }
427
+
428
+ #remainingTimeout(limit = this.#timeoutMs): number {
429
+ if (this.#deadline === undefined) return limit;
430
+ return Math.min(limit, Math.max(0, this.#deadline - Date.now()));
431
+ }
432
+
433
+ #throwIfDeadlineElapsed(): void {
434
+ if (this.#deadline !== undefined && Date.now() >= this.#deadline) throw this.#deadlineError();
435
+ }
436
+
437
+ async #connect(): Promise<Incarnation> {
438
+ this.#throwIfDeadlineElapsed();
439
+ const current = this.#currentSocketRecord;
440
+ if (current && this.#isActive(current) && current.socket.readyState === WebSocket.OPEN) return current;
441
+ if (current)
442
+ this.#retire(current, new SdkClientError("connection_closed", "SDK WebSocket connection closed"), true, true);
443
+
444
+ let cycle = this.#opening;
445
+ if (!cycle) {
446
+ cycle = { generation: ++this.#cycleGeneration, phase: "opening", candidate: null };
447
+ this.#opening = cycle;
448
+ cycle.promise = this.#openWithRetry(cycle);
449
+ }
450
+ return await cycle.promise!;
451
+ }
452
+
453
+ async #openWithRetry(cycle: Cycle): Promise<Incarnation> {
454
+ const startedAt = Date.now();
455
+ let attemptsConsumed = 0;
456
+ let lastError: unknown;
457
+ const diagnostics = (reason: SdkReconnectTerminationReason): SdkReconnectExhaustedDetails => ({
458
+ attemptsConsumed,
459
+ attemptBudget: this.#reconnectAttempts,
460
+ elapsedMs: Date.now() - startedAt,
461
+ reason,
462
+ });
463
+ const cancelled = (): SdkClientError =>
464
+ new SdkClientError("connection_closed", "SDK client closed", lastError, diagnostics("cancelled"));
465
+ /**
466
+ * A deadline and retry budget measure different failure axes. One-shot clients
467
+ * need the deadline to fast-fail their operation, while long-lived ACP sessions
468
+ * express their recovery window as retry slots. Honor both, but retain the
469
+ * terminating axis so a deadline-truncated session budget is not misdiagnosed
470
+ * as a host that consumed every retry.
471
+ */
472
+ for (let attempt = 0; attempt <= this.#reconnectAttempts; attempt++) {
473
+ if (this.#deadline !== undefined && Date.now() >= this.#deadline) {
474
+ const error = this.#deadlineError(diagnostics("deadline"));
475
+ this.#completeCycle(cycle, error);
476
+ throw error;
477
+ }
478
+ if (!this.#isOpening(cycle)) throw cancelled();
479
+ if (attempt > 0) attemptsConsumed++;
480
+ try {
481
+ const incarnation = await this.#open(cycle);
482
+ if (!this.#isActive(incarnation) && (!this.#isOpening(cycle) || cycle.candidate !== incarnation))
483
+ throw new SdkClientError("connection_closed", "SDK WebSocket is not connected");
484
+ await this.#waitForHello(incarnation);
485
+ if (this.#isActive(incarnation)) return incarnation;
486
+ throw new SdkClientError("connection_closed", "SDK WebSocket is not connected");
487
+ } catch (error) {
488
+ lastError = error;
489
+ if (!this.#isOpening(cycle)) throw cancelled();
490
+ const candidate = cycle.candidate;
491
+ if (candidate && candidate.phase !== "active")
492
+ this.#retire(
493
+ candidate,
494
+ error instanceof SdkClientError
495
+ ? error
496
+ : new SdkClientError("unavailable", "SDK WebSocket connection failed", error),
497
+ true,
498
+ );
499
+ if (attempt < this.#reconnectAttempts) {
500
+ const backoffMs = this.#remainingTimeout(
501
+ Math.min(this.#reconnectBackoffMs * 2 ** attempt, this.#reconnectMaxBackoffMs),
502
+ );
503
+ if (backoffMs <= 0) break;
504
+ cycle.phase = "backoff";
505
+ // A close during the sleep rejects this promise. Left uncaught it escapes as
506
+ // the bare teardown error, so the one cancellation that is hardest to observe
507
+ // would be the only one carrying no attribution.
508
+ const deferred = Promise.withResolvers<void>();
509
+ cycle.rejectBackoff = deferred.reject;
510
+ cycle.backoffTimer = setTimeout(() => deferred.resolve(), backoffMs);
511
+ try {
512
+ await deferred.promise;
513
+ } catch (backoffRejection) {
514
+ lastError = backoffRejection;
515
+ throw cancelled();
516
+ } finally {
517
+ cycle.rejectBackoff = undefined;
518
+ cycle.backoffTimer = undefined;
519
+ }
520
+ if (!this.#isOpening(cycle)) throw cancelled();
521
+ cycle.phase = "opening";
522
+ }
523
+ }
524
+ }
525
+ if (!this.#isOpening(cycle)) throw cancelled();
526
+ if (this.#deadline !== undefined && Date.now() >= this.#deadline) {
527
+ const error = this.#deadlineError(diagnostics("deadline"));
528
+ this.#completeCycle(cycle, error);
529
+ throw error;
530
+ }
531
+ cycle.phase = "complete";
532
+ if (this.#opening === cycle) this.#opening = null;
533
+ const error = new SdkClientError(
534
+ "reconnect_exhausted",
535
+ "SDK WebSocket reconnect attempts exhausted",
536
+ lastError,
537
+ diagnostics("attempts_exhausted"),
538
+ );
539
+ this.#notifyReconnectFailedHandlers(error);
540
+ throw error;
541
+ }
542
+
543
+ #completeCycle(cycle: Cycle, error: SdkClientError): void {
544
+ if (cycle.backoffTimer) clearTimeout(cycle.backoffTimer);
545
+ cycle.rejectBackoff?.(error);
546
+ cycle.rejectBackoff = undefined;
547
+ cycle.backoffTimer = undefined;
548
+ const candidate = cycle.candidate;
549
+ if (candidate) this.#retire(candidate, error, true);
550
+ cycle.candidate = null;
551
+ cycle.phase = "complete";
552
+ if (this.#opening === cycle) this.#opening = null;
553
+ }
554
+
555
+ #open(cycle: Cycle): Promise<Incarnation> {
556
+ const timeoutMs = this.#remainingTimeout();
557
+ if (timeoutMs <= 0) return Promise.reject(this.#deadlineError());
558
+ const deferred = Promise.withResolvers<Incarnation>();
559
+ try {
560
+ const url = new URL(this.#url);
561
+ url.searchParams.set("token", this.#token);
562
+ const socket = new WebSocket(url);
563
+ const incarnation: Incarnation = {
564
+ generation: ++this.#incarnationGeneration,
565
+ cycle,
566
+ socket,
567
+ phase: "opening",
568
+ tornDown: false,
569
+ listeners: [],
570
+ resolveOpen: () => deferred.resolve(incarnation),
571
+ rejectOpen: deferred.reject,
572
+ };
573
+ cycle.candidate = incarnation;
574
+ const add = (type: "open" | "error" | "close" | "message", listener: EventListener, once = false) => {
575
+ incarnation.listeners.push([type, listener]);
576
+ socket.addEventListener(type, listener, once ? { once: true } : undefined);
577
+ };
578
+ add(
579
+ "open",
580
+ (() => {
581
+ if (!this.#isCandidate(cycle, incarnation) || incarnation.phase !== "opening") return;
582
+ if (incarnation.openTimer) clearTimeout(incarnation.openTimer);
583
+ incarnation.phase = "hello";
584
+ incarnation.resolveOpen?.();
585
+ incarnation.resolveOpen = undefined;
586
+ incarnation.rejectOpen = undefined;
587
+ this.#beginHello(incarnation);
588
+ const earlyHello = incarnation.earlyHello;
589
+ if (earlyHello) {
590
+ incarnation.earlyHello = undefined;
591
+ this.#acceptHello(incarnation, earlyHello);
592
+ if (this.#isActive(incarnation)) this.#notifyFrameHandlers(earlyHello);
593
+ }
594
+ }) as EventListener,
595
+ true,
596
+ );
597
+ add("error", ((event: Event) => this.#onSocketFailure(incarnation, event)) as EventListener);
598
+ add("close", (() => this.#onSocketFailure(incarnation)) as EventListener);
599
+ add("message", ((event: MessageEvent) => this.#onMessage(event.data, incarnation)) as EventListener);
600
+ incarnation.openTimer = setTimeout(() => this.#onOpenTimeout(incarnation, timeoutMs), timeoutMs);
601
+ incarnation.openTimer.unref?.();
602
+ } catch (error) {
603
+ deferred.reject(error);
604
+ }
605
+ return deferred.promise;
606
+ }
607
+
608
+ #beginHello(incarnation: Incarnation): void {
609
+ const timeoutMs = this.#remainingTimeout();
610
+ if (timeoutMs <= 0) {
611
+ this.#retire(incarnation, this.#deadlineError(), true);
612
+ return;
613
+ }
614
+ incarnation.helloTimer = setTimeout(() => {
615
+ if (!this.#isCandidate(incarnation.cycle, incarnation) || incarnation.phase !== "hello") return;
616
+ const error =
617
+ this.#deadline !== undefined && Date.now() >= this.#deadline
618
+ ? this.#deadlineError()
619
+ : new SdkClientError("protocol_error", "SDK server did not send a hello frame.");
620
+ incarnation.rejectHello?.(error);
621
+ this.#retire(incarnation, error, true);
622
+ }, timeoutMs);
623
+ incarnation.helloTimer.unref?.();
624
+ }
625
+
626
+ #waitForHello(incarnation: Incarnation): Promise<void> {
627
+ if (incarnation.failure) return Promise.reject(incarnation.failure);
628
+ if (this.#isActive(incarnation)) return Promise.resolve();
629
+ if (!this.#isCandidate(incarnation.cycle, incarnation) || incarnation.phase !== "hello")
630
+ return Promise.reject(new SdkClientError("connection_closed", "SDK WebSocket is not connected"));
631
+ const deferred = Promise.withResolvers<void>();
632
+ incarnation.resolveHello = deferred.resolve;
633
+ incarnation.rejectHello = deferred.reject;
634
+ return deferred.promise;
635
+ }
636
+
637
+ #onOpenTimeout(incarnation: Incarnation, timeoutMs: number): void {
638
+ if (!this.#isCandidate(incarnation.cycle, incarnation) || incarnation.phase !== "opening") return;
639
+ const error =
640
+ this.#deadline !== undefined && Date.now() >= this.#deadline
641
+ ? this.#deadlineError()
642
+ : new SdkClientError("timeout", `SDK WebSocket connection timed out after ${timeoutMs}ms`);
643
+ incarnation.rejectOpen?.(error);
644
+ this.#retire(incarnation, error, true);
645
+ }
646
+
647
+ #onSocketFailure(incarnation: Incarnation, event?: Event): void {
648
+ if (!this.#isCandidate(incarnation.cycle, incarnation) && !this.#isActive(incarnation)) return;
649
+ const detail = event as (Event & { error?: unknown; message?: unknown }) | undefined;
650
+ const error =
651
+ detail?.error instanceof Error
652
+ ? detail.error
653
+ : new SdkClientError(
654
+ "connection_closed",
655
+ typeof detail?.message === "string" ? detail.message : "SDK WebSocket connection closed",
656
+ );
657
+ if (incarnation.phase === "opening") incarnation.rejectOpen?.(error);
658
+ if (incarnation.phase === "hello") incarnation.rejectHello?.(error);
659
+ this.#retire(
660
+ incarnation,
661
+ error instanceof SdkClientError
662
+ ? error
663
+ : new SdkClientError("unavailable", "SDK WebSocket connection failed", error),
664
+ true,
665
+ true,
666
+ );
667
+ }
668
+
669
+ #onMessage(value: unknown, incarnation: Incarnation): void {
670
+ if (!this.#isCandidate(incarnation.cycle, incarnation) && !this.#isActive(incarnation)) return;
671
+ let frame: Frame;
672
+ try {
673
+ frame = parseFrame(value);
674
+ if (frame.type === "control_command_result" && typeof frame.message === "string")
675
+ frame = parseFrame(frame.message);
676
+ } catch (error) {
677
+ this.#rejectPendingFor(
678
+ incarnation,
679
+ error instanceof SdkClientError
680
+ ? error
681
+ : new SdkClientError("protocol_error", "SDK server sent malformed frame.", error),
682
+ );
683
+ return;
684
+ }
685
+ if (frame.type === "hello" || frame.type === "server_hello" || frame.type === "broker_hello") {
686
+ if (incarnation.phase === "opening" && this.#isCandidate(incarnation.cycle, incarnation)) {
687
+ // Buffer until the open handler advances phase; do not drop.
688
+ incarnation.earlyHello = frame;
689
+ return;
690
+ }
691
+ if (incarnation.phase === "hello" && this.#isCandidate(incarnation.cycle, incarnation)) {
692
+ this.#acceptHello(incarnation, frame);
693
+ if (this.#isActive(incarnation)) this.#notifyFrameHandlers(frame);
694
+ return;
695
+ }
696
+ if (!this.#isActive(incarnation)) return;
697
+ if (
698
+ typeof frame.connectionId !== "string" ||
699
+ frame.connectionId.length === 0 ||
700
+ frame.connectionId === this.connectionId
701
+ )
702
+ return;
703
+ this.connectionId = frame.connectionId;
704
+ this.#notifyReconnectHandlers();
705
+ }
706
+ if (!this.#isActive(incarnation)) return;
707
+ const id =
708
+ typeof frame.id === "string" ? frame.id : typeof frame.requestId === "string" ? frame.requestId : undefined;
709
+ if (id) {
710
+ const pending = this.#pending.get(id);
711
+ if (pending?.incarnation === incarnation) {
712
+ this.#settlePending(
713
+ id,
714
+ pending,
715
+ frame.ok === false || frame.status === "error" ? errorFrom(frame) : frame,
716
+ false,
717
+ true,
718
+ );
719
+ }
720
+ }
721
+ this.#notifyFrameHandlers(frame);
722
+ }
723
+
724
+ #notifyFrameHandlers(frame: Frame): void {
725
+ for (const handler of [...this.#frameHandlers]) {
726
+ try {
727
+ handler(frame);
728
+ } catch {
729
+ // Observers cannot change transport settlement or prevent later observers.
730
+ }
731
+ }
732
+ }
733
+
734
+ #notifyReconnectHandlers(): void {
735
+ for (const handler of [...this.#reconnectHandlers]) {
736
+ try {
737
+ handler();
738
+ } catch {
739
+ // Reconnect observers cannot change transport state or prevent later observers.
740
+ }
741
+ }
742
+ }
743
+
744
+ #notifyReconnectFailedHandlers(error: SdkClientError): void {
745
+ for (const handler of [...this.#reconnectFailedHandlers]) {
746
+ try {
747
+ handler(error);
748
+ } catch {
749
+ // Failure observers cannot replace the typed transport error or prevent later observers.
750
+ }
751
+ }
752
+ }
753
+
754
+ #acceptHello(incarnation: Incarnation, frame: Frame): void {
755
+ if (!this.#isCandidate(incarnation.cycle, incarnation) || incarnation.phase !== "hello") return;
756
+ if (incarnation.helloTimer) clearTimeout(incarnation.helloTimer);
757
+ const reconnecting =
758
+ typeof frame.connectionId === "string" &&
759
+ frame.connectionId.length > 0 &&
760
+ this.connectionId !== undefined &&
761
+ this.connectionId !== frame.connectionId;
762
+ if (typeof frame.connectionId === "string" && frame.connectionId.length > 0)
763
+ this.connectionId = frame.connectionId;
764
+ incarnation.phase = "active";
765
+ this.#currentSocketRecord = incarnation;
766
+ incarnation.cycle.phase = "complete";
767
+ if (this.#opening === incarnation.cycle) this.#opening = null;
768
+ const resolveHello = incarnation.resolveHello;
769
+ incarnation.resolveHello = undefined;
770
+ incarnation.rejectHello = undefined;
771
+ resolveHello?.();
772
+ if (reconnecting) this.#notifyReconnectHandlers();
773
+ }
774
+
775
+ #settlePending(
776
+ id: string,
777
+ pending: Pending,
778
+ result: unknown,
779
+ transportFailure = false,
780
+ responseReceived = false,
781
+ ): void {
782
+ if (this.#pending.get(id) !== pending) return;
783
+ this.#pending.delete(id);
784
+ clearTimeout(pending.timer);
785
+ if (responseReceived) pending.onResponse?.();
786
+ if (result instanceof Error) {
787
+ if (
788
+ transportFailure &&
789
+ pending.sent &&
790
+ result instanceof SdkClientError &&
791
+ (result.code === "timeout" || result.code === "connection_closed")
792
+ )
793
+ pending.reject(
794
+ new SdkClientError(
795
+ "uncertain_after_send",
796
+ "SDK request outcome is uncertain after the frame was sent.",
797
+ this.#sentRecords.get(id),
798
+ ),
799
+ );
800
+ else {
801
+ this.#sentRecords.delete(id);
802
+ pending.reject(result);
803
+ }
804
+ return;
805
+ }
806
+ this.#sentRecords.delete(id);
807
+ pending.resolve(result);
808
+ }
809
+ #rejectPendingFor(incarnation: Incarnation, error: SdkClientError, transportFailure = false): void {
810
+ for (const [id, pending] of this.#pending)
811
+ if (pending.incarnation === incarnation) this.#settlePending(id, pending, error, transportFailure);
812
+ }
813
+ #retire(incarnation: Incarnation, error: SdkClientError, closeSocket: boolean, transportFailure = false): void {
814
+ if (incarnation.tornDown) return;
815
+ const phase = incarnation.phase;
816
+ incarnation.phase = "retired";
817
+ incarnation.failure = error;
818
+ if (phase === "opening") incarnation.rejectOpen?.(error);
819
+ if (phase === "hello") incarnation.rejectHello?.(error);
820
+ incarnation.resolveOpen = undefined;
821
+ incarnation.rejectOpen = undefined;
822
+ incarnation.resolveHello = undefined;
823
+ incarnation.rejectHello = undefined;
824
+ this.#rejectPendingFor(incarnation, error, transportFailure);
825
+ if (this.#currentSocketRecord === incarnation) this.#currentSocketRecord = null;
826
+ if (incarnation.cycle.candidate === incarnation) incarnation.cycle.candidate = null;
827
+ this.#teardown(incarnation, closeSocket);
828
+ }
829
+ #teardown(incarnation: Incarnation, closeSocket: boolean): void {
830
+ if (incarnation.tornDown) return;
831
+ incarnation.tornDown = true;
832
+ if (incarnation.openTimer) clearTimeout(incarnation.openTimer);
833
+ if (incarnation.helloTimer) clearTimeout(incarnation.helloTimer);
834
+ for (const [type, listener] of incarnation.listeners) incarnation.socket.removeEventListener(type, listener);
835
+ incarnation.listeners = [];
836
+ if (closeSocket)
837
+ try {
838
+ incarnation.socket.close();
839
+ } catch {}
840
+ }
841
+ async #closeTransport(incarnation: Incarnation): Promise<void> {
842
+ const socket = incarnation.socket;
843
+ if (socket.readyState === WebSocket.CLOSED) return;
844
+ // Close teardown must always issue socket.close() and be bounded by a
845
+ // dedicated close grace, never by the (possibly elapsed) request deadline —
846
+ // gating on an expired deadline would throw before close and leak the socket.
847
+ const timeoutMs = this.#closeGraceMs;
848
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
849
+ const onClose = (): void => resolve();
850
+ socket.addEventListener("close", onClose, { once: true });
851
+ const timer = setTimeout(
852
+ () => reject(new SdkClientError("timeout", `SDK WebSocket close timed out after ${timeoutMs}ms`)),
853
+ timeoutMs,
854
+ );
855
+ timer.unref?.();
856
+ try {
857
+ socket.close();
858
+ if (Number(socket.readyState) === WebSocket.CLOSED) resolve();
859
+ await promise;
860
+ } catch (error) {
861
+ if (error instanceof SdkClientError) throw error;
862
+ if (Number(socket.readyState) !== WebSocket.CLOSED)
863
+ throw new SdkClientError("connection_closed", "SDK WebSocket close failed", error);
864
+ } finally {
865
+ clearTimeout(timer);
866
+ socket.removeEventListener("close", onClose);
867
+ }
868
+ }
869
+ #isCandidate(cycle: Cycle, incarnation: Incarnation): boolean {
870
+ return (
871
+ !this.#closed &&
872
+ this.#opening === cycle &&
873
+ cycle.candidate === incarnation &&
874
+ cycle.generation > 0 &&
875
+ incarnation.generation > 0 &&
876
+ incarnation.cycle === cycle &&
877
+ (cycle.phase === "opening" || cycle.phase === "backoff")
878
+ );
879
+ }
880
+ #isOpening(cycle: Cycle): boolean {
881
+ return !this.#closed && this.#opening === cycle && (cycle.phase === "opening" || cycle.phase === "backoff");
882
+ }
883
+ #isActive(incarnation: Incarnation | null): boolean {
884
+ return (
885
+ !!incarnation &&
886
+ incarnation.generation > 0 &&
887
+ !this.#closed &&
888
+ this.#currentSocketRecord === incarnation &&
889
+ incarnation.phase === "active"
890
+ );
891
+ }
892
+ }