@phuetz/code-buddy 2.0.0 → 2.2.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 (421) hide show
  1. package/README.fr.md +56 -0
  2. package/README.md +74 -16
  3. package/codebuddy-runtime.json +5 -5
  4. package/dist/agent/codebuddy-agent.d.ts +18 -0
  5. package/dist/agent/codebuddy-agent.js +45 -8
  6. package/dist/agent/execution/agent-executor.d.ts +0 -9
  7. package/dist/agent/execution/agent-executor.js +176 -12
  8. package/dist/agent/execution/context-pipeline.d.ts +9 -0
  9. package/dist/agent/execution/context-pipeline.js +64 -2
  10. package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
  11. package/dist/agent/execution/tool-loop-guard.js +165 -0
  12. package/dist/agent/execution/tool-selection-strategy.d.ts +7 -0
  13. package/dist/agent/execution/tool-selection-strategy.js +32 -2
  14. package/dist/agent/facades/session-facade.d.ts +0 -6
  15. package/dist/agent/facades/session-facade.js +0 -45
  16. package/dist/agent/hermes-claw-migrate.d.ts +0 -21
  17. package/dist/agent/hermes-claw-migrate.js +9 -4
  18. package/dist/agent/hermes-protocol-gateways.js +8 -1
  19. package/dist/agent/repo-profiler.js +17 -4
  20. package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
  21. package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
  22. package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
  23. package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
  24. package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
  25. package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
  26. package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
  27. package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
  28. package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
  29. package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
  30. package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
  31. package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
  32. package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
  33. package/dist/agent/self-improvement/proposal-store.js +79 -17
  34. package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
  35. package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
  36. package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
  37. package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
  38. package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
  39. package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
  40. package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
  41. package/dist/agent/self-improvement/skill-engine.js +385 -13
  42. package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
  43. package/dist/agent/self-improvement/skill-gate.js +17 -2
  44. package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
  45. package/dist/agent/self-improvement/skill-mutator.js +51 -15
  46. package/dist/agent/self-improvement/skill-types.d.ts +2 -1
  47. package/dist/agent/self-improvement/types.d.ts +11 -0
  48. package/dist/agent/tool-handler.d.ts +1 -1
  49. package/dist/agent/tool-handler.js +28 -9
  50. package/dist/auth/profile-manager.js +1 -1
  51. package/dist/channels/companion-channel-turn.d.ts +53 -4
  52. package/dist/channels/companion-channel-turn.js +218 -16
  53. package/dist/channels/slash-parity.d.ts +4 -6
  54. package/dist/channels/slash-parity.js +9 -44
  55. package/dist/cli/session-commands.d.ts +9 -0
  56. package/dist/cli/session-commands.js +45 -5
  57. package/dist/cli/session-picker.d.ts +43 -0
  58. package/dist/cli/session-picker.js +128 -0
  59. package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
  60. package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
  61. package/dist/codebuddy/fleet-tool-defs.d.ts +2 -0
  62. package/dist/codebuddy/fleet-tool-defs.js +55 -0
  63. package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
  64. package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
  65. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
  66. package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
  67. package/dist/codebuddy/providers/provider-openai-compat.d.ts +7 -0
  68. package/dist/codebuddy/providers/provider-openai-compat.js +29 -1
  69. package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
  70. package/dist/codebuddy/ragchat-tool-defs.js +19 -0
  71. package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
  72. package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
  73. package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
  74. package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
  75. package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
  76. package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
  77. package/dist/codebuddy/tools.d.ts +0 -6
  78. package/dist/codebuddy/tools.js +25 -3
  79. package/dist/commands/cli/daemon-commands.js +3 -1
  80. package/dist/commands/cli/evolve-command.js +21 -5
  81. package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
  82. package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
  83. package/dist/commands/cli/fleet-commands.js +29 -0
  84. package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
  85. package/dist/commands/cli/fleet-mission-commands.js +72 -0
  86. package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
  87. package/dist/commands/cli/fleet-rooms-commands.js +289 -0
  88. package/dist/commands/cli/tools-commands.js +2 -1
  89. package/dist/commands/cli/triage-command.d.ts +8 -0
  90. package/dist/commands/cli/triage-command.js +72 -0
  91. package/dist/commands/cli/utility-commands.js +25 -1
  92. package/dist/commands/client-dispatcher.d.ts +7 -0
  93. package/dist/commands/client-dispatcher.js +73 -21
  94. package/dist/commands/cron-cli/index.d.ts +3 -1
  95. package/dist/commands/cron-cli/index.js +25 -0
  96. package/dist/commands/device-auth.d.ts +10 -0
  97. package/dist/commands/device-auth.js +91 -0
  98. package/dist/commands/enhanced-command-handler.d.ts +9 -0
  99. package/dist/commands/enhanced-command-handler.js +28 -6
  100. package/dist/commands/handlers/agent-handlers.d.ts +1 -1
  101. package/dist/commands/handlers/agent-handlers.js +4 -4
  102. package/dist/commands/handlers/channel-handlers.js +53 -2
  103. package/dist/commands/handlers/export-handlers.d.ts +3 -2
  104. package/dist/commands/handlers/export-handlers.js +55 -28
  105. package/dist/commands/handlers/extra-handlers.js +5 -1
  106. package/dist/commands/handlers/grill-me-handler.js +26 -18
  107. package/dist/commands/handlers/memory-handlers.d.ts +6 -2
  108. package/dist/commands/handlers/memory-handlers.js +35 -15
  109. package/dist/commands/handlers/missing-handlers.d.ts +2 -2
  110. package/dist/commands/handlers/missing-handlers.js +12 -4
  111. package/dist/commands/handlers/resources-handler.d.ts +12 -0
  112. package/dist/commands/handlers/resources-handler.js +52 -0
  113. package/dist/commands/handlers/starter-handlers.d.ts +1 -1
  114. package/dist/commands/handlers/starter-handlers.js +32 -2
  115. package/dist/commands/handlers/switch-handler.d.ts +1 -1
  116. package/dist/commands/handlers/switch-handler.js +15 -0
  117. package/dist/commands/handlers/test-handlers.js +4 -86
  118. package/dist/commands/handlers/ui-handlers.js +11 -5
  119. package/dist/commands/login-chatgpt.d.ts +8 -0
  120. package/dist/commands/login-chatgpt.js +17 -0
  121. package/dist/commands/mcp-import.d.ts +11 -0
  122. package/dist/commands/mcp-import.js +67 -0
  123. package/dist/commands/mcp-invoke.d.ts +2 -0
  124. package/dist/commands/mcp-invoke.js +68 -0
  125. package/dist/commands/mcp.d.ts +13 -0
  126. package/dist/commands/mcp.js +66 -21
  127. package/dist/commands/pipeline.d.ts +7 -1
  128. package/dist/commands/pipeline.js +104 -7
  129. package/dist/commands/resources.d.ts +2 -0
  130. package/dist/commands/resources.js +64 -0
  131. package/dist/commands/skills-cli/index.js +35 -12
  132. package/dist/commands/slash/builtin-commands.js +16 -3
  133. package/dist/commands/slash/index.d.ts +3 -1
  134. package/dist/commands/slash/index.js +2 -0
  135. package/dist/commands/slash/surfaces.d.ts +53 -0
  136. package/dist/commands/slash/surfaces.js +177 -0
  137. package/dist/commands/slash/types.d.ts +17 -0
  138. package/dist/commands/try.d.ts +1 -1
  139. package/dist/commands/try.js +1 -1
  140. package/dist/companion/companion-identity.d.ts +43 -0
  141. package/dist/companion/companion-identity.js +136 -0
  142. package/dist/companion/companion-toolset.d.ts +66 -0
  143. package/dist/companion/companion-toolset.js +368 -0
  144. package/dist/companion/companion-turn.d.ts +10 -2
  145. package/dist/companion/companion-turn.js +38 -1
  146. package/dist/config/code-exec-policy.d.ts +25 -0
  147. package/dist/config/code-exec-policy.js +39 -0
  148. package/dist/config/model-tools.d.ts +9 -0
  149. package/dist/config/model-tools.js +103 -0
  150. package/dist/context/context-manager-v2.js +2 -2
  151. package/dist/context/lm-resizer-compressor.js +3 -2
  152. package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
  153. package/dist/context/lm-resizer-diagnostics.js +30 -0
  154. package/dist/context/tool-output-masking.js +3 -4
  155. package/dist/curator/curator.d.ts +6 -0
  156. package/dist/curator/curator.js +53 -3
  157. package/dist/daemon/cron-agent-bridge.d.ts +6 -0
  158. package/dist/daemon/cron-agent-bridge.js +31 -4
  159. package/dist/doctor/index.d.ts +11 -2
  160. package/dist/doctor/index.js +84 -21
  161. package/dist/doctor/integrations.d.ts +197 -0
  162. package/dist/doctor/integrations.js +241 -0
  163. package/dist/doctor/triage.d.ts +108 -0
  164. package/dist/doctor/triage.js +304 -0
  165. package/dist/errors/crash-handler.js +2 -0
  166. package/dist/fleet/collaboration.d.ts +44 -0
  167. package/dist/fleet/collaboration.js +170 -0
  168. package/dist/fleet/fleet-listener.d.ts +1 -0
  169. package/dist/fleet/fleet-listener.js +87 -46
  170. package/dist/fleet/fleet-registry.d.ts +2 -0
  171. package/dist/fleet/resource-catalog.d.ts +245 -0
  172. package/dist/fleet/resource-catalog.js +211 -0
  173. package/dist/fleet/rooms/room-access.d.ts +138 -0
  174. package/dist/fleet/rooms/room-access.js +196 -0
  175. package/dist/fleet/rooms/room-client.d.ts +158 -0
  176. package/dist/fleet/rooms/room-client.js +685 -0
  177. package/dist/fleet/rooms/room-event.d.ts +139 -0
  178. package/dist/fleet/rooms/room-event.js +330 -0
  179. package/dist/fleet/rooms/room-filter.d.ts +48 -0
  180. package/dist/fleet/rooms/room-filter.js +152 -0
  181. package/dist/fleet/rooms/room-hub.d.ts +154 -0
  182. package/dist/fleet/rooms/room-hub.js +363 -0
  183. package/dist/fleet/rooms/room-identity.d.ts +26 -0
  184. package/dist/fleet/rooms/room-identity.js +82 -0
  185. package/dist/fleet/rooms/room-lock.d.ts +34 -0
  186. package/dist/fleet/rooms/room-lock.js +180 -0
  187. package/dist/fleet/rooms/room-observations.d.ts +7 -0
  188. package/dist/fleet/rooms/room-observations.js +61 -0
  189. package/dist/fleet/rooms/room-server.d.ts +28 -0
  190. package/dist/fleet/rooms/room-server.js +75 -0
  191. package/dist/fleet/rooms/room-store.d.ts +119 -0
  192. package/dist/fleet/rooms/room-store.js +448 -0
  193. package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
  194. package/dist/fleet/rooms/room-ws-bridge.js +137 -0
  195. package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
  196. package/dist/fleet/sensory-mission-bridge.js +136 -0
  197. package/dist/git/worktree-sessions.js +1 -0
  198. package/dist/harness/contract.d.ts +4 -4
  199. package/dist/harness/fleet-supervisor.d.ts +16 -0
  200. package/dist/harness/fleet-supervisor.js +64 -0
  201. package/dist/harness/index.d.ts +4 -0
  202. package/dist/harness/index.js +4 -0
  203. package/dist/harness/mission-runner.d.ts +3 -0
  204. package/dist/harness/mission-runner.js +53 -0
  205. package/dist/harness/mission-store.d.ts +93 -0
  206. package/dist/harness/mission-store.js +297 -0
  207. package/dist/harness/tool-harness.d.ts +56 -0
  208. package/dist/harness/tool-harness.js +153 -0
  209. package/dist/hooks/use-enhanced-input.js +48 -4
  210. package/dist/hooks/use-input-handler.d.ts +1 -0
  211. package/dist/hooks/use-input-handler.js +82 -22
  212. package/dist/hooks/use-input-history.js +1 -0
  213. package/dist/identity/lisa-introspection.js +5 -2
  214. package/dist/identity/operational-self-model.d.ts +2 -0
  215. package/dist/identity/operational-self-model.js +4 -0
  216. package/dist/index.js +52 -6
  217. package/dist/mcp/client.d.ts +11 -0
  218. package/dist/mcp/client.js +68 -3
  219. package/dist/mcp/config.d.ts +20 -0
  220. package/dist/mcp/config.js +121 -8
  221. package/dist/mcp/import-normalize.d.ts +12 -0
  222. package/dist/mcp/import-normalize.js +114 -0
  223. package/dist/mcp/mcp-oauth-constants.d.ts +8 -0
  224. package/dist/mcp/mcp-oauth-constants.js +9 -0
  225. package/dist/mcp/mcp-oauth-provider.d.ts +80 -0
  226. package/dist/mcp/mcp-oauth-provider.js +241 -0
  227. package/dist/mcp/mcp-oauth.d.ts +60 -1
  228. package/dist/mcp/mcp-oauth.js +241 -65
  229. package/dist/mcp/transports.d.ts +14 -5
  230. package/dist/mcp/transports.js +112 -101
  231. package/dist/mcp/types.d.ts +5 -0
  232. package/dist/media/comfyui-recipe-contract.d.ts +7 -7
  233. package/dist/memory/facts-memory.d.ts +11 -1
  234. package/dist/memory/facts-memory.js +47 -8
  235. package/dist/memory/memory-candidate-queue.d.ts +2 -2
  236. package/dist/memory/memory-candidate-queue.js +6 -6
  237. package/dist/memory/persistent-memory.d.ts +13 -8
  238. package/dist/memory/persistent-memory.js +77 -30
  239. package/dist/nodes/device-node.js +5 -9
  240. package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
  241. package/dist/observability/run-event-writer.d.ts +21 -0
  242. package/dist/observability/run-event-writer.js +81 -0
  243. package/dist/observability/run-store.d.ts +5 -0
  244. package/dist/observability/run-store.js +18 -13
  245. package/dist/observability/run-trajectory.js +1 -1
  246. package/dist/optimization/prompt-cache.d.ts +19 -2
  247. package/dist/optimization/prompt-cache.js +41 -6
  248. package/dist/orchestration/orchestrator.d.ts +21 -0
  249. package/dist/orchestration/orchestrator.js +122 -15
  250. package/dist/persistence/session-content.d.ts +8 -0
  251. package/dist/persistence/session-content.js +76 -0
  252. package/dist/persistence/session-handoff.d.ts +64 -0
  253. package/dist/persistence/session-handoff.js +80 -0
  254. package/dist/persistence/session-history.d.ts +4 -0
  255. package/dist/persistence/session-history.js +38 -0
  256. package/dist/persistence/session-store.d.ts +9 -0
  257. package/dist/persistence/session-store.js +48 -20
  258. package/dist/personas/persona-manager.js +17 -1
  259. package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
  260. package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
  261. package/dist/plugins/plugin-manager.js +10 -1
  262. package/dist/prompts/prompt-manager.js +11 -4
  263. package/dist/prompts/system-base.js +6 -2
  264. package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
  265. package/dist/protocols/a2a/codebuddy-executor.js +9 -8
  266. package/dist/protocols/a2a/index.d.ts +1 -1
  267. package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
  268. package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
  269. package/dist/protocols/a2a/peer-config.d.ts +20 -0
  270. package/dist/protocols/a2a/peer-config.js +55 -0
  271. package/dist/providers/codex-oauth.js +30 -6
  272. package/dist/providers/model-provider-compat.js +2 -1
  273. package/dist/sandbox/os-sandbox.d.ts +1 -1
  274. package/dist/scheduler/cron-scheduler.d.ts +15 -2
  275. package/dist/scheduler/cron-scheduler.js +35 -4
  276. package/dist/scheduler/job-notepad.d.ts +69 -0
  277. package/dist/scheduler/job-notepad.js +339 -0
  278. package/dist/security/audit-logger.d.ts +1 -1
  279. package/dist/security/compute-confinement.d.ts +4 -0
  280. package/dist/security/compute-confinement.js +89 -0
  281. package/dist/security/session-encryption.d.ts +4 -0
  282. package/dist/security/session-encryption.js +31 -0
  283. package/dist/sensory/voice-loop.d.ts +17 -0
  284. package/dist/sensory/voice-loop.js +72 -15
  285. package/dist/server/auth/device-session-context.d.ts +9 -0
  286. package/dist/server/auth/device-session-context.js +13 -0
  287. package/dist/server/auth/device-store.d.ts +89 -0
  288. package/dist/server/auth/device-store.js +261 -0
  289. package/dist/server/auth/device-token.d.ts +3 -0
  290. package/dist/server/auth/device-token.js +6 -0
  291. package/dist/server/auth/jwt.js +4 -0
  292. package/dist/server/index.d.ts +0 -10
  293. package/dist/server/index.js +28 -0
  294. package/dist/server/middleware/auth.js +3 -2
  295. package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
  296. package/dist/server/routes/a2a-jsonrpc.js +66 -0
  297. package/dist/server/routes/a2a-protocol.d.ts +12 -0
  298. package/dist/server/routes/a2a-protocol.js +1 -1
  299. package/dist/server/routes/device-auth.d.ts +4 -0
  300. package/dist/server/routes/device-auth.js +40 -0
  301. package/dist/server/types.d.ts +4 -0
  302. package/dist/server/websocket/handler.d.ts +13 -0
  303. package/dist/server/websocket/handler.js +102 -17
  304. package/dist/services/prompt-builder.d.ts +2 -0
  305. package/dist/services/prompt-builder.js +16 -6
  306. package/dist/services/runtime-settings-context.d.ts +81 -0
  307. package/dist/services/runtime-settings-context.js +79 -0
  308. package/dist/skills/executor.js +2 -0
  309. package/dist/skills/hub.js +16 -14
  310. package/dist/skills/local-inventory.d.ts +21 -0
  311. package/dist/skills/local-inventory.js +72 -0
  312. package/dist/skills/skill-loader.js +2 -2
  313. package/dist/skills/skill-usage-store.d.ts +49 -0
  314. package/dist/skills/skill-usage-store.js +121 -0
  315. package/dist/testing/ai-integration-tests.js +8 -5
  316. package/dist/themes/theme-manager.d.ts +12 -0
  317. package/dist/themes/theme-manager.js +29 -0
  318. package/dist/themes/theme-schema.d.ts +24 -24
  319. package/dist/tools/a2a-call-tool.d.ts +34 -0
  320. package/dist/tools/a2a-call-tool.js +75 -0
  321. package/dist/tools/bash/streaming-executor.d.ts +1 -0
  322. package/dist/tools/bash/streaming-executor.js +78 -90
  323. package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
  324. package/dist/tools/code-exec-preflight-runner.js +254 -0
  325. package/dist/tools/code-exec-preflight.d.ts +44 -0
  326. package/dist/tools/code-exec-preflight.js +501 -0
  327. package/dist/tools/code-exec-tool.d.ts +48 -1
  328. package/dist/tools/code-exec-tool.js +191 -47
  329. package/dist/tools/comment-watcher.js +29 -12
  330. package/dist/tools/core-code-inspection.d.ts +56 -0
  331. package/dist/tools/core-code-inspection.js +86 -0
  332. package/dist/tools/create-skill-tool.d.ts +2 -3
  333. package/dist/tools/create-skill-tool.js +16 -75
  334. package/dist/tools/cronjob-tool.d.ts +1 -0
  335. package/dist/tools/cronjob-tool.js +1 -0
  336. package/dist/tools/env-doctor-tool.d.ts +2 -0
  337. package/dist/tools/env-doctor-tool.js +4 -3
  338. package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
  339. package/dist/tools/execute-code-rpc-invoker.js +35 -19
  340. package/dist/tools/execute-code-runner.d.ts +2 -0
  341. package/dist/tools/execute-code-runner.js +52 -17
  342. package/dist/tools/fleet-room-tool.d.ts +104 -0
  343. package/dist/tools/fleet-room-tool.js +124 -0
  344. package/dist/tools/integration-tool-hints.d.ts +4 -0
  345. package/dist/tools/integration-tool-hints.js +19 -0
  346. package/dist/tools/list-peers-tool.d.ts +3 -0
  347. package/dist/tools/list-peers-tool.js +11 -4
  348. package/dist/tools/media-generation-tool.d.ts +16 -1
  349. package/dist/tools/media-generation-tool.js +341 -9
  350. package/dist/tools/merge-conflict-tool.js +53 -25
  351. package/dist/tools/metadata.d.ts +1 -5
  352. package/dist/tools/metadata.js +30 -28
  353. package/dist/tools/peer-tool-invoke-tool.d.ts +61 -0
  354. package/dist/tools/peer-tool-invoke-tool.js +396 -0
  355. package/dist/tools/ragchat-tool.d.ts +39 -0
  356. package/dist/tools/ragchat-tool.js +105 -0
  357. package/dist/tools/registry/fleet-tools.d.ts +21 -3
  358. package/dist/tools/registry/fleet-tools.js +137 -4
  359. package/dist/tools/registry/index.d.ts +1 -1
  360. package/dist/tools/registry/index.js +2 -2
  361. package/dist/tools/registry/knowledge-tools.d.ts +2 -2
  362. package/dist/tools/registry/knowledge-tools.js +2 -2
  363. package/dist/tools/registry/lessons-tools.d.ts +7 -0
  364. package/dist/tools/registry/lessons-tools.js +43 -3
  365. package/dist/tools/registry/memory-tools.d.ts +1 -1
  366. package/dist/tools/registry/memory-tools.js +8 -8
  367. package/dist/tools/registry/research-tools.d.ts +1 -0
  368. package/dist/tools/registry/research-tools.js +4 -13
  369. package/dist/tools/registry/self-describe-tools.d.ts +1 -1
  370. package/dist/tools/registry/self-describe-tools.js +43 -30
  371. package/dist/tools/resource-catalog-tool.d.ts +38 -0
  372. package/dist/tools/resource-catalog-tool.js +46 -0
  373. package/dist/tools/skills-inspection-tool.js +30 -1
  374. package/dist/tools/stream-tool-output.d.ts +3 -0
  375. package/dist/tools/stream-tool-output.js +33 -0
  376. package/dist/tools/tool-call-scheduler.d.ts +10 -0
  377. package/dist/tools/tool-call-scheduler.js +58 -0
  378. package/dist/tools/tool-effect.d.ts +5 -0
  379. package/dist/tools/tool-effect.js +26 -0
  380. package/dist/tools/tool-search.d.ts +12 -11
  381. package/dist/tools/tool-search.js +67 -26
  382. package/dist/tools/types.d.ts +6 -0
  383. package/dist/ui/components/ChatHistory.d.ts +1 -1
  384. package/dist/ui/components/ChatHistory.js +2 -2
  385. package/dist/ui/components/ChatInput.d.ts +2 -1
  386. package/dist/ui/components/ChatInput.js +42 -88
  387. package/dist/ui/components/ChatInterface.d.ts +4 -2
  388. package/dist/ui/components/ChatInterface.js +27 -63
  389. package/dist/ui/components/CommandSuggestions.d.ts +0 -1
  390. package/dist/ui/components/CommandSuggestions.js +4 -6
  391. package/dist/ui/components/FileAutocomplete.js +1 -1
  392. package/dist/ui/components/KeyboardHelp.js +8 -6
  393. package/dist/ui/components/ModelSelection.js +6 -5
  394. package/dist/ui/components/StatusBar.d.ts +2 -1
  395. package/dist/ui/components/StatusBar.js +8 -7
  396. package/dist/ui/context/theme-context.js +3 -2
  397. package/dist/ui/index.d.ts +1 -1
  398. package/dist/ui/index.js +1 -1
  399. package/dist/ui/utils/markdown-renderer.js +4 -13
  400. package/dist/utils/atomic-write.d.ts +22 -0
  401. package/dist/utils/atomic-write.js +29 -0
  402. package/dist/utils/bounded-output.d.ts +16 -0
  403. package/dist/utils/bounded-output.js +63 -0
  404. package/dist/utils/config-validation/schema.d.ts +2 -2
  405. package/dist/utils/device-store-file.d.ts +4 -0
  406. package/dist/utils/device-store-file.js +40 -0
  407. package/dist/utils/export-manager.d.ts +9 -0
  408. package/dist/utils/export-manager.js +19 -0
  409. package/dist/utils/first-use-hints.d.ts +25 -0
  410. package/dist/utils/first-use-hints.js +58 -0
  411. package/dist/utils/graceful-shutdown.d.ts +8 -0
  412. package/dist/utils/graceful-shutdown.js +23 -7
  413. package/dist/utils/history-manager.js +1 -1
  414. package/dist/utils/update-notifier.js +16 -18
  415. package/dist/wizard/environment-detection.d.ts +5 -1
  416. package/dist/wizard/environment-detection.js +49 -7
  417. package/dist/wizard/onboarding.d.ts +5 -1
  418. package/dist/wizard/onboarding.js +90 -30
  419. package/dist/wizard/provider-onboarding.d.ts +2 -0
  420. package/dist/wizard/provider-onboarding.js +55 -17
  421. package/package.json +7 -3
