@phuetz/code-buddy 2.0.0 → 2.1.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 (403) hide show
  1. package/README.fr.md +56 -0
  2. package/README.md +73 -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 +165 -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.js +19 -2
  13. package/dist/agent/facades/session-facade.d.ts +0 -6
  14. package/dist/agent/facades/session-facade.js +0 -45
  15. package/dist/agent/hermes-claw-migrate.d.ts +0 -21
  16. package/dist/agent/hermes-claw-migrate.js +9 -4
  17. package/dist/agent/hermes-protocol-gateways.js +8 -1
  18. package/dist/agent/repo-profiler.js +17 -4
  19. package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
  20. package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
  21. package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
  22. package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
  23. package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
  24. package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
  25. package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
  26. package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
  27. package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
  28. package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
  29. package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
  30. package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
  31. package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
  32. package/dist/agent/self-improvement/proposal-store.js +79 -17
  33. package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
  34. package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
  35. package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
  36. package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
  37. package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
  38. package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
  39. package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
  40. package/dist/agent/self-improvement/skill-engine.js +385 -13
  41. package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
  42. package/dist/agent/self-improvement/skill-gate.js +17 -2
  43. package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
  44. package/dist/agent/self-improvement/skill-mutator.js +51 -15
  45. package/dist/agent/self-improvement/skill-types.d.ts +2 -1
  46. package/dist/agent/self-improvement/types.d.ts +11 -0
  47. package/dist/agent/tool-handler.d.ts +1 -1
  48. package/dist/agent/tool-handler.js +28 -9
  49. package/dist/auth/profile-manager.js +1 -1
  50. package/dist/channels/companion-channel-turn.d.ts +53 -4
  51. package/dist/channels/companion-channel-turn.js +218 -16
  52. package/dist/channels/slash-parity.d.ts +4 -6
  53. package/dist/channels/slash-parity.js +9 -44
  54. package/dist/cli/session-commands.d.ts +9 -0
  55. package/dist/cli/session-commands.js +45 -5
  56. package/dist/cli/session-picker.d.ts +43 -0
  57. package/dist/cli/session-picker.js +128 -0
  58. package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
  59. package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
  60. package/dist/codebuddy/fleet-tool-defs.d.ts +1 -0
  61. package/dist/codebuddy/fleet-tool-defs.js +24 -0
  62. package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
  63. package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
  64. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
  65. package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
  66. package/dist/codebuddy/providers/provider-openai-compat.js +1 -1
  67. package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
  68. package/dist/codebuddy/ragchat-tool-defs.js +19 -0
  69. package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
  70. package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
  71. package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
  72. package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
  73. package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
  74. package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
  75. package/dist/codebuddy/tools.d.ts +0 -6
  76. package/dist/codebuddy/tools.js +25 -3
  77. package/dist/commands/cli/daemon-commands.js +3 -1
  78. package/dist/commands/cli/evolve-command.js +21 -5
  79. package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
  80. package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
  81. package/dist/commands/cli/fleet-commands.js +29 -0
  82. package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
  83. package/dist/commands/cli/fleet-mission-commands.js +72 -0
  84. package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
  85. package/dist/commands/cli/fleet-rooms-commands.js +289 -0
  86. package/dist/commands/cli/tools-commands.js +2 -1
  87. package/dist/commands/cli/triage-command.d.ts +8 -0
  88. package/dist/commands/cli/triage-command.js +72 -0
  89. package/dist/commands/cli/utility-commands.js +25 -1
  90. package/dist/commands/client-dispatcher.d.ts +7 -0
  91. package/dist/commands/client-dispatcher.js +73 -21
  92. package/dist/commands/cron-cli/index.d.ts +3 -1
  93. package/dist/commands/cron-cli/index.js +25 -0
  94. package/dist/commands/device-auth.d.ts +10 -0
  95. package/dist/commands/device-auth.js +91 -0
  96. package/dist/commands/enhanced-command-handler.d.ts +9 -0
  97. package/dist/commands/enhanced-command-handler.js +28 -6
  98. package/dist/commands/handlers/agent-handlers.d.ts +1 -1
  99. package/dist/commands/handlers/agent-handlers.js +4 -4
  100. package/dist/commands/handlers/channel-handlers.js +53 -2
  101. package/dist/commands/handlers/export-handlers.d.ts +3 -2
  102. package/dist/commands/handlers/export-handlers.js +55 -28
  103. package/dist/commands/handlers/extra-handlers.js +5 -1
  104. package/dist/commands/handlers/grill-me-handler.js +26 -18
  105. package/dist/commands/handlers/memory-handlers.d.ts +6 -2
  106. package/dist/commands/handlers/memory-handlers.js +35 -15
  107. package/dist/commands/handlers/missing-handlers.d.ts +2 -2
  108. package/dist/commands/handlers/missing-handlers.js +4 -4
  109. package/dist/commands/handlers/resources-handler.d.ts +12 -0
  110. package/dist/commands/handlers/resources-handler.js +52 -0
  111. package/dist/commands/handlers/starter-handlers.d.ts +1 -1
  112. package/dist/commands/handlers/starter-handlers.js +32 -2
  113. package/dist/commands/handlers/switch-handler.d.ts +1 -1
  114. package/dist/commands/handlers/switch-handler.js +15 -0
  115. package/dist/commands/handlers/test-handlers.js +4 -86
  116. package/dist/commands/handlers/ui-handlers.js +11 -5
  117. package/dist/commands/login-chatgpt.d.ts +8 -0
  118. package/dist/commands/login-chatgpt.js +17 -0
  119. package/dist/commands/mcp-import.d.ts +11 -0
  120. package/dist/commands/mcp-import.js +67 -0
  121. package/dist/commands/mcp-invoke.d.ts +2 -0
  122. package/dist/commands/mcp-invoke.js +68 -0
  123. package/dist/commands/mcp.js +4 -0
  124. package/dist/commands/pipeline.d.ts +7 -1
  125. package/dist/commands/pipeline.js +104 -7
  126. package/dist/commands/resources.d.ts +2 -0
  127. package/dist/commands/resources.js +64 -0
  128. package/dist/commands/skills-cli/index.js +35 -12
  129. package/dist/commands/slash/builtin-commands.js +16 -3
  130. package/dist/commands/slash/index.d.ts +3 -1
  131. package/dist/commands/slash/index.js +2 -0
  132. package/dist/commands/slash/surfaces.d.ts +53 -0
  133. package/dist/commands/slash/surfaces.js +177 -0
  134. package/dist/commands/slash/types.d.ts +17 -0
  135. package/dist/commands/try.d.ts +1 -1
  136. package/dist/commands/try.js +1 -1
  137. package/dist/companion/companion-identity.d.ts +43 -0
  138. package/dist/companion/companion-identity.js +136 -0
  139. package/dist/companion/companion-toolset.d.ts +66 -0
  140. package/dist/companion/companion-toolset.js +368 -0
  141. package/dist/companion/companion-turn.d.ts +10 -2
  142. package/dist/companion/companion-turn.js +38 -1
  143. package/dist/config/code-exec-policy.d.ts +25 -0
  144. package/dist/config/code-exec-policy.js +39 -0
  145. package/dist/config/model-tools.d.ts +9 -0
  146. package/dist/config/model-tools.js +4 -0
  147. package/dist/context/context-manager-v2.js +2 -2
  148. package/dist/context/lm-resizer-compressor.js +3 -2
  149. package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
  150. package/dist/context/lm-resizer-diagnostics.js +30 -0
  151. package/dist/context/tool-output-masking.js +3 -4
  152. package/dist/curator/curator.d.ts +6 -0
  153. package/dist/curator/curator.js +53 -3
  154. package/dist/daemon/cron-agent-bridge.d.ts +6 -0
  155. package/dist/daemon/cron-agent-bridge.js +31 -4
  156. package/dist/doctor/index.d.ts +11 -2
  157. package/dist/doctor/index.js +84 -21
  158. package/dist/doctor/integrations.d.ts +179 -0
  159. package/dist/doctor/integrations.js +227 -0
  160. package/dist/doctor/triage.d.ts +108 -0
  161. package/dist/doctor/triage.js +304 -0
  162. package/dist/fleet/collaboration.d.ts +44 -0
  163. package/dist/fleet/collaboration.js +170 -0
  164. package/dist/fleet/fleet-listener.d.ts +1 -0
  165. package/dist/fleet/fleet-listener.js +87 -46
  166. package/dist/fleet/fleet-registry.d.ts +2 -0
  167. package/dist/fleet/resource-catalog.d.ts +245 -0
  168. package/dist/fleet/resource-catalog.js +211 -0
  169. package/dist/fleet/rooms/room-access.d.ts +138 -0
  170. package/dist/fleet/rooms/room-access.js +196 -0
  171. package/dist/fleet/rooms/room-client.d.ts +158 -0
  172. package/dist/fleet/rooms/room-client.js +685 -0
  173. package/dist/fleet/rooms/room-event.d.ts +139 -0
  174. package/dist/fleet/rooms/room-event.js +330 -0
  175. package/dist/fleet/rooms/room-filter.d.ts +48 -0
  176. package/dist/fleet/rooms/room-filter.js +152 -0
  177. package/dist/fleet/rooms/room-hub.d.ts +154 -0
  178. package/dist/fleet/rooms/room-hub.js +363 -0
  179. package/dist/fleet/rooms/room-identity.d.ts +26 -0
  180. package/dist/fleet/rooms/room-identity.js +82 -0
  181. package/dist/fleet/rooms/room-lock.d.ts +34 -0
  182. package/dist/fleet/rooms/room-lock.js +180 -0
  183. package/dist/fleet/rooms/room-observations.d.ts +7 -0
  184. package/dist/fleet/rooms/room-observations.js +61 -0
  185. package/dist/fleet/rooms/room-server.d.ts +28 -0
  186. package/dist/fleet/rooms/room-server.js +75 -0
  187. package/dist/fleet/rooms/room-store.d.ts +119 -0
  188. package/dist/fleet/rooms/room-store.js +448 -0
  189. package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
  190. package/dist/fleet/rooms/room-ws-bridge.js +137 -0
  191. package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
  192. package/dist/fleet/sensory-mission-bridge.js +136 -0
  193. package/dist/git/worktree-sessions.js +1 -0
  194. package/dist/harness/contract.d.ts +4 -4
  195. package/dist/harness/fleet-supervisor.d.ts +16 -0
  196. package/dist/harness/fleet-supervisor.js +64 -0
  197. package/dist/harness/index.d.ts +4 -0
  198. package/dist/harness/index.js +4 -0
  199. package/dist/harness/mission-runner.d.ts +3 -0
  200. package/dist/harness/mission-runner.js +53 -0
  201. package/dist/harness/mission-store.d.ts +93 -0
  202. package/dist/harness/mission-store.js +297 -0
  203. package/dist/harness/tool-harness.d.ts +56 -0
  204. package/dist/harness/tool-harness.js +153 -0
  205. package/dist/hooks/use-enhanced-input.js +48 -4
  206. package/dist/hooks/use-input-handler.d.ts +1 -0
  207. package/dist/hooks/use-input-handler.js +82 -22
  208. package/dist/hooks/use-input-history.js +1 -0
  209. package/dist/identity/lisa-introspection.js +5 -2
  210. package/dist/identity/operational-self-model.d.ts +2 -0
  211. package/dist/identity/operational-self-model.js +4 -0
  212. package/dist/index.js +52 -6
  213. package/dist/mcp/client.js +6 -0
  214. package/dist/mcp/config.d.ts +20 -0
  215. package/dist/mcp/config.js +121 -8
  216. package/dist/mcp/import-normalize.d.ts +12 -0
  217. package/dist/mcp/import-normalize.js +114 -0
  218. package/dist/mcp/transports.d.ts +10 -5
  219. package/dist/mcp/transports.js +61 -99
  220. package/dist/mcp/types.d.ts +5 -0
  221. package/dist/media/comfyui-recipe-contract.d.ts +7 -7
  222. package/dist/memory/facts-memory.d.ts +11 -1
  223. package/dist/memory/facts-memory.js +47 -8
  224. package/dist/memory/memory-candidate-queue.d.ts +2 -2
  225. package/dist/memory/memory-candidate-queue.js +6 -6
  226. package/dist/memory/persistent-memory.d.ts +13 -8
  227. package/dist/memory/persistent-memory.js +77 -30
  228. package/dist/nodes/device-node.js +5 -9
  229. package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
  230. package/dist/observability/run-event-writer.d.ts +21 -0
  231. package/dist/observability/run-event-writer.js +81 -0
  232. package/dist/observability/run-store.d.ts +5 -0
  233. package/dist/observability/run-store.js +18 -13
  234. package/dist/observability/run-trajectory.js +1 -1
  235. package/dist/optimization/prompt-cache.d.ts +19 -2
  236. package/dist/optimization/prompt-cache.js +41 -6
  237. package/dist/orchestration/orchestrator.d.ts +21 -0
  238. package/dist/orchestration/orchestrator.js +122 -15
  239. package/dist/persistence/session-content.d.ts +8 -0
  240. package/dist/persistence/session-content.js +76 -0
  241. package/dist/persistence/session-handoff.d.ts +64 -0
  242. package/dist/persistence/session-handoff.js +80 -0
  243. package/dist/persistence/session-history.d.ts +4 -0
  244. package/dist/persistence/session-history.js +38 -0
  245. package/dist/persistence/session-store.d.ts +9 -0
  246. package/dist/persistence/session-store.js +48 -20
  247. package/dist/personas/persona-manager.js +17 -1
  248. package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
  249. package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
  250. package/dist/plugins/plugin-manager.js +10 -1
  251. package/dist/prompts/prompt-manager.js +11 -4
  252. package/dist/prompts/system-base.js +6 -2
  253. package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
  254. package/dist/protocols/a2a/codebuddy-executor.js +9 -8
  255. package/dist/protocols/a2a/index.d.ts +1 -1
  256. package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
  257. package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
  258. package/dist/protocols/a2a/peer-config.d.ts +20 -0
  259. package/dist/protocols/a2a/peer-config.js +55 -0
  260. package/dist/providers/codex-oauth.js +30 -6
  261. package/dist/providers/model-provider-compat.js +2 -1
  262. package/dist/sandbox/os-sandbox.d.ts +1 -1
  263. package/dist/scheduler/cron-scheduler.d.ts +15 -2
  264. package/dist/scheduler/cron-scheduler.js +35 -4
  265. package/dist/scheduler/job-notepad.d.ts +69 -0
  266. package/dist/scheduler/job-notepad.js +339 -0
  267. package/dist/security/audit-logger.d.ts +1 -1
  268. package/dist/security/compute-confinement.d.ts +4 -0
  269. package/dist/security/compute-confinement.js +89 -0
  270. package/dist/security/session-encryption.d.ts +4 -0
  271. package/dist/security/session-encryption.js +31 -0
  272. package/dist/sensory/voice-loop.d.ts +17 -0
  273. package/dist/sensory/voice-loop.js +72 -15
  274. package/dist/server/auth/device-session-context.d.ts +9 -0
  275. package/dist/server/auth/device-session-context.js +13 -0
  276. package/dist/server/auth/device-store.d.ts +89 -0
  277. package/dist/server/auth/device-store.js +261 -0
  278. package/dist/server/auth/device-token.d.ts +3 -0
  279. package/dist/server/auth/device-token.js +6 -0
  280. package/dist/server/auth/jwt.js +4 -0
  281. package/dist/server/index.d.ts +0 -10
  282. package/dist/server/index.js +28 -0
  283. package/dist/server/middleware/auth.js +3 -2
  284. package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
  285. package/dist/server/routes/a2a-jsonrpc.js +66 -0
  286. package/dist/server/routes/a2a-protocol.d.ts +12 -0
  287. package/dist/server/routes/a2a-protocol.js +1 -1
  288. package/dist/server/routes/device-auth.d.ts +4 -0
  289. package/dist/server/routes/device-auth.js +40 -0
  290. package/dist/server/types.d.ts +4 -0
  291. package/dist/server/websocket/handler.d.ts +13 -0
  292. package/dist/server/websocket/handler.js +102 -17
  293. package/dist/services/prompt-builder.d.ts +2 -0
  294. package/dist/services/prompt-builder.js +13 -5
  295. package/dist/services/runtime-settings-context.d.ts +73 -0
  296. package/dist/services/runtime-settings-context.js +69 -0
  297. package/dist/skills/executor.js +2 -0
  298. package/dist/skills/hub.js +16 -14
  299. package/dist/skills/local-inventory.d.ts +21 -0
  300. package/dist/skills/local-inventory.js +72 -0
  301. package/dist/skills/skill-loader.js +2 -2
  302. package/dist/skills/skill-usage-store.d.ts +49 -0
  303. package/dist/skills/skill-usage-store.js +121 -0
  304. package/dist/testing/ai-integration-tests.js +8 -5
  305. package/dist/themes/theme-manager.d.ts +12 -0
  306. package/dist/themes/theme-manager.js +29 -0
  307. package/dist/themes/theme-schema.d.ts +24 -24
  308. package/dist/tools/a2a-call-tool.d.ts +34 -0
  309. package/dist/tools/a2a-call-tool.js +75 -0
  310. package/dist/tools/bash/streaming-executor.d.ts +1 -0
  311. package/dist/tools/bash/streaming-executor.js +78 -90
  312. package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
  313. package/dist/tools/code-exec-preflight-runner.js +254 -0
  314. package/dist/tools/code-exec-preflight.d.ts +44 -0
  315. package/dist/tools/code-exec-preflight.js +501 -0
  316. package/dist/tools/code-exec-tool.d.ts +48 -1
  317. package/dist/tools/code-exec-tool.js +191 -47
  318. package/dist/tools/comment-watcher.js +29 -12
  319. package/dist/tools/core-code-inspection.d.ts +56 -0
  320. package/dist/tools/core-code-inspection.js +86 -0
  321. package/dist/tools/create-skill-tool.d.ts +2 -3
  322. package/dist/tools/create-skill-tool.js +16 -75
  323. package/dist/tools/cronjob-tool.d.ts +1 -0
  324. package/dist/tools/cronjob-tool.js +1 -0
  325. package/dist/tools/env-doctor-tool.d.ts +2 -0
  326. package/dist/tools/env-doctor-tool.js +4 -3
  327. package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
  328. package/dist/tools/execute-code-rpc-invoker.js +35 -19
  329. package/dist/tools/execute-code-runner.d.ts +2 -0
  330. package/dist/tools/execute-code-runner.js +52 -17
  331. package/dist/tools/fleet-room-tool.d.ts +104 -0
  332. package/dist/tools/fleet-room-tool.js +124 -0
  333. package/dist/tools/integration-tool-hints.d.ts +4 -0
  334. package/dist/tools/integration-tool-hints.js +19 -0
  335. package/dist/tools/list-peers-tool.d.ts +3 -0
  336. package/dist/tools/list-peers-tool.js +11 -4
  337. package/dist/tools/media-generation-tool.d.ts +16 -1
  338. package/dist/tools/media-generation-tool.js +341 -9
  339. package/dist/tools/merge-conflict-tool.js +53 -25
  340. package/dist/tools/metadata.d.ts +1 -5
  341. package/dist/tools/metadata.js +21 -28
  342. package/dist/tools/ragchat-tool.d.ts +39 -0
  343. package/dist/tools/ragchat-tool.js +105 -0
  344. package/dist/tools/registry/fleet-tools.d.ts +9 -0
  345. package/dist/tools/registry/fleet-tools.js +22 -1
  346. package/dist/tools/registry/knowledge-tools.d.ts +2 -2
  347. package/dist/tools/registry/knowledge-tools.js +2 -2
  348. package/dist/tools/registry/lessons-tools.d.ts +7 -0
  349. package/dist/tools/registry/lessons-tools.js +43 -3
  350. package/dist/tools/registry/memory-tools.d.ts +1 -1
  351. package/dist/tools/registry/memory-tools.js +8 -8
  352. package/dist/tools/registry/research-tools.d.ts +1 -0
  353. package/dist/tools/registry/research-tools.js +4 -13
  354. package/dist/tools/registry/self-describe-tools.d.ts +1 -1
  355. package/dist/tools/registry/self-describe-tools.js +43 -30
  356. package/dist/tools/resource-catalog-tool.d.ts +38 -0
  357. package/dist/tools/resource-catalog-tool.js +46 -0
  358. package/dist/tools/skills-inspection-tool.js +30 -1
  359. package/dist/tools/stream-tool-output.d.ts +3 -0
  360. package/dist/tools/stream-tool-output.js +33 -0
  361. package/dist/tools/tool-call-scheduler.d.ts +10 -0
  362. package/dist/tools/tool-call-scheduler.js +58 -0
  363. package/dist/tools/tool-effect.d.ts +5 -0
  364. package/dist/tools/tool-effect.js +26 -0
  365. package/dist/tools/tool-search.d.ts +12 -11
  366. package/dist/tools/tool-search.js +67 -26
  367. package/dist/tools/types.d.ts +6 -0
  368. package/dist/ui/components/ChatHistory.d.ts +1 -1
  369. package/dist/ui/components/ChatHistory.js +2 -2
  370. package/dist/ui/components/ChatInput.d.ts +2 -1
  371. package/dist/ui/components/ChatInput.js +42 -88
  372. package/dist/ui/components/ChatInterface.d.ts +4 -2
  373. package/dist/ui/components/ChatInterface.js +27 -63
  374. package/dist/ui/components/CommandSuggestions.d.ts +0 -1
  375. package/dist/ui/components/CommandSuggestions.js +4 -6
  376. package/dist/ui/components/FileAutocomplete.js +1 -1
  377. package/dist/ui/components/KeyboardHelp.js +8 -6
  378. package/dist/ui/components/ModelSelection.js +6 -5
  379. package/dist/ui/components/StatusBar.d.ts +2 -1
  380. package/dist/ui/components/StatusBar.js +8 -7
  381. package/dist/ui/context/theme-context.js +3 -2
  382. package/dist/ui/index.d.ts +1 -1
  383. package/dist/ui/index.js +1 -1
  384. package/dist/ui/utils/markdown-renderer.js +4 -13
  385. package/dist/utils/atomic-write.d.ts +22 -0
  386. package/dist/utils/atomic-write.js +29 -0
  387. package/dist/utils/bounded-output.d.ts +16 -0
  388. package/dist/utils/bounded-output.js +63 -0
  389. package/dist/utils/device-store-file.d.ts +4 -0
  390. package/dist/utils/device-store-file.js +40 -0
  391. package/dist/utils/export-manager.d.ts +9 -0
  392. package/dist/utils/export-manager.js +19 -0
  393. package/dist/utils/first-use-hints.d.ts +25 -0
  394. package/dist/utils/first-use-hints.js +58 -0
  395. package/dist/utils/history-manager.js +1 -1
  396. package/dist/utils/update-notifier.js +16 -18
  397. package/dist/wizard/environment-detection.d.ts +5 -1
  398. package/dist/wizard/environment-detection.js +49 -7
  399. package/dist/wizard/onboarding.d.ts +5 -1
  400. package/dist/wizard/onboarding.js +90 -30
  401. package/dist/wizard/provider-onboarding.d.ts +2 -0
  402. package/dist/wizard/provider-onboarding.js +55 -17
  403. package/package.json +7 -3
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Fleet rooms — transport-agnostic hub.
3
+ *
4
+ * Adapted from block/buzz @ 4cd82f51 (Apache-2.0):
5
+ * - publish pipeline order and machine-readable `OK` prefixes
6
+ * (`auth-required:`, `invalid:`, `restricted:`, `rate-limited:`, `duplicate:`,
7
+ * `error:`) from `crates/buzz-relay/src/handlers/event.rs` + `ARCHITECTURE.md` §4;
8
+ * - REQ handling (access checked before anything is read or registered, stored
9
+ * events then `EOSE` then live fan-out, bounded subscriptions and history)
10
+ * from `crates/buzz-relay/src/handlers/req.rs` + `ARCHITECTURE.md` §5;
11
+ * - NIP-42 challenge/response from `ARCHITECTURE.md` §3 and `crates/buzz-auth`.
12
+ *
13
+ * Messages are DATA. The hub stores and forwards signed events; nothing here
14
+ * turns a message into a prompt, a tool call or an agent turn, so a peer cannot
15
+ * start work on another member and no reply loop can form through the hub.
16
+ *
17
+ * @module fleet/rooms/room-hub
18
+ */
19
+ import { type ReadonlyRoomEvent } from './room-event.js';
20
+ import type { RoomAccessLevel, RoomAccessPolicy } from './room-access.js';
21
+ import { type RoomRecord, type RoomStore } from './room-store.js';
22
+ export interface RoomHubOptions {
23
+ store: RoomStore;
24
+ access: RoomAccessPolicy;
25
+ /** Canonical audiences accepted in auth proofs (see `canonicalRoomAudience`). */
26
+ audiences: readonly string[];
27
+ maxSubscriptionsPerSession?: number;
28
+ /** Accepted publications per member key per minute. */
29
+ publishPerMinute?: number;
30
+ maxFutureSkewSec?: number;
31
+ maxPastSkewSec?: number;
32
+ authSkewSec?: number;
33
+ challengeTtlMs?: number;
34
+ maxAuthAttempts?: number;
35
+ /** Clock in ms (tests). */
36
+ now?: () => number;
37
+ }
38
+ export interface RoomTransport {
39
+ readonly connectionId: string;
40
+ /** Server-derived WebSocket principal (`key:…`, `user:…`). */
41
+ readonly principalId: string;
42
+ send(frame: RoomStreamFrame): boolean;
43
+ isBackpressured(): boolean;
44
+ }
45
+ export type RoomStreamFrame = {
46
+ type: 'fleet.rooms.event';
47
+ payload: {
48
+ subId: string;
49
+ seq: number;
50
+ event: ReadonlyRoomEvent;
51
+ };
52
+ } | {
53
+ type: 'fleet.rooms.eose';
54
+ payload: {
55
+ subId: string;
56
+ storeId: string;
57
+ throughSeq: number;
58
+ /** false: the replay was truncated; resubscribe after `throughSeq`. */
59
+ live: boolean;
60
+ truncated: boolean;
61
+ /** Records after the cursor were evicted; the replay is incomplete. */
62
+ gap: boolean;
63
+ /** The cursor named another ledger generation and was ignored. */
64
+ epochChanged: boolean;
65
+ };
66
+ } | {
67
+ type: 'fleet.rooms.closed';
68
+ payload: {
69
+ subId: string;
70
+ message: string;
71
+ storeId: string;
72
+ throughSeq: number | null;
73
+ };
74
+ };
75
+ export type RoomAuthResult = {
76
+ ok: true;
77
+ pubkey: string;
78
+ name: string;
79
+ rooms: Array<{
80
+ room: string;
81
+ access: RoomAccessLevel;
82
+ description?: string;
83
+ }>;
84
+ } | {
85
+ ok: false;
86
+ message: string;
87
+ };
88
+ export interface RoomPublishResult {
89
+ id: string;
90
+ accepted: boolean;
91
+ message: string;
92
+ seq?: number;
93
+ storeId: string;
94
+ }
95
+ export declare class RoomSession {
96
+ private readonly hub;
97
+ readonly transport: RoomTransport;
98
+ pubkey: string | undefined;
99
+ private challenge;
100
+ private authAttempts;
101
+ private readonly subscriptions;
102
+ private disposed;
103
+ constructor(hub: RoomHub, transport: RoomTransport);
104
+ get isDisposed(): boolean;
105
+ /** Issue a fresh single-use challenge (replaces any previous one). */
106
+ hello(): {
107
+ challenge: string;
108
+ expiresAt: string;
109
+ };
110
+ authenticate(input: unknown): RoomAuthResult;
111
+ publish(input: unknown): RoomPublishResult;
112
+ subscribe(request: unknown): {
113
+ ok: true;
114
+ } | {
115
+ ok: false;
116
+ message: string;
117
+ };
118
+ unsubscribe(subId: unknown): {
119
+ subId: string;
120
+ closed: boolean;
121
+ };
122
+ /** Hub fan-out for one stored record. */
123
+ deliver(record: RoomRecord): void;
124
+ private closeSubscription;
125
+ private closeAll;
126
+ get subscriptionCount(): number;
127
+ dispose(): void;
128
+ }
129
+ export declare class RoomHub {
130
+ readonly store: RoomStore;
131
+ readonly access: RoomAccessPolicy;
132
+ readonly audiences: readonly string[];
133
+ readonly maxSubscriptionsPerSession: number;
134
+ readonly publishPerMinute: number;
135
+ readonly maxFutureSkewSec: number;
136
+ readonly maxPastSkewSec: number;
137
+ readonly authSkewSec: number;
138
+ readonly challengeTtlMs: number;
139
+ readonly maxAuthAttempts: number;
140
+ readonly now: () => number;
141
+ private readonly sessions;
142
+ private readonly publishWindows;
143
+ constructor(options: RoomHubOptions);
144
+ open(transport: RoomTransport): RoomSession;
145
+ /** @internal */
146
+ forget(session: RoomSession): void;
147
+ /** @internal Fixed one-minute window per member key. */
148
+ takePublishToken(pubkey: string): boolean;
149
+ /** @internal */
150
+ deliver(record: RoomRecord): void;
151
+ get sessionCount(): number;
152
+ /** Dispose every session. The store stays owned by the caller. */
153
+ close(): void;
154
+ }
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Fleet rooms — transport-agnostic hub.
3
+ *
4
+ * Adapted from block/buzz @ 4cd82f51 (Apache-2.0):
5
+ * - publish pipeline order and machine-readable `OK` prefixes
6
+ * (`auth-required:`, `invalid:`, `restricted:`, `rate-limited:`, `duplicate:`,
7
+ * `error:`) from `crates/buzz-relay/src/handlers/event.rs` + `ARCHITECTURE.md` §4;
8
+ * - REQ handling (access checked before anything is read or registered, stored
9
+ * events then `EOSE` then live fan-out, bounded subscriptions and history)
10
+ * from `crates/buzz-relay/src/handlers/req.rs` + `ARCHITECTURE.md` §5;
11
+ * - NIP-42 challenge/response from `ARCHITECTURE.md` §3 and `crates/buzz-auth`.
12
+ *
13
+ * Messages are DATA. The hub stores and forwards signed events; nothing here
14
+ * turns a message into a prompt, a tool call or an agent turn, so a peer cannot
15
+ * start work on another member and no reply loop can form through the hub.
16
+ *
17
+ * @module fleet/rooms/room-hub
18
+ */
19
+ import { randomBytes } from 'crypto';
20
+ import { logger } from '../../utils/logger.js';
21
+ import { canonicalRoomAudience, checkRoomEventShape, MAX_ROOM_MENTIONS, ROOM_AUTH_KIND, ROOM_MESSAGE_KIND, roomOf, tagValues, verifyRoomEvent, } from './room-event.js';
22
+ import { parseRoomFilters, roomFiltersMatch, roomsReferenced } from './room-filter.js';
23
+ import { RoomStoreError } from './room-store.js';
24
+ const SUB_ID_PATTERN = /^[A-Za-z0-9._:-]{1,64}$/;
25
+ function finitePositive(name, value, fallback) {
26
+ if (value === undefined)
27
+ return fallback;
28
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
29
+ throw new RangeError(`${name} must be a finite number > 0`);
30
+ }
31
+ return value;
32
+ }
33
+ export class RoomSession {
34
+ hub;
35
+ transport;
36
+ pubkey;
37
+ challenge;
38
+ authAttempts = 0;
39
+ subscriptions = new Map();
40
+ disposed = false;
41
+ constructor(hub, transport) {
42
+ this.hub = hub;
43
+ this.transport = transport;
44
+ }
45
+ get isDisposed() {
46
+ return this.disposed;
47
+ }
48
+ /** Issue a fresh single-use challenge (replaces any previous one). */
49
+ hello() {
50
+ const value = randomBytes(32).toString('hex');
51
+ const expiresAt = this.hub.now() + this.hub.challengeTtlMs;
52
+ this.challenge = { value, expiresAt };
53
+ return { challenge: value, expiresAt: new Date(expiresAt).toISOString() };
54
+ }
55
+ authenticate(input) {
56
+ if (this.disposed)
57
+ return { ok: false, message: 'error: session closed' };
58
+ this.authAttempts++;
59
+ const challenge = this.challenge;
60
+ this.challenge = undefined;
61
+ if (this.authAttempts > this.hub.maxAuthAttempts) {
62
+ return { ok: false, message: 'restricted: too many authentication attempts on this connection' };
63
+ }
64
+ if (!challenge || this.hub.now() > challenge.expiresAt) {
65
+ return { ok: false, message: 'auth-required: request a fresh challenge with fleet.rooms.hello' };
66
+ }
67
+ const shape = checkRoomEventShape(input);
68
+ if (!shape.ok)
69
+ return { ok: false, message: `invalid: ${shape.reason}` };
70
+ const event = shape.event;
71
+ if (event.kind !== ROOM_AUTH_KIND)
72
+ return { ok: false, message: 'invalid: auth proof must be kind 22242' };
73
+ const challengeTags = event.tags.filter((tag) => tag[0] === 'challenge');
74
+ if (challengeTags.length !== 1 || challengeTags[0]?.length !== 2 || challengeTags[0][1] !== challenge.value) {
75
+ return { ok: false, message: 'invalid: auth proof does not answer this challenge' };
76
+ }
77
+ const relayTags = event.tags.filter((tag) => tag[0] === 'relay');
78
+ let audience;
79
+ try {
80
+ audience = relayTags.length === 1 && relayTags[0]?.length === 2
81
+ ? canonicalRoomAudience(relayTags[0][1])
82
+ : undefined;
83
+ }
84
+ catch {
85
+ audience = undefined;
86
+ }
87
+ if (!audience || !this.hub.audiences.includes(audience)) {
88
+ return { ok: false, message: 'restricted: auth proof is addressed to another hub' };
89
+ }
90
+ const nowSec = Math.floor(this.hub.now() / 1000);
91
+ if (Math.abs(event.created_at - nowSec) > this.hub.authSkewSec) {
92
+ return { ok: false, message: 'invalid: auth proof timestamp outside the allowed window' };
93
+ }
94
+ const verified = verifyRoomEvent(event);
95
+ if (!verified.ok)
96
+ return { ok: false, message: `invalid: ${verified.reason}` };
97
+ const admission = this.hub.access.admit(event.pubkey, this.transport.principalId);
98
+ if (!admission.ok)
99
+ return { ok: false, message: admission.reason };
100
+ if (this.pubkey !== event.pubkey)
101
+ this.closeAll('restricted: room identity changed');
102
+ this.pubkey = event.pubkey;
103
+ this.authAttempts = 0;
104
+ return {
105
+ ok: true,
106
+ pubkey: event.pubkey,
107
+ name: admission.member.name,
108
+ rooms: this.hub.access.roomsFor(event.pubkey),
109
+ };
110
+ }
111
+ publish(input) {
112
+ const storeId = this.hub.store.storeId;
113
+ const claimedId = input && typeof input === 'object' && typeof input.id === 'string'
114
+ ? (input.id.match(/^[0-9a-f]{64}$/)?.[0] ?? '')
115
+ : '';
116
+ const reject = (message) => ({ id: claimedId, accepted: false, message, storeId });
117
+ if (this.disposed)
118
+ return reject('error: session closed');
119
+ if (!this.pubkey)
120
+ return reject('auth-required: authenticate with fleet.rooms.auth first');
121
+ const admission = this.hub.access.admit(this.pubkey, this.transport.principalId);
122
+ if (!admission.ok)
123
+ return reject(admission.reason);
124
+ const shape = checkRoomEventShape(input);
125
+ if (!shape.ok)
126
+ return reject(`invalid: ${shape.reason}`);
127
+ const event = shape.event;
128
+ if (event.pubkey !== this.pubkey)
129
+ return reject('invalid: event pubkey does not match authenticated identity');
130
+ if (event.kind === ROOM_AUTH_KIND)
131
+ return reject('invalid: AUTH events cannot be published');
132
+ if (event.kind !== ROOM_MESSAGE_KIND)
133
+ return reject('invalid: only kind 9 room messages are accepted');
134
+ const room = roomOf(event);
135
+ if (!room)
136
+ return reject('invalid: message must carry exactly one valid h tag');
137
+ if (tagValues(event, 'p').length > MAX_ROOM_MENTIONS) {
138
+ return reject(`invalid: at most ${MAX_ROOM_MENTIONS} mentions`);
139
+ }
140
+ const verified = verifyRoomEvent(event);
141
+ if (!verified.ok)
142
+ return reject(`invalid: ${verified.reason}`);
143
+ if (!this.hub.access.canWrite(this.pubkey, room)) {
144
+ return reject(`restricted: no write access to room ${room}`);
145
+ }
146
+ const existing = this.hub.store.get(event.id);
147
+ if (existing) {
148
+ return { id: event.id, accepted: true, message: 'duplicate: already stored', seq: existing.seq, storeId };
149
+ }
150
+ const nowSec = Math.floor(this.hub.now() / 1000);
151
+ if (event.created_at > nowSec + this.hub.maxFutureSkewSec) {
152
+ return reject('invalid: created_at is too far in the future');
153
+ }
154
+ if (event.created_at < nowSec - this.hub.maxPastSkewSec) {
155
+ return reject('invalid: created_at is too old');
156
+ }
157
+ if (!this.hub.takePublishToken(this.pubkey)) {
158
+ return reject('rate-limited: too many messages, slow down');
159
+ }
160
+ let stored;
161
+ try {
162
+ const result = this.hub.store.append(event, this.hub.now());
163
+ stored = result.record;
164
+ if (result.status === 'duplicate') {
165
+ return { id: event.id, accepted: true, message: 'duplicate: already stored', seq: stored.seq, storeId };
166
+ }
167
+ }
168
+ catch (error) {
169
+ if (error instanceof RoomStoreError && error.code === 'FULL')
170
+ return reject('error: room storage full');
171
+ logger.error('[fleet-rooms] append failed', {
172
+ error: error instanceof Error ? error.message : String(error),
173
+ });
174
+ return reject('error: room storage unavailable');
175
+ }
176
+ this.hub.deliver(stored);
177
+ return { id: event.id, accepted: true, message: '', seq: stored.seq, storeId };
178
+ }
179
+ subscribe(request) {
180
+ const storeId = this.hub.store.storeId;
181
+ const raw = request && typeof request === 'object' ? request : {};
182
+ const subId = raw.subId;
183
+ if (typeof subId !== 'string' || !SUB_ID_PATTERN.test(subId)) {
184
+ return { ok: false, message: 'invalid: subId must match ^[A-Za-z0-9._:-]{1,64}$' };
185
+ }
186
+ const closed = (message, throughSeq = null) => {
187
+ this.transport.send({ type: 'fleet.rooms.closed', payload: { subId, message, storeId, throughSeq } });
188
+ return { ok: true };
189
+ };
190
+ // NIP-01: a REQ reusing an id replaces the previous subscription.
191
+ this.subscriptions.delete(subId);
192
+ if (this.disposed)
193
+ return closed('error: session closed');
194
+ if (!this.pubkey)
195
+ return closed('auth-required: authenticate with fleet.rooms.auth first');
196
+ const admission = this.hub.access.admit(this.pubkey, this.transport.principalId);
197
+ if (!admission.ok)
198
+ return closed(admission.reason);
199
+ const parsed = parseRoomFilters(raw.filters);
200
+ if (!parsed.ok)
201
+ return closed(`invalid: ${parsed.reason}`);
202
+ const rooms = roomsReferenced(parsed.filters);
203
+ const denied = rooms.find((room) => !this.hub.access.canRead(this.pubkey, room));
204
+ if (denied)
205
+ return closed(`restricted: no read access to room ${denied}`);
206
+ if (this.subscriptions.size >= this.hub.maxSubscriptionsPerSession) {
207
+ return closed('error: too many subscriptions');
208
+ }
209
+ let afterSeq;
210
+ let epochChanged = false;
211
+ if (raw.afterSeq !== undefined) {
212
+ if (!Number.isSafeInteger(raw.afterSeq) || raw.afterSeq < 0 || typeof raw.storeId !== 'string') {
213
+ return closed('invalid: afterSeq must be a non-negative integer sent with its storeId');
214
+ }
215
+ if (raw.storeId === storeId)
216
+ afterSeq = raw.afterSeq;
217
+ else
218
+ epochChanged = true;
219
+ }
220
+ const latestSeq = this.hub.store.latestSeq;
221
+ const { records, truncated } = this.hub.store.query(parsed.filters, afterSeq === undefined ? {} : { afterSeq });
222
+ const gap = afterSeq !== undefined && this.hub.store.hasGapAfter(rooms, afterSeq);
223
+ let lastSent = afterSeq ?? null;
224
+ for (const record of records) {
225
+ if (this.transport.isBackpressured()) {
226
+ return closed('error: backpressure, resubscribe after throughSeq', lastSent);
227
+ }
228
+ if (!this.transport.send({ type: 'fleet.rooms.event', payload: { subId, seq: record.seq, event: record.event } })) {
229
+ this.dispose();
230
+ return { ok: true };
231
+ }
232
+ lastSent = record.seq;
233
+ }
234
+ const throughSeq = truncated ? (lastSent ?? 0) : latestSeq;
235
+ this.transport.send({
236
+ type: 'fleet.rooms.eose',
237
+ payload: { subId, storeId, throughSeq, live: !truncated, truncated, gap, epochChanged },
238
+ });
239
+ // Synchronous from the query to here: no append can interleave, so live
240
+ // delivery starts exactly after `throughSeq`.
241
+ if (!truncated)
242
+ this.subscriptions.set(subId, { subId, filters: parsed.filters, rooms, throughSeq });
243
+ return { ok: true };
244
+ }
245
+ unsubscribe(subId) {
246
+ const id = typeof subId === 'string' ? subId : '';
247
+ return { subId: id, closed: this.subscriptions.delete(id) };
248
+ }
249
+ /** Hub fan-out for one stored record. */
250
+ deliver(record) {
251
+ if (this.disposed || !this.pubkey)
252
+ return;
253
+ for (const sub of [...this.subscriptions.values()]) {
254
+ if (record.seq <= sub.throughSeq || !roomFiltersMatch(sub.filters, record.event))
255
+ continue;
256
+ const admission = this.hub.access.admit(this.pubkey, this.transport.principalId);
257
+ if (!admission.ok || sub.rooms.some((room) => !this.hub.access.canRead(this.pubkey, room))) {
258
+ this.closeSubscription(sub, 'restricted: membership revoked');
259
+ continue;
260
+ }
261
+ if (this.transport.isBackpressured()) {
262
+ this.closeSubscription(sub, 'error: backpressure, resubscribe after throughSeq');
263
+ continue;
264
+ }
265
+ if (!this.transport.send({ type: 'fleet.rooms.event', payload: { subId: sub.subId, seq: record.seq, event: record.event } })) {
266
+ this.dispose();
267
+ return;
268
+ }
269
+ sub.throughSeq = record.seq;
270
+ }
271
+ }
272
+ closeSubscription(sub, message) {
273
+ this.subscriptions.delete(sub.subId);
274
+ this.transport.send({
275
+ type: 'fleet.rooms.closed',
276
+ payload: { subId: sub.subId, message, storeId: this.hub.store.storeId, throughSeq: sub.throughSeq },
277
+ });
278
+ }
279
+ closeAll(message) {
280
+ for (const sub of [...this.subscriptions.values()])
281
+ this.closeSubscription(sub, message);
282
+ }
283
+ get subscriptionCount() {
284
+ return this.subscriptions.size;
285
+ }
286
+ dispose() {
287
+ if (this.disposed)
288
+ return;
289
+ this.disposed = true;
290
+ this.subscriptions.clear();
291
+ this.challenge = undefined;
292
+ this.pubkey = undefined;
293
+ this.hub.forget(this);
294
+ }
295
+ }
296
+ export class RoomHub {
297
+ store;
298
+ access;
299
+ audiences;
300
+ maxSubscriptionsPerSession;
301
+ publishPerMinute;
302
+ maxFutureSkewSec;
303
+ maxPastSkewSec;
304
+ authSkewSec;
305
+ challengeTtlMs;
306
+ maxAuthAttempts;
307
+ now;
308
+ sessions = new Set();
309
+ publishWindows = new Map();
310
+ constructor(options) {
311
+ this.store = options.store;
312
+ this.access = options.access;
313
+ const audiences = options.audiences.map((audience) => canonicalRoomAudience(audience));
314
+ if (audiences.length === 0)
315
+ throw new RangeError('at least one hub audience is required');
316
+ this.audiences = Object.freeze([...new Set(audiences)]);
317
+ this.maxSubscriptionsPerSession = Math.floor(finitePositive('maxSubscriptionsPerSession', options.maxSubscriptionsPerSession, 16));
318
+ this.publishPerMinute = Math.floor(finitePositive('publishPerMinute', options.publishPerMinute, 60));
319
+ this.maxFutureSkewSec = finitePositive('maxFutureSkewSec', options.maxFutureSkewSec, 300);
320
+ this.maxPastSkewSec = finitePositive('maxPastSkewSec', options.maxPastSkewSec, 86_400);
321
+ this.authSkewSec = finitePositive('authSkewSec', options.authSkewSec, 60);
322
+ this.challengeTtlMs = finitePositive('challengeTtlMs', options.challengeTtlMs, 120_000);
323
+ this.maxAuthAttempts = Math.floor(finitePositive('maxAuthAttempts', options.maxAuthAttempts, 5));
324
+ this.now = options.now ?? Date.now;
325
+ }
326
+ open(transport) {
327
+ const session = new RoomSession(this, transport);
328
+ this.sessions.add(session);
329
+ return session;
330
+ }
331
+ /** @internal */
332
+ forget(session) {
333
+ this.sessions.delete(session);
334
+ }
335
+ /** @internal Fixed one-minute window per member key. */
336
+ takePublishToken(pubkey) {
337
+ const now = this.now();
338
+ const window = this.publishWindows.get(pubkey);
339
+ if (!window || now - window.start >= 60_000) {
340
+ this.publishWindows.set(pubkey, { start: now, count: 1 });
341
+ return true;
342
+ }
343
+ if (window.count >= this.publishPerMinute)
344
+ return false;
345
+ window.count++;
346
+ return true;
347
+ }
348
+ /** @internal */
349
+ deliver(record) {
350
+ for (const session of [...this.sessions])
351
+ session.deliver(record);
352
+ }
353
+ get sessionCount() {
354
+ return this.sessions.size;
355
+ }
356
+ /** Dispose every session. The store stays owned by the caller. */
357
+ close() {
358
+ for (const session of [...this.sessions])
359
+ session.dispose();
360
+ this.publishWindows.clear();
361
+ }
362
+ }
363
+ //# sourceMappingURL=room-hub.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Fleet rooms — member key file.
3
+ *
4
+ * A Buzz agent is identified by its own keypair (`BUZZ_PRIVATE_KEY`); a fleet
5
+ * room member likewise owns a secp256k1 key stored in a 0600 JSON file,
6
+ * `$CODEBUDDY_FLEET_ROOMS_IDENTITY` or `<CODEBUDDY_HOME>/fleet/rooms-identity.json`.
7
+ * The file is only created on explicit request and never overwritten silently;
8
+ * on POSIX a file readable by group or others is refused, like an SSH key.
9
+ * The secret never leaves this module except as the signing input.
10
+ *
11
+ * @module fleet/rooms/room-identity
12
+ */
13
+ export interface RoomIdentity {
14
+ readonly publicKey: string;
15
+ readonly secretKey: string;
16
+ readonly path: string;
17
+ }
18
+ export declare class RoomIdentityError extends Error {
19
+ constructor(message: string);
20
+ }
21
+ export declare function defaultRoomIdentityPath(): string;
22
+ /** Create a new identity file. Refuses to replace an existing one unless `force`. */
23
+ export declare function createRoomIdentity(filePath?: string, options?: {
24
+ force?: boolean;
25
+ }): RoomIdentity;
26
+ export declare function loadRoomIdentity(filePath?: string): RoomIdentity;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Fleet rooms — member key file.
3
+ *
4
+ * A Buzz agent is identified by its own keypair (`BUZZ_PRIVATE_KEY`); a fleet
5
+ * room member likewise owns a secp256k1 key stored in a 0600 JSON file,
6
+ * `$CODEBUDDY_FLEET_ROOMS_IDENTITY` or `<CODEBUDDY_HOME>/fleet/rooms-identity.json`.
7
+ * The file is only created on explicit request and never overwritten silently;
8
+ * on POSIX a file readable by group or others is refused, like an SSH key.
9
+ * The secret never leaves this module except as the signing input.
10
+ *
11
+ * @module fleet/rooms/room-identity
12
+ */
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import { getCodeBuddyPath } from '../../utils/codebuddy-home.js';
16
+ import { deriveRoomPublicKey, generateRoomSecretKey, isHex64 } from './room-event.js';
17
+ export class RoomIdentityError extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = 'RoomIdentityError';
21
+ }
22
+ }
23
+ export function defaultRoomIdentityPath() {
24
+ return process.env.CODEBUDDY_FLEET_ROOMS_IDENTITY || getCodeBuddyPath('fleet', 'rooms-identity.json');
25
+ }
26
+ /** Create a new identity file. Refuses to replace an existing one unless `force`. */
27
+ export function createRoomIdentity(filePath = defaultRoomIdentityPath(), options = {}) {
28
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
29
+ const secretKey = generateRoomSecretKey();
30
+ const publicKey = deriveRoomPublicKey(secretKey);
31
+ const body = `${JSON.stringify({ version: 1, publicKey, secretKey, createdAt: new Date().toISOString() }, null, 2)}\n`;
32
+ if (options.force && fs.existsSync(filePath)) {
33
+ fs.renameSync(filePath, `${filePath}.replaced-${Date.now()}`);
34
+ }
35
+ let fd;
36
+ try {
37
+ fd = fs.openSync(filePath, 'wx', 0o600);
38
+ }
39
+ catch (error) {
40
+ if (error.code === 'EEXIST') {
41
+ throw new RoomIdentityError(`identity file already exists: ${filePath} (use --force to rotate it)`);
42
+ }
43
+ throw error;
44
+ }
45
+ try {
46
+ fs.writeSync(fd, body);
47
+ fs.fsyncSync(fd);
48
+ }
49
+ finally {
50
+ fs.closeSync(fd);
51
+ }
52
+ return Object.freeze({ publicKey, secretKey, path: filePath });
53
+ }
54
+ export function loadRoomIdentity(filePath = defaultRoomIdentityPath()) {
55
+ let stat;
56
+ try {
57
+ stat = fs.statSync(filePath);
58
+ }
59
+ catch {
60
+ throw new RoomIdentityError(`no fleet room identity at ${filePath}; run \`buddy fleet rooms identity init\``);
61
+ }
62
+ if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) {
63
+ throw new RoomIdentityError(`identity file ${filePath} is readable by other users; chmod 600 it`);
64
+ }
65
+ if (stat.size > 4_096)
66
+ throw new RoomIdentityError(`identity file ${filePath} is not a room identity`);
67
+ let parsed;
68
+ try {
69
+ parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
70
+ }
71
+ catch {
72
+ throw new RoomIdentityError(`identity file ${filePath} is not valid JSON`);
73
+ }
74
+ if (parsed.version !== 1 || !isHex64(parsed.secretKey) || !isHex64(parsed.publicKey)) {
75
+ throw new RoomIdentityError(`identity file ${filePath} is not a room identity`);
76
+ }
77
+ if (deriveRoomPublicKey(parsed.secretKey) !== parsed.publicKey) {
78
+ throw new RoomIdentityError(`identity file ${filePath} is inconsistent (public key mismatch)`);
79
+ }
80
+ return Object.freeze({ publicKey: parsed.publicKey, secretKey: parsed.secretKey, path: filePath });
81
+ }
82
+ //# sourceMappingURL=room-identity.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Fleet rooms — exclusive single-writer lock for one ledger directory.
3
+ *
4
+ * Two `buddy server` processes commonly share one HOME. Without a lock both
5
+ * would hand out the same `seq` and one compaction would erase the other's
6
+ * appends. The lock is a `writer.lock` file created with `O_CREAT|O_EXCL`
7
+ * holding `{ pid, hostname, token }`.
8
+ *
9
+ * Recovery is deliberately narrow: a lock is reclaimed only when it names THIS
10
+ * host and a pid that no longer exists (`ESRCH`). A live pid (even a reused
11
+ * one), another host, `EPERM` or an unreadable/corrupt lock file all fail
12
+ * closed with {@link RoomLockError}; the operator removes the file by hand.
13
+ *
14
+ * @module fleet/rooms/room-lock
15
+ */
16
+ export declare class RoomLockError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ export declare class RoomDirectoryLock {
20
+ readonly lockPath: string;
21
+ private readonly recoveryLockPath;
22
+ private readonly token;
23
+ private released;
24
+ private constructor();
25
+ /** Acquire the lock or throw {@link RoomLockError}. */
26
+ static acquire(directory: string): RoomDirectoryLock;
27
+ private tryCreateRecoveryMutex;
28
+ private releaseRecoveryMutex;
29
+ private tryCreate;
30
+ /** True while the lock file on disk still carries this holder's token. */
31
+ isHeld(): boolean;
32
+ /** Release once; never removes a lock that another holder now owns. */
33
+ release(): void;
34
+ }