@@ -0,0 +1,685 @@
1
+ /**
2
+ * Fleet rooms — member client.
3
+ *
4
+ * Reconnection follows the Buzz ACP harness (`crates/buzz-acp/src/relay.rs`):
5
+ * resubscribe after reconnect from a per-subscription cursor and drop events
6
+ * already seen with a two-generation id set (`TwoGenDedup`, 12 000 ids). The
7
+ * cursor is the hub `seq` (plus its `storeId`) instead of `created_at`, so
8
+ * member clock drift cannot open a hole.
9
+ *
10
+ * Everything received is untrusted data: signatures and room tags are checked
11
+ * again locally (the hub could be compromised), and `text` is the content
12
+ * passed through the fleet peer-text sanitizer. This client never runs a tool,
13
+ * a prompt or a command because of a message.
14
+ *
15
+ * @module fleet/rooms/room-client
16
+ */
17
+ import { EventEmitter } from 'events';
18
+ import WebSocket from 'ws';
19
+ import { sanitizePeerText } from '../peer-text-sanitizer.js';
20
+ import { buildRoomAuth, buildRoomMessage, canonicalRoomAudience, checkRoomEventShape, deriveRoomPublicKey, freezeRoomEvent, parseThreadMarkers, resolveThread, roomOf, signRoomEvent, tagValues, verifyRoomEvent, } from './room-event.js';
21
+ import { parseRoomFilters, roomFiltersMatch } from './room-filter.js';
22
+ const SEEN_ID_LIMIT = 12_000;
23
+ const DEFAULT_FETCH_MAX_MESSAGES = 500;
24
+ const MAX_FETCH_MESSAGES = 1_000;
25
+ const DEFAULT_FETCH_MAX_BYTES = 4 * 1024 * 1024;
26
+ const MAX_FETCH_BYTES = 4 * 1024 * 1024;
27
+ const STORE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
28
+ export class RoomClientError extends Error {
29
+ code;
30
+ constructor(message, code) {
31
+ super(message);
32
+ this.code = code;
33
+ this.name = 'RoomClientError';
34
+ }
35
+ }
36
+ class TwoGenerationSet {
37
+ limit;
38
+ current = new Set();
39
+ previous = new Set();
40
+ constructor(limit) {
41
+ this.limit = limit;
42
+ }
43
+ /** Returns true when the id was not seen before. */
44
+ add(id) {
45
+ if (this.current.has(id) || this.previous.has(id))
46
+ return false;
47
+ this.current.add(id);
48
+ if (this.current.size >= this.limit / 2) {
49
+ this.previous = this.current;
50
+ this.current = new Set();
51
+ }
52
+ return true;
53
+ }
54
+ }
55
+ function positive(name, value, fallback) {
56
+ const resolved = value ?? fallback;
57
+ if (!Number.isFinite(resolved) || resolved <= 0)
58
+ throw new RangeError(`${name} must be a finite number > 0`);
59
+ return resolved;
60
+ }
61
+ function boundedPositiveInteger(name, value, fallback, maximum) {
62
+ const resolved = value ?? fallback;
63
+ if (!Number.isSafeInteger(resolved) || resolved <= 0 || resolved > maximum) {
64
+ throw new RangeError(`${name} must be a positive safe integer <= ${maximum}`);
65
+ }
66
+ return resolved;
67
+ }
68
+ function isStoreId(value) {
69
+ return typeof value === 'string' && STORE_ID_PATTERN.test(value);
70
+ }
71
+ export class FleetRoomClient extends EventEmitter {
72
+ pubkey;
73
+ audience;
74
+ options;
75
+ requestTimeoutMs;
76
+ reconnectInitialDelayMs;
77
+ reconnectMaxDelayMs;
78
+ reconnectMaxAttempts;
79
+ ws = null;
80
+ ready = false;
81
+ closing = false;
82
+ everConnected = false;
83
+ requestCounter = 0;
84
+ pending = new Map();
85
+ subscriptions = new Map();
86
+ seen = new TwoGenerationSet(SEEN_ID_LIMIT);
87
+ reconnectTimer = null;
88
+ reconnectAttempts = 0;
89
+ readyWaiters = [];
90
+ opening = null;
91
+ connectionInfo = null;
92
+ constructor(options) {
93
+ super();
94
+ this.options = options;
95
+ this.pubkey = deriveRoomPublicKey(options.secretKey);
96
+ this.audience = canonicalRoomAudience(options.audience ?? options.url);
97
+ this.requestTimeoutMs = positive('requestTimeoutMs', options.requestTimeoutMs, 10_000);
98
+ this.reconnectInitialDelayMs = positive('reconnectInitialDelayMs', options.reconnectInitialDelayMs, 250);
99
+ this.reconnectMaxDelayMs = positive('reconnectMaxDelayMs', options.reconnectMaxDelayMs, 30_000);
100
+ this.reconnectMaxAttempts = positive('reconnectMaxAttempts', options.reconnectMaxAttempts, 20);
101
+ }
102
+ get isReady() {
103
+ return this.ready;
104
+ }
105
+ /** Connect, authenticate the WebSocket, then prove the member key. */
106
+ connect() {
107
+ this.closing = false;
108
+ if (this.ready && this.connectionInfo)
109
+ return Promise.resolve(this.connectionInfo);
110
+ return this.startOpen();
111
+ }
112
+ startOpen() {
113
+ if (this.opening)
114
+ return this.opening;
115
+ const opening = this.open();
116
+ this.opening = opening;
117
+ void opening.finally(() => {
118
+ if (this.opening === opening)
119
+ this.opening = null;
120
+ }).catch(() => undefined);
121
+ return opening;
122
+ }
123
+ open() {
124
+ return new Promise((resolve, reject) => {
125
+ let settled = false;
126
+ let transportAuthRequested = false;
127
+ let roomProofStarted = false;
128
+ const fail = (error) => {
129
+ if (settled)
130
+ return;
131
+ settled = true;
132
+ reject(error);
133
+ try {
134
+ ws.terminate();
135
+ }
136
+ catch {
137
+ // ignore
138
+ }
139
+ };
140
+ const ws = new WebSocket(this.options.url);
141
+ this.ws = ws;
142
+ const timer = setTimeout(() => fail(new RoomClientError('connection timed out', 'TIMEOUT')), this.requestTimeoutMs);
143
+ const startRoomProof = () => {
144
+ if (roomProofStarted || this.ws !== ws)
145
+ return;
146
+ roomProofStarted = true;
147
+ void this.proveIdentity(ws).then((info) => {
148
+ clearTimeout(timer);
149
+ if (!settled) {
150
+ settled = true;
151
+ resolve(info);
152
+ }
153
+ }, (error) => { clearTimeout(timer); fail(error); });
154
+ };
155
+ ws.on('message', (data) => {
156
+ if (this.ws !== ws)
157
+ return;
158
+ let message;
159
+ try {
160
+ message = JSON.parse(data.toString());
161
+ }
162
+ catch {
163
+ return;
164
+ }
165
+ if (message.type === 'connected') {
166
+ if (this.options.apiKey || this.options.jwt) {
167
+ if (transportAuthRequested)
168
+ return;
169
+ transportAuthRequested = true;
170
+ this.sendRaw({
171
+ type: 'authenticate',
172
+ payload: this.options.apiKey ? { apiKey: this.options.apiKey } : { token: this.options.jwt },
173
+ });
174
+ }
175
+ else {
176
+ startRoomProof();
177
+ }
178
+ return;
179
+ }
180
+ if (message.type === 'authenticated') {
181
+ startRoomProof();
182
+ return;
183
+ }
184
+ if (message.type === 'error' && !message.requestId && !this.ready) {
185
+ clearTimeout(timer);
186
+ fail(new RoomClientError(message.error?.message ?? 'server error', message.error?.code ?? 'SERVER_ERROR'));
187
+ return;
188
+ }
189
+ this.handleMessage(message);
190
+ });
191
+ ws.on('error', (error) => {
192
+ clearTimeout(timer);
193
+ fail(error instanceof Error ? error : new Error(String(error)));
194
+ });
195
+ ws.on('close', () => {
196
+ clearTimeout(timer);
197
+ if (this.ws === ws)
198
+ this.onSocketClosed();
199
+ fail(new RoomClientError('connection closed before authentication', 'DISCONNECTED'));
200
+ });
201
+ });
202
+ }
203
+ async proveIdentity(ws) {
204
+ const hello = await this.request('fleet.rooms.hello', {}, 'fleet.rooms.challenge');
205
+ const challenge = typeof hello.challenge === 'string' ? hello.challenge : '';
206
+ // The audience comes from the URL this client dialed, never from the hub.
207
+ const proof = signRoomEvent(buildRoomAuth(challenge, this.audience), this.options.secretKey);
208
+ const result = await this.request('fleet.rooms.auth', { event: proof }, 'fleet.rooms.auth');
209
+ if (result.ok !== true) {
210
+ throw new RoomClientError(String(result.message ?? 'room authentication refused'), 'ROOM_AUTH_REFUSED');
211
+ }
212
+ if (this.ws !== ws)
213
+ throw new RoomClientError('connection replaced during authentication', 'DISCONNECTED');
214
+ const info = {
215
+ pubkey: String(result.pubkey),
216
+ name: String(result.name),
217
+ rooms: Array.isArray(result.rooms) ? result.rooms : [],
218
+ };
219
+ if (info.pubkey !== this.pubkey) {
220
+ throw new RoomClientError('room hub returned another member identity', 'ROOM_AUTH_REFUSED');
221
+ }
222
+ this.ready = true;
223
+ this.everConnected = true;
224
+ this.connectionInfo = info;
225
+ this.reconnectAttempts = 0;
226
+ for (const waiter of this.readyWaiters.splice(0))
227
+ waiter.resolve();
228
+ for (const sub of this.subscriptions.values()) {
229
+ if (!sub.closed)
230
+ this.sendSubscribe(sub);
231
+ }
232
+ this.emit('ready');
233
+ return info;
234
+ }
235
+ sendRaw(message) {
236
+ const ws = this.ws;
237
+ if (!ws || ws.readyState !== WebSocket.OPEN)
238
+ return false;
239
+ ws.send(JSON.stringify(message));
240
+ return true;
241
+ }
242
+ request(type, payload, expect) {
243
+ return new Promise((resolve, reject) => {
244
+ const id = `room-${Date.now().toString(36)}-${(++this.requestCounter).toString(36)}`;
245
+ const timer = setTimeout(() => {
246
+ this.pending.delete(id);
247
+ reject(new RoomClientError(`${type} timed out`, 'TIMEOUT'));
248
+ }, this.requestTimeoutMs);
249
+ this.pending.set(id, { expect, resolve, reject, timer });
250
+ if (!this.sendRaw({ type, id, payload })) {
251
+ clearTimeout(timer);
252
+ this.pending.delete(id);
253
+ reject(new RoomClientError('not connected', 'DISCONNECTED'));
254
+ }
255
+ });
256
+ }
257
+ handleMessage(message) {
258
+ const payload = message.payload && typeof message.payload === 'object'
259
+ ? message.payload
260
+ : {};
261
+ if (message.requestId && this.pending.has(message.requestId)) {
262
+ const pending = this.pending.get(message.requestId);
263
+ this.pending.delete(message.requestId);
264
+ clearTimeout(pending.timer);
265
+ if (message.type === pending.expect)
266
+ pending.resolve(payload);
267
+ else
268
+ pending.reject(new RoomClientError(String(payload.message ?? `unexpected ${message.type}`), String(payload.code ?? 'ERROR')));
269
+ return;
270
+ }
271
+ const subId = typeof payload.subId === 'string' ? payload.subId : undefined;
272
+ const sub = subId ? this.subscriptions.get(subId) : undefined;
273
+ if (!sub || sub.closed)
274
+ return;
275
+ if (message.type === 'fleet.rooms.event')
276
+ this.onEvent(sub, payload);
277
+ else if (message.type === 'fleet.rooms.eose')
278
+ this.onEose(sub, payload);
279
+ else if (message.type === 'fleet.rooms.closed')
280
+ this.onClosed(sub, payload);
281
+ }
282
+ onEvent(sub, payload) {
283
+ const seq = payload.seq;
284
+ if (!Number.isSafeInteger(seq) || seq <= 0)
285
+ return;
286
+ const shape = checkRoomEventShape(payload.event);
287
+ const room = shape.ok ? roomOf(shape.event) : undefined;
288
+ if (!shape.ok || !room || !verifyRoomEvent(shape.event).ok || !roomFiltersMatch(sub.filters, shape.event)) {
289
+ this.emit('invalid-event', { subId: sub.subId, seq });
290
+ return;
291
+ }
292
+ sub.maxSeenSeq = Math.max(sub.maxSeenSeq ?? 0, seq);
293
+ // While replaying, the old cursor is provisional until EOSE tells us which
294
+ // ledger generation produced these records.
295
+ if (sub.cursor && !sub.replaying) {
296
+ sub.cursor = { ...sub.cursor, throughSeq: Math.max(sub.cursor.throughSeq, seq) };
297
+ }
298
+ if (!this.seen.add(`${sub.subId.length}:${sub.subId}:${shape.event.id}`))
299
+ return;
300
+ const event = freezeRoomEvent(shape.event);
301
+ const thread = resolveThread(parseThreadMarkers(event.tags));
302
+ try {
303
+ sub.handlers.onMessage({
304
+ seq: seq,
305
+ room,
306
+ author: event.pubkey,
307
+ event,
308
+ mentions: tagValues(event, 'p'),
309
+ ...(thread ? { thread } : {}),
310
+ text: sanitizePeerText(event.content),
311
+ });
312
+ }
313
+ catch (error) {
314
+ this.emit('handler-error', error);
315
+ }
316
+ }
317
+ onEose(sub, payload) {
318
+ const valid = isStoreId(payload.storeId) &&
319
+ Number.isSafeInteger(payload.throughSeq) && payload.throughSeq >= 0 &&
320
+ typeof payload.live === 'boolean' && typeof payload.truncated === 'boolean' &&
321
+ payload.truncated === !payload.live && typeof payload.gap === 'boolean' &&
322
+ typeof payload.epochChanged === 'boolean';
323
+ if (!valid) {
324
+ this.closeSubscriptionLocally(sub, 'invalid: malformed end-of-stored-events frame');
325
+ return;
326
+ }
327
+ const storeId = payload.storeId;
328
+ const throughSeq = payload.throughSeq;
329
+ const replayCursor = sub.replayCursor;
330
+ const epochChanged = payload.epochChanged === true ||
331
+ (replayCursor !== undefined && replayCursor.storeId !== storeId);
332
+ if ((sub.maxSeenSeq ?? 0) > throughSeq) {
333
+ this.closeSubscriptionLocally(sub, 'invalid: EOSE cursor precedes a delivered event');
334
+ return;
335
+ }
336
+ const replayFloor = replayCursor?.storeId === storeId ? replayCursor.throughSeq : 0;
337
+ if (payload.live === false && throughSeq <= replayFloor) {
338
+ this.closeSubscriptionLocally(sub, 'invalid: truncated replay made no cursor progress');
339
+ return;
340
+ }
341
+ const previousThroughSeq = !epochChanged && replayCursor?.storeId === storeId
342
+ ? replayCursor.throughSeq
343
+ : 0;
344
+ sub.cursor = { storeId, throughSeq: Math.max(previousThroughSeq, throughSeq, sub.maxSeenSeq ?? 0) };
345
+ sub.replaying = false;
346
+ sub.replayCursor = undefined;
347
+ sub.maxSeenSeq = sub.cursor.throughSeq;
348
+ sub.backpressureRetries = 0;
349
+ sub.replayGap ||= payload.gap;
350
+ sub.replayEpochChanged ||= epochChanged;
351
+ if (payload.live === false && sub.autoPage) {
352
+ // Truncated replay: page forward from the cursor.
353
+ this.sendSubscribe(sub, true);
354
+ return;
355
+ }
356
+ this.callHandler(() => sub.handlers.onEose?.({
357
+ storeId,
358
+ throughSeq: sub.cursor?.throughSeq ?? throughSeq,
359
+ gap: sub.replayGap,
360
+ epochChanged: sub.replayEpochChanged,
361
+ truncated: payload.truncated,
362
+ }));
363
+ sub.replayGap = false;
364
+ sub.replayEpochChanged = false;
365
+ }
366
+ onClosed(sub, payload) {
367
+ const message = typeof payload.message === 'string' ? payload.message : 'closed';
368
+ if (message.startsWith('error: backpressure') && sub.backpressureRetries < 5) {
369
+ sub.backpressureRetries++;
370
+ if (isStoreId(payload.storeId) && Number.isSafeInteger(payload.throughSeq) &&
371
+ payload.throughSeq >= 0) {
372
+ const sameGeneration = sub.cursor?.storeId === payload.storeId;
373
+ sub.cursor = {
374
+ storeId: payload.storeId,
375
+ throughSeq: sameGeneration
376
+ ? Math.max(payload.throughSeq, sub.maxSeenSeq ?? 0)
377
+ : payload.throughSeq,
378
+ };
379
+ }
380
+ const retry = setTimeout(() => {
381
+ sub.retryTimers.delete(retry);
382
+ if (!sub.closed && this.ready)
383
+ this.sendSubscribe(sub);
384
+ }, 50 * sub.backpressureRetries);
385
+ sub.retryTimers.add(retry);
386
+ retry.unref?.();
387
+ return;
388
+ }
389
+ this.closeSubscriptionLocally(sub, message);
390
+ }
391
+ closeSubscriptionLocally(sub, message) {
392
+ if (sub.closed)
393
+ return;
394
+ sub.closed = true;
395
+ for (const timer of sub.retryTimers)
396
+ clearTimeout(timer);
397
+ sub.retryTimers.clear();
398
+ this.subscriptions.delete(sub.subId);
399
+ this.callHandler(() => sub.handlers.onClosed?.(message));
400
+ }
401
+ callHandler(callback) {
402
+ try {
403
+ callback();
404
+ }
405
+ catch (error) {
406
+ this.emit('handler-error', error);
407
+ }
408
+ }
409
+ sendSubscribe(sub, continuingPage = false) {
410
+ if (!continuingPage) {
411
+ sub.replayGap = false;
412
+ sub.replayEpochChanged = false;
413
+ }
414
+ const cursor = sub.cursor;
415
+ sub.replayCursor = cursor ? { ...cursor } : undefined;
416
+ sub.replaying = true;
417
+ sub.maxSeenSeq = undefined;
418
+ this.sendRaw({
419
+ type: 'fleet.rooms.subscribe',
420
+ payload: {
421
+ subId: sub.subId,
422
+ filters: sub.filters,
423
+ ...(cursor ? { afterSeq: cursor.throughSeq, storeId: cursor.storeId } : {}),
424
+ },
425
+ });
426
+ }
427
+ waitUntilReady(timeoutMs) {
428
+ if (this.ready)
429
+ return Promise.resolve();
430
+ if (this.closing)
431
+ return Promise.reject(new RoomClientError('client closed', 'CLOSED'));
432
+ return new Promise((resolve, reject) => {
433
+ const waiter = {
434
+ resolve: () => { clearTimeout(timer); resolve(); },
435
+ reject: (error) => { clearTimeout(timer); reject(error); },
436
+ };
437
+ const timer = setTimeout(() => {
438
+ this.readyWaiters = this.readyWaiters.filter((entry) => entry !== waiter);
439
+ reject(new RoomClientError('not reconnected in time', 'DISCONNECTED'));
440
+ }, timeoutMs);
441
+ this.readyWaiters.push(waiter);
442
+ });
443
+ }
444
+ /**
445
+ * Sign and publish. Resolves once the hub has durably stored the message
446
+ * (or already had it). A connection lost before the acknowledgement is
447
+ * retried once with the SAME signed event, which the hub deduplicates.
448
+ */
449
+ async publish(input) {
450
+ const event = signRoomEvent(buildRoomMessage(input, this.pubkey), this.options.secretKey);
451
+ return this.publishSigned(event);
452
+ }
453
+ async publishSigned(event) {
454
+ for (let attempt = 0;; attempt++) {
455
+ try {
456
+ if (!this.ready)
457
+ await this.waitUntilReady(this.requestTimeoutMs);
458
+ const ack = await this.request('fleet.rooms.publish', { event }, 'fleet.rooms.ok');
459
+ if (ack.accepted !== true || ack.id !== event.id ||
460
+ !Number.isSafeInteger(ack.seq) || ack.seq <= 0 || !isStoreId(ack.storeId)) {
461
+ throw new RoomClientError(String(ack.message || 'publication refused'), 'REJECTED');
462
+ }
463
+ const message = String(ack.message ?? '');
464
+ return {
465
+ id: event.id,
466
+ seq: ack.seq,
467
+ duplicate: message.startsWith('duplicate:'),
468
+ storeId: ack.storeId,
469
+ };
470
+ }
471
+ catch (error) {
472
+ const retryable = error instanceof RoomClientError && error.code === 'DISCONNECTED';
473
+ if (!retryable || attempt >= 1 || this.options.autoReconnect === false)
474
+ throw error;
475
+ }
476
+ }
477
+ }
478
+ /** Open a subscription; it survives reconnections until closed. */
479
+ subscribe(subId, filters, handlers, cursor) {
480
+ return this.openSubscription(subId, filters, handlers, cursor, true);
481
+ }
482
+ openSubscription(subId, filters, handlers, cursor, autoPage) {
483
+ if (!/^[A-Za-z0-9._:-]{1,64}$/.test(subId)) {
484
+ throw new RoomClientError('subId must match ^[A-Za-z0-9._:-]{1,64}$', 'INVALID_SUB');
485
+ }
486
+ if (this.subscriptions.has(subId))
487
+ throw new RoomClientError(`subscription ${subId} already open`, 'DUPLICATE_SUB');
488
+ const parsed = parseRoomFilters(filters);
489
+ if (!parsed.ok)
490
+ throw new RoomClientError(`invalid filters: ${parsed.reason}`, 'INVALID_FILTER');
491
+ if (cursor && (!isStoreId(cursor.storeId) || !Number.isSafeInteger(cursor.throughSeq) || cursor.throughSeq < 0)) {
492
+ throw new RoomClientError('cursor must contain a valid storeId and non-negative throughSeq', 'INVALID_CURSOR');
493
+ }
494
+ const sub = {
495
+ subId,
496
+ filters: parsed.filters,
497
+ handlers,
498
+ ...(cursor ? { cursor: { ...cursor } } : {}),
499
+ replaying: false,
500
+ autoPage,
501
+ replayGap: false,
502
+ replayEpochChanged: false,
503
+ closed: false,
504
+ backpressureRetries: 0,
505
+ retryTimers: new Set(),
506
+ };
507
+ this.subscriptions.set(subId, sub);
508
+ if (this.ready)
509
+ this.sendSubscribe(sub);
510
+ return {
511
+ subId,
512
+ cursor: () => (sub.cursor ? { ...sub.cursor } : undefined),
513
+ close: async () => {
514
+ if (sub.closed)
515
+ return;
516
+ sub.closed = true;
517
+ for (const timer of sub.retryTimers)
518
+ clearTimeout(timer);
519
+ sub.retryTimers.clear();
520
+ this.subscriptions.delete(subId);
521
+ if (this.ready) {
522
+ await this.request('fleet.rooms.close', { subId }, 'fleet.rooms.unsubscribed').catch(() => undefined);
523
+ }
524
+ },
525
+ };
526
+ }
527
+ /**
528
+ * One-shot read: stored messages matching `filters` (or after `cursor`),
529
+ * returned with the cursor to resume from next time.
530
+ */
531
+ async fetch(filters, cursor, options = {}) {
532
+ const maxMessages = boundedPositiveInteger('maxMessages', options.maxMessages, DEFAULT_FETCH_MAX_MESSAGES, MAX_FETCH_MESSAGES);
533
+ const maxBytes = boundedPositiveInteger('maxBytes', options.maxBytes, DEFAULT_FETCH_MAX_BYTES, MAX_FETCH_BYTES);
534
+ const subId = `fetch-${Date.now().toString(36)}-${(++this.requestCounter).toString(36)}`;
535
+ const messages = [];
536
+ let retainedBytes = 0;
537
+ let pageTruncated = false;
538
+ let lastRetainedSeq;
539
+ return new Promise((resolve, reject) => {
540
+ let settled = false;
541
+ let handle;
542
+ const fail = (error) => {
543
+ if (settled)
544
+ return;
545
+ settled = true;
546
+ clearTimeout(timer);
547
+ void handle?.close();
548
+ reject(error);
549
+ };
550
+ const timer = setTimeout(() => {
551
+ fail(new RoomClientError('fetch timed out', 'TIMEOUT'));
552
+ }, this.requestTimeoutMs * 3);
553
+ try {
554
+ handle = this.openSubscription(subId, filters, {
555
+ onMessage: (message) => {
556
+ if (pageTruncated)
557
+ return;
558
+ const messageBytes = Buffer.byteLength(JSON.stringify(message), 'utf8');
559
+ if (messages.length >= maxMessages || retainedBytes + messageBytes > maxBytes) {
560
+ if (messages.length === 0) {
561
+ fail(new RoomClientError('one room message exceeds the configured fetch byte limit', 'LIMIT_EXCEEDED'));
562
+ return;
563
+ }
564
+ pageTruncated = true;
565
+ return;
566
+ }
567
+ retainedBytes += messageBytes;
568
+ messages.push(message);
569
+ lastRetainedSeq = message.seq;
570
+ },
571
+ onEose: (info) => {
572
+ if (settled)
573
+ return;
574
+ settled = true;
575
+ clearTimeout(timer);
576
+ void handle?.close();
577
+ const throughSeq = pageTruncated && lastRetainedSeq !== undefined
578
+ ? lastRetainedSeq
579
+ : info.throughSeq;
580
+ resolve({
581
+ messages,
582
+ cursor: { storeId: info.storeId, throughSeq },
583
+ gap: info.gap,
584
+ epochChanged: info.epochChanged,
585
+ truncated: pageTruncated || info.truncated,
586
+ });
587
+ },
588
+ onClosed: (message) => {
589
+ fail(new RoomClientError(message, 'CLOSED'));
590
+ },
591
+ }, cursor, false);
592
+ }
593
+ catch (error) {
594
+ fail(error instanceof RoomClientError
595
+ ? error
596
+ : new RoomClientError(error instanceof Error ? error.message : String(error), 'INVALID_SUB'));
597
+ }
598
+ });
599
+ }
600
+ onSocketClosed() {
601
+ const wasReady = this.ready;
602
+ this.ready = false;
603
+ this.ws = null;
604
+ for (const [id, pending] of this.pending) {
605
+ clearTimeout(pending.timer);
606
+ pending.reject(new RoomClientError('connection closed', 'DISCONNECTED'));
607
+ this.pending.delete(id);
608
+ }
609
+ if (wasReady)
610
+ this.emit('disconnected');
611
+ if (this.closing || this.options.autoReconnect === false || !this.everConnected) {
612
+ for (const waiter of this.readyWaiters.splice(0)) {
613
+ waiter.reject(new RoomClientError('connection closed', 'DISCONNECTED'));
614
+ }
615
+ return;
616
+ }
617
+ this.scheduleReconnect();
618
+ }
619
+ scheduleReconnect() {
620
+ if (this.reconnectTimer || this.closing)
621
+ return;
622
+ if (this.reconnectAttempts >= this.reconnectMaxAttempts) {
623
+ for (const waiter of this.readyWaiters.splice(0)) {
624
+ waiter.reject(new RoomClientError('reconnection attempts exhausted', 'DISCONNECTED'));
625
+ }
626
+ this.emit('reconnect-exhausted');
627
+ return;
628
+ }
629
+ const base = Math.min(this.reconnectMaxDelayMs, this.reconnectInitialDelayMs * 2 ** this.reconnectAttempts);
630
+ const delay = Math.floor(base / 2 + Math.random() * (base / 2));
631
+ this.reconnectAttempts++;
632
+ this.reconnectTimer = setTimeout(() => {
633
+ this.reconnectTimer = null;
634
+ if (this.closing)
635
+ return;
636
+ this.startOpen().catch((error) => {
637
+ this.emit('reconnect-error', error);
638
+ if (error instanceof RoomClientError && error.code === 'ROOM_AUTH_REFUSED') {
639
+ this.closing = true;
640
+ for (const waiter of this.readyWaiters.splice(0))
641
+ waiter.reject(error);
642
+ }
643
+ });
644
+ }, delay);
645
+ this.reconnectTimer.unref?.();
646
+ }
647
+ async close() {
648
+ this.closing = true;
649
+ if (this.reconnectTimer) {
650
+ clearTimeout(this.reconnectTimer);
651
+ this.reconnectTimer = null;
652
+ }
653
+ for (const waiter of this.readyWaiters.splice(0))
654
+ waiter.reject(new RoomClientError('client closed', 'CLOSED'));
655
+ for (const sub of [...this.subscriptions.values()])
656
+ this.closeSubscriptionLocally(sub, 'client closed');
657
+ for (const [id, pending] of this.pending) {
658
+ clearTimeout(pending.timer);
659
+ pending.reject(new RoomClientError('client closed', 'CLOSED'));
660
+ this.pending.delete(id);
661
+ }
662
+ const ws = this.ws;
663
+ if (!ws || ws.readyState === WebSocket.CLOSED)
664
+ return;
665
+ await new Promise((resolve) => {
666
+ ws.once('close', () => resolve());
667
+ try {
668
+ ws.close();
669
+ }
670
+ catch {
671
+ resolve();
672
+ }
673
+ setTimeout(() => {
674
+ try {
675
+ ws.terminate();
676
+ }
677
+ catch {
678
+ // ignore
679
+ }
680
+ resolve();
681
+ }, 1_000).unref?.();
682
+ });
683
+ }
684
+ }
685
+ //# sourceMappingURL=room-client.js.map