@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,139 @@
1
+ /**
2
+ * Fleet rooms — signed message events.
3
+ *
4
+ * A room message is a Nostr NIP-01 event: `id` is the sha256 of the canonical
5
+ * serialization, `sig` a BIP-340 Schnorr signature by `pubkey`. Authorship and
6
+ * integrity therefore survive any transport, restart or copy of the ledger,
7
+ * which a hub-stamped principal alone cannot prove.
8
+ *
9
+ * Shapes adapted (re-implemented in TypeScript, no code copied) from block/buzz
10
+ * @ 4cd82f513214aad11c2b742ce7cc7c681e8e32a0, Apache-2.0:
11
+ * - event id + signature checks: `crates/buzz-core/src/verification.rs`;
12
+ * - stream-message tag layout (`h` room, `p` mentions, `e` thread markers,
13
+ * 64 KiB content, 50 mentions): `crates/buzz-sdk/src/builders.rs::build_message`;
14
+ * - NIP-10 root/reply resolution: `crates/buzz-core/src/nip10.rs`;
15
+ * - mention normalisation: `crates/buzz-sdk/src/mentions.rs::normalize_mention_pubkeys`;
16
+ * - NIP-42 proof of key possession (`challenge` + `relay` tags):
17
+ * `crates/buzz-auth` / `ARCHITECTURE.md` §3.
18
+ *
19
+ * @module fleet/rooms/room-event
20
+ */
21
+ /** Buzz `KIND_STREAM_MESSAGE` — the only stored kind in this first lot. */
22
+ export declare const ROOM_MESSAGE_KIND = 9;
23
+ /** NIP-42 authentication event. Proves key possession; never stored. */
24
+ export declare const ROOM_AUTH_KIND = 22242;
25
+ /** Same content ceiling as Buzz stream messages (UTF-8 bytes). */
26
+ export declare const MAX_ROOM_CONTENT_BYTES: number;
27
+ /** Same mention ceiling as Buzz (`MENTION_CAP`). */
28
+ export declare const MAX_ROOM_MENTIONS = 50;
29
+ /** 50 mentions + room + two thread markers fit with room to spare. */
30
+ export declare const MAX_ROOM_TAGS = 64;
31
+ /** Sum of the UTF-8 bytes of every tag part. */
32
+ export declare const MAX_ROOM_TAG_BYTES: number;
33
+ /** Whole event as JSON on the wire, escaping included. */
34
+ export declare const MAX_ROOM_EVENT_BYTES: number;
35
+ /** Lowercase slug: rooms are named by the hub operator. */
36
+ export declare const ROOM_ID_PATTERN: RegExp;
37
+ export interface RoomEvent {
38
+ id: string;
39
+ pubkey: string;
40
+ created_at: number;
41
+ kind: number;
42
+ tags: string[][];
43
+ content: string;
44
+ sig: string;
45
+ }
46
+ /** Deeply read-only view handed out by the store and the hub. */
47
+ export interface ReadonlyRoomEvent {
48
+ readonly id: string;
49
+ readonly pubkey: string;
50
+ readonly created_at: number;
51
+ readonly kind: number;
52
+ readonly tags: readonly (readonly string[])[];
53
+ readonly content: string;
54
+ readonly sig: string;
55
+ }
56
+ export interface UnsignedRoomEvent {
57
+ created_at: number;
58
+ kind: number;
59
+ tags: string[][];
60
+ content: string;
61
+ }
62
+ export interface ThreadMarkers {
63
+ root?: string;
64
+ reply?: string;
65
+ }
66
+ export interface ThreadReference {
67
+ /** Id of the thread root message. */
68
+ root: string;
69
+ /** Id of the message being answered; equals `root` for a direct reply. */
70
+ parent: string;
71
+ }
72
+ export type RoomEventCheck = {
73
+ ok: true;
74
+ event: RoomEvent;
75
+ } | {
76
+ ok: false;
77
+ reason: string;
78
+ };
79
+ export declare class RoomEventError extends Error {
80
+ constructor(message: string);
81
+ }
82
+ export declare function isHex64(value: unknown): value is string;
83
+ export declare function isValidRoomId(value: unknown): value is string;
84
+ /** NIP-01 canonical serialization `[0, pubkey, created_at, kind, tags, content]`. */
85
+ export declare function serializeRoomEvent(event: Pick<ReadonlyRoomEvent, 'pubkey' | 'created_at' | 'kind' | 'tags' | 'content'>): string;
86
+ export declare function computeRoomEventId(event: Pick<ReadonlyRoomEvent, 'pubkey' | 'created_at' | 'kind' | 'tags' | 'content'>): string;
87
+ /** x-only BIP-340 public key (lowercase hex) for a 32-byte secret key. */
88
+ export declare function deriveRoomPublicKey(secretKeyHex: string): string;
89
+ export declare function generateRoomSecretKey(): string;
90
+ export declare function signRoomEvent(template: UnsignedRoomEvent, secretKeyHex: string): RoomEvent;
91
+ /**
92
+ * Structural validation of an untrusted event, including every byte bound.
93
+ * Does not check id or signature; see {@link verifyRoomEvent}. The returned
94
+ * event is a fresh copy sharing nothing with the input.
95
+ */
96
+ export declare function checkRoomEventShape(value: unknown): RoomEventCheck;
97
+ /** Recompute the id and verify the Schnorr signature. */
98
+ export declare function verifyRoomEvent(event: ReadonlyRoomEvent): {
99
+ ok: true;
100
+ } | {
101
+ ok: false;
102
+ reason: string;
103
+ };
104
+ /** Deep-freeze a validated event so shared references cannot be mutated. */
105
+ export declare function freezeRoomEvent(event: RoomEvent): ReadonlyRoomEvent;
106
+ export declare function tagValues(event: Pick<ReadonlyRoomEvent, 'tags'>, name: string): string[];
107
+ /**
108
+ * The room of a message. Every tag named `h` counts — including a malformed
109
+ * `['h']` — and there must be exactly one, shaped `['h', <room id>]`.
110
+ */
111
+ export declare function roomOf(event: Pick<ReadonlyRoomEvent, 'tags'>): string | undefined;
112
+ /** NIP-10 markers; a marker counts only with a valid 64-hex id, last one wins. */
113
+ export declare function parseThreadMarkers(tags: readonly (readonly string[])[]): ThreadMarkers;
114
+ /**
115
+ * `root`+`reply` → nested reply; `reply` alone → direct reply to the root;
116
+ * `root` alone or nothing → top-level (a lone root never anchors a reply).
117
+ */
118
+ export declare function resolveThread(markers: ThreadMarkers): ThreadReference | undefined;
119
+ /** Lowercase, drop duplicates and the sender's own key, keep first-seen order. */
120
+ export declare function normalizeMentions(pubkeys: readonly string[], sender?: string): string[];
121
+ export interface RoomMessageInput {
122
+ room: string;
123
+ content: string;
124
+ mentions?: readonly string[];
125
+ replyTo?: ThreadReference;
126
+ /** Unix seconds; defaults to now. */
127
+ createdAt?: number;
128
+ }
129
+ /** Build an unsigned kind-9 room message with Buzz's tag layout. */
130
+ export declare function buildRoomMessage(input: RoomMessageInput, senderPubkey?: string): UnsignedRoomEvent;
131
+ /**
132
+ * Canonical hub audience for a WebSocket URL: `ws`/`wss` scheme, lowercase host,
133
+ * explicit port, normalized path, no credentials/query/fragment. The client
134
+ * derives it from the URL it dialed — never from anything the hub says — so a
135
+ * proof signed for one hub cannot be replayed to another.
136
+ */
137
+ export declare function canonicalRoomAudience(url: string): string;
138
+ /** NIP-42 style proof of key possession bound to one challenge AND one hub. */
139
+ export declare function buildRoomAuth(challenge: string, audience: string, createdAt?: number): UnsignedRoomEvent;
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Fleet rooms — signed message events.
3
+ *
4
+ * A room message is a Nostr NIP-01 event: `id` is the sha256 of the canonical
5
+ * serialization, `sig` a BIP-340 Schnorr signature by `pubkey`. Authorship and
6
+ * integrity therefore survive any transport, restart or copy of the ledger,
7
+ * which a hub-stamped principal alone cannot prove.
8
+ *
9
+ * Shapes adapted (re-implemented in TypeScript, no code copied) from block/buzz
10
+ * @ 4cd82f513214aad11c2b742ce7cc7c681e8e32a0, Apache-2.0:
11
+ * - event id + signature checks: `crates/buzz-core/src/verification.rs`;
12
+ * - stream-message tag layout (`h` room, `p` mentions, `e` thread markers,
13
+ * 64 KiB content, 50 mentions): `crates/buzz-sdk/src/builders.rs::build_message`;
14
+ * - NIP-10 root/reply resolution: `crates/buzz-core/src/nip10.rs`;
15
+ * - mention normalisation: `crates/buzz-sdk/src/mentions.rs::normalize_mention_pubkeys`;
16
+ * - NIP-42 proof of key possession (`challenge` + `relay` tags):
17
+ * `crates/buzz-auth` / `ARCHITECTURE.md` §3.
18
+ *
19
+ * @module fleet/rooms/room-event
20
+ */
21
+ import { createHash, randomBytes } from 'crypto';
22
+ import { schnorr } from '@noble/curves/secp256k1';
23
+ /** Buzz `KIND_STREAM_MESSAGE` — the only stored kind in this first lot. */
24
+ export const ROOM_MESSAGE_KIND = 9;
25
+ /** NIP-42 authentication event. Proves key possession; never stored. */
26
+ export const ROOM_AUTH_KIND = 22242;
27
+ /** Same content ceiling as Buzz stream messages (UTF-8 bytes). */
28
+ export const MAX_ROOM_CONTENT_BYTES = 64 * 1024;
29
+ /** Same mention ceiling as Buzz (`MENTION_CAP`). */
30
+ export const MAX_ROOM_MENTIONS = 50;
31
+ /** 50 mentions + room + two thread markers fit with room to spare. */
32
+ export const MAX_ROOM_TAGS = 64;
33
+ /** Sum of the UTF-8 bytes of every tag part. */
34
+ export const MAX_ROOM_TAG_BYTES = 16 * 1024;
35
+ /** Whole event as JSON on the wire, escaping included. */
36
+ export const MAX_ROOM_EVENT_BYTES = 512 * 1024;
37
+ const MAX_TAG_PARTS = 8;
38
+ const MAX_TAG_PART_CHARS = 256;
39
+ /** Lowercase slug: rooms are named by the hub operator. */
40
+ export const ROOM_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
41
+ const HEX_64 = /^[0-9a-f]{64}$/;
42
+ const HEX_128 = /^[0-9a-f]{128}$/;
43
+ const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
44
+ const EVENT_FIELDS = new Set(['id', 'pubkey', 'created_at', 'kind', 'tags', 'content', 'sig']);
45
+ export class RoomEventError extends Error {
46
+ constructor(message) {
47
+ super(message);
48
+ this.name = 'RoomEventError';
49
+ }
50
+ }
51
+ export function isHex64(value) {
52
+ return typeof value === 'string' && HEX_64.test(value);
53
+ }
54
+ export function isValidRoomId(value) {
55
+ return typeof value === 'string' && ROOM_ID_PATTERN.test(value);
56
+ }
57
+ /** NIP-01 canonical serialization `[0, pubkey, created_at, kind, tags, content]`. */
58
+ export function serializeRoomEvent(event) {
59
+ return JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]);
60
+ }
61
+ export function computeRoomEventId(event) {
62
+ return createHash('sha256').update(serializeRoomEvent(event), 'utf8').digest('hex');
63
+ }
64
+ function secretKeyBytes(secretKeyHex) {
65
+ if (!isHex64(secretKeyHex)) {
66
+ throw new RoomEventError('secret key must be 64 lowercase hex characters');
67
+ }
68
+ return Uint8Array.from(Buffer.from(secretKeyHex, 'hex'));
69
+ }
70
+ function toHex(bytes) {
71
+ return Buffer.from(bytes).toString('hex');
72
+ }
73
+ /** x-only BIP-340 public key (lowercase hex) for a 32-byte secret key. */
74
+ export function deriveRoomPublicKey(secretKeyHex) {
75
+ const bytes = secretKeyBytes(secretKeyHex);
76
+ try {
77
+ return toHex(schnorr.getPublicKey(bytes));
78
+ }
79
+ catch {
80
+ throw new RoomEventError('secret key is not a valid secp256k1 scalar');
81
+ }
82
+ }
83
+ export function generateRoomSecretKey() {
84
+ for (;;) {
85
+ const candidate = randomBytes(32).toString('hex');
86
+ try {
87
+ deriveRoomPublicKey(candidate);
88
+ return candidate;
89
+ }
90
+ catch {
91
+ // Out-of-range scalar (probability ~2^-128): draw again.
92
+ }
93
+ }
94
+ }
95
+ export function signRoomEvent(template, secretKeyHex) {
96
+ const pubkey = deriveRoomPublicKey(secretKeyHex);
97
+ const draft = {
98
+ pubkey,
99
+ created_at: template.created_at,
100
+ kind: template.kind,
101
+ tags: template.tags.map((tag) => [...tag]),
102
+ content: template.content,
103
+ };
104
+ const shape = checkRoomEventShape({ ...draft, id: '0'.repeat(64), sig: '0'.repeat(128) });
105
+ if (!shape.ok)
106
+ throw new RoomEventError(shape.reason);
107
+ const id = computeRoomEventId(draft);
108
+ const sig = toHex(schnorr.sign(Uint8Array.from(Buffer.from(id, 'hex')), secretKeyBytes(secretKeyHex)));
109
+ return { id, ...draft, sig };
110
+ }
111
+ /**
112
+ * Structural validation of an untrusted event, including every byte bound.
113
+ * Does not check id or signature; see {@link verifyRoomEvent}. The returned
114
+ * event is a fresh copy sharing nothing with the input.
115
+ */
116
+ export function checkRoomEventShape(value) {
117
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
118
+ return { ok: false, reason: 'event must be an object' };
119
+ }
120
+ const raw = value;
121
+ for (const key of Object.keys(raw)) {
122
+ if (!EVENT_FIELDS.has(key))
123
+ return { ok: false, reason: 'unknown event field' };
124
+ }
125
+ if (!isHex64(raw.id))
126
+ return { ok: false, reason: 'id must be 64 lowercase hex characters' };
127
+ if (!isHex64(raw.pubkey))
128
+ return { ok: false, reason: 'pubkey must be 64 lowercase hex characters' };
129
+ if (typeof raw.sig !== 'string' || !HEX_128.test(raw.sig)) {
130
+ return { ok: false, reason: 'sig must be 128 lowercase hex characters' };
131
+ }
132
+ if (!Number.isSafeInteger(raw.created_at) || raw.created_at < 0) {
133
+ return { ok: false, reason: 'created_at must be a non-negative integer' };
134
+ }
135
+ if (!Number.isInteger(raw.kind) || raw.kind < 0 || raw.kind > 65_535) {
136
+ return { ok: false, reason: 'kind must be an integer between 0 and 65535' };
137
+ }
138
+ if (typeof raw.content !== 'string')
139
+ return { ok: false, reason: 'content must be a string' };
140
+ // Cheap character bound first so an oversized string is never re-encoded.
141
+ if (raw.content.length > MAX_ROOM_CONTENT_BYTES ||
142
+ Buffer.byteLength(raw.content, 'utf8') > MAX_ROOM_CONTENT_BYTES) {
143
+ return { ok: false, reason: `content exceeds ${MAX_ROOM_CONTENT_BYTES} bytes` };
144
+ }
145
+ if (LONE_SURROGATE.test(raw.content))
146
+ return { ok: false, reason: 'content is not valid UTF-8' };
147
+ if (!Array.isArray(raw.tags) || raw.tags.length > MAX_ROOM_TAGS) {
148
+ return { ok: false, reason: `tags must be an array of at most ${MAX_ROOM_TAGS} tags` };
149
+ }
150
+ const tags = [];
151
+ let tagBytes = 0;
152
+ for (const tag of raw.tags) {
153
+ if (!Array.isArray(tag) || tag.length === 0 || tag.length > MAX_TAG_PARTS) {
154
+ return { ok: false, reason: `each tag must hold 1 to ${MAX_TAG_PARTS} strings` };
155
+ }
156
+ const copy = [];
157
+ for (const part of tag) {
158
+ if (typeof part !== 'string' || part.length > MAX_TAG_PART_CHARS || LONE_SURROGATE.test(part)) {
159
+ return { ok: false, reason: 'tag parts must be valid strings of at most 256 characters' };
160
+ }
161
+ tagBytes += Buffer.byteLength(part, 'utf8');
162
+ copy.push(part);
163
+ }
164
+ if (tagBytes > MAX_ROOM_TAG_BYTES) {
165
+ return { ok: false, reason: `tags exceed ${MAX_ROOM_TAG_BYTES} bytes` };
166
+ }
167
+ tags.push(copy);
168
+ }
169
+ const event = {
170
+ id: raw.id,
171
+ pubkey: raw.pubkey,
172
+ created_at: raw.created_at,
173
+ kind: raw.kind,
174
+ tags,
175
+ content: raw.content,
176
+ sig: raw.sig,
177
+ };
178
+ if (Buffer.byteLength(JSON.stringify(event), 'utf8') > MAX_ROOM_EVENT_BYTES) {
179
+ return { ok: false, reason: `event exceeds ${MAX_ROOM_EVENT_BYTES} bytes` };
180
+ }
181
+ return { ok: true, event };
182
+ }
183
+ /** Recompute the id and verify the Schnorr signature. */
184
+ export function verifyRoomEvent(event) {
185
+ if (computeRoomEventId(event) !== event.id) {
186
+ return { ok: false, reason: 'event id does not match its content' };
187
+ }
188
+ try {
189
+ const valid = schnorr.verify(Uint8Array.from(Buffer.from(event.sig, 'hex')), Uint8Array.from(Buffer.from(event.id, 'hex')), Uint8Array.from(Buffer.from(event.pubkey, 'hex')));
190
+ if (!valid)
191
+ return { ok: false, reason: 'bad signature' };
192
+ }
193
+ catch {
194
+ return { ok: false, reason: 'bad signature' };
195
+ }
196
+ return { ok: true };
197
+ }
198
+ /** Deep-freeze a validated event so shared references cannot be mutated. */
199
+ export function freezeRoomEvent(event) {
200
+ for (const tag of event.tags)
201
+ Object.freeze(tag);
202
+ Object.freeze(event.tags);
203
+ return Object.freeze(event);
204
+ }
205
+ export function tagValues(event, name) {
206
+ const values = [];
207
+ for (const tag of event.tags) {
208
+ if (tag[0] === name && typeof tag[1] === 'string')
209
+ values.push(tag[1]);
210
+ }
211
+ return values;
212
+ }
213
+ /**
214
+ * The room of a message. Every tag named `h` counts — including a malformed
215
+ * `['h']` — and there must be exactly one, shaped `['h', <room id>]`.
216
+ */
217
+ export function roomOf(event) {
218
+ const roomTags = event.tags.filter((tag) => tag[0] === 'h');
219
+ if (roomTags.length !== 1)
220
+ return undefined;
221
+ const only = roomTags[0];
222
+ return only && only.length === 2 && isValidRoomId(only[1]) ? only[1] : undefined;
223
+ }
224
+ /** NIP-10 markers; a marker counts only with a valid 64-hex id, last one wins. */
225
+ export function parseThreadMarkers(tags) {
226
+ const markers = {};
227
+ for (const tag of tags) {
228
+ if (tag.length >= 4 && tag[0] === 'e' && isHex64(tag[1])) {
229
+ if (tag[3] === 'root')
230
+ markers.root = tag[1];
231
+ else if (tag[3] === 'reply')
232
+ markers.reply = tag[1];
233
+ }
234
+ }
235
+ return markers;
236
+ }
237
+ /**
238
+ * `root`+`reply` → nested reply; `reply` alone → direct reply to the root;
239
+ * `root` alone or nothing → top-level (a lone root never anchors a reply).
240
+ */
241
+ export function resolveThread(markers) {
242
+ if (!markers.reply)
243
+ return undefined;
244
+ return { root: markers.root ?? markers.reply, parent: markers.reply };
245
+ }
246
+ /** Lowercase, drop duplicates and the sender's own key, keep first-seen order. */
247
+ export function normalizeMentions(pubkeys, sender) {
248
+ const self = sender?.toLowerCase();
249
+ const seen = new Set();
250
+ const result = [];
251
+ for (const pubkey of pubkeys) {
252
+ const lower = pubkey.toLowerCase();
253
+ if (lower === self || seen.has(lower))
254
+ continue;
255
+ seen.add(lower);
256
+ result.push(lower);
257
+ }
258
+ return result;
259
+ }
260
+ /** Build an unsigned kind-9 room message with Buzz's tag layout. */
261
+ export function buildRoomMessage(input, senderPubkey) {
262
+ if (!isValidRoomId(input.room)) {
263
+ throw new RoomEventError('room must match ^[a-z0-9][a-z0-9_-]{0,63}$');
264
+ }
265
+ if (typeof input.content !== 'string' || Buffer.byteLength(input.content, 'utf8') > MAX_ROOM_CONTENT_BYTES) {
266
+ throw new RoomEventError(`content must be a string of at most ${MAX_ROOM_CONTENT_BYTES} bytes`);
267
+ }
268
+ const tags = [['h', input.room]];
269
+ if (input.replyTo) {
270
+ const { root, parent } = input.replyTo;
271
+ if (!isHex64(root) || !isHex64(parent)) {
272
+ throw new RoomEventError('thread ids must be 64 lowercase hex characters');
273
+ }
274
+ if (root === parent)
275
+ tags.push(['e', root, '', 'reply']);
276
+ else
277
+ tags.push(['e', root, '', 'root'], ['e', parent, '', 'reply']);
278
+ }
279
+ const mentions = normalizeMentions(input.mentions ?? [], senderPubkey);
280
+ if (mentions.length > MAX_ROOM_MENTIONS) {
281
+ throw new RoomEventError(`at most ${MAX_ROOM_MENTIONS} mentions`);
282
+ }
283
+ for (const pubkey of mentions) {
284
+ if (!isHex64(pubkey))
285
+ throw new RoomEventError('mentions must be 64-hex public keys');
286
+ tags.push(['p', pubkey]);
287
+ }
288
+ const createdAt = input.createdAt ?? Math.floor(Date.now() / 1000);
289
+ if (!Number.isSafeInteger(createdAt) || createdAt < 0) {
290
+ throw new RoomEventError('createdAt must be a non-negative integer');
291
+ }
292
+ return { created_at: createdAt, kind: ROOM_MESSAGE_KIND, tags, content: input.content };
293
+ }
294
+ /**
295
+ * Canonical hub audience for a WebSocket URL: `ws`/`wss` scheme, lowercase host,
296
+ * explicit port, normalized path, no credentials/query/fragment. The client
297
+ * derives it from the URL it dialed — never from anything the hub says — so a
298
+ * proof signed for one hub cannot be replayed to another.
299
+ */
300
+ export function canonicalRoomAudience(url) {
301
+ let parsed;
302
+ try {
303
+ parsed = new URL(url);
304
+ }
305
+ catch {
306
+ throw new RoomEventError('audience must be an absolute ws:// or wss:// URL');
307
+ }
308
+ const scheme = parsed.protocol === 'https:' ? 'wss:' : parsed.protocol === 'http:' ? 'ws:' : parsed.protocol;
309
+ if (scheme !== 'ws:' && scheme !== 'wss:') {
310
+ throw new RoomEventError('audience must be an absolute ws:// or wss:// URL');
311
+ }
312
+ if (parsed.username || parsed.password) {
313
+ throw new RoomEventError('audience must not carry credentials');
314
+ }
315
+ const port = parsed.port || (scheme === 'wss:' ? '443' : '80');
316
+ const pathname = parsed.pathname.replace(/\/+$/, '') || '/';
317
+ return `${scheme}//${parsed.hostname.toLowerCase()}:${port}${pathname}`;
318
+ }
319
+ /** NIP-42 style proof of key possession bound to one challenge AND one hub. */
320
+ export function buildRoomAuth(challenge, audience, createdAt = Math.floor(Date.now() / 1000)) {
321
+ if (!/^[0-9a-f]{64}$/.test(challenge))
322
+ throw new RoomEventError('challenge must be 64 hex characters');
323
+ return {
324
+ created_at: createdAt,
325
+ kind: ROOM_AUTH_KIND,
326
+ tags: [['challenge', challenge], ['relay', canonicalRoomAudience(audience)]],
327
+ content: '',
328
+ };
329
+ }
330
+ //# sourceMappingURL=room-event.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Fleet rooms — NIP-01 subscription filters.
3
+ *
4
+ * Semantics adapted from block/buzz @ 4cd82f51 (Apache-2.0),
5
+ * `crates/buzz-core/src/filter.rs::filters_match`: filters are OR-ed, fields
6
+ * inside one filter are AND-ed, `ids` match by prefix, and an explicitly empty
7
+ * list matches nothing (NIP-01 edge case documented in Buzz `ARCHITECTURE.md` §5).
8
+ *
9
+ * Deliberate differences from a public relay:
10
+ * - every filter MUST name its rooms with `#h` — there is no global
11
+ * subscription, so access control is decided before anything is read
12
+ * (Buzz excludes global subscriptions from channel fan-out for the same reason);
13
+ * - unknown keys are rejected instead of ignored, so a typo cannot silently
14
+ * widen or empty a subscription;
15
+ * - every list and the history `limit` are bounded.
16
+ *
17
+ * @module fleet/rooms/room-filter
18
+ */
19
+ import { type ReadonlyRoomEvent } from './room-event.js';
20
+ /** Buzz `MAX_HISTORICAL_LIMIT`. */
21
+ export declare const MAX_ROOM_HISTORY_LIMIT = 500;
22
+ export declare const DEFAULT_ROOM_HISTORY_LIMIT = 100;
23
+ export declare const MAX_ROOM_FILTERS = 8;
24
+ export declare const MAX_ROOMS_PER_FILTER = 32;
25
+ export interface RoomFilter {
26
+ ids?: string[];
27
+ authors?: string[];
28
+ kinds?: number[];
29
+ since?: number;
30
+ until?: number;
31
+ limit?: number;
32
+ '#h': string[];
33
+ '#p'?: string[];
34
+ '#e'?: string[];
35
+ }
36
+ export type RoomFilterParse = {
37
+ ok: true;
38
+ filters: RoomFilter[];
39
+ } | {
40
+ ok: false;
41
+ reason: string;
42
+ };
43
+ /** Validate untrusted subscription filters. */
44
+ export declare function parseRoomFilters(value: unknown): RoomFilterParse;
45
+ export declare function roomFilterMatches(filter: RoomFilter, event: ReadonlyRoomEvent): boolean;
46
+ export declare function roomFiltersMatch(filters: readonly RoomFilter[], event: ReadonlyRoomEvent): boolean;
47
+ /** Every room a subscription could read; the ACL is checked against this set. */
48
+ export declare function roomsReferenced(filters: readonly RoomFilter[]): string[];
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Fleet rooms — NIP-01 subscription filters.
3
+ *
4
+ * Semantics adapted from block/buzz @ 4cd82f51 (Apache-2.0),
5
+ * `crates/buzz-core/src/filter.rs::filters_match`: filters are OR-ed, fields
6
+ * inside one filter are AND-ed, `ids` match by prefix, and an explicitly empty
7
+ * list matches nothing (NIP-01 edge case documented in Buzz `ARCHITECTURE.md` §5).
8
+ *
9
+ * Deliberate differences from a public relay:
10
+ * - every filter MUST name its rooms with `#h` — there is no global
11
+ * subscription, so access control is decided before anything is read
12
+ * (Buzz excludes global subscriptions from channel fan-out for the same reason);
13
+ * - unknown keys are rejected instead of ignored, so a typo cannot silently
14
+ * widen or empty a subscription;
15
+ * - every list and the history `limit` are bounded.
16
+ *
17
+ * @module fleet/rooms/room-filter
18
+ */
19
+ import { ROOM_ID_PATTERN } from './room-event.js';
20
+ /** Buzz `MAX_HISTORICAL_LIMIT`. */
21
+ export const MAX_ROOM_HISTORY_LIMIT = 500;
22
+ export const DEFAULT_ROOM_HISTORY_LIMIT = 100;
23
+ export const MAX_ROOM_FILTERS = 8;
24
+ export const MAX_ROOMS_PER_FILTER = 32;
25
+ const MAX_VALUES_PER_LIST = 100;
26
+ const MAX_KINDS = 20;
27
+ const HEX_PREFIX = /^[0-9a-f]{1,64}$/;
28
+ const HEX_64 = /^[0-9a-f]{64}$/;
29
+ const FILTER_KEYS = new Set(['ids', 'authors', 'kinds', 'since', 'until', 'limit', '#h', '#p', '#e']);
30
+ function parseStringList(value, key, pattern, maxLength) {
31
+ if (!Array.isArray(value) || value.length > maxLength) {
32
+ return `${key} must be an array of at most ${maxLength} values`;
33
+ }
34
+ const list = [];
35
+ for (const item of value) {
36
+ if (typeof item !== 'string' || !pattern.test(item))
37
+ return `${key} contains an invalid value`;
38
+ list.push(item);
39
+ }
40
+ return list;
41
+ }
42
+ function parseTimestamp(value, key) {
43
+ if (!Number.isSafeInteger(value) || value < 0) {
44
+ return `${key} must be a non-negative integer`;
45
+ }
46
+ return value;
47
+ }
48
+ function parseOne(value) {
49
+ if (!value || typeof value !== 'object' || Array.isArray(value))
50
+ return 'filter must be an object';
51
+ const raw = value;
52
+ for (const key of Object.keys(raw)) {
53
+ if (!FILTER_KEYS.has(key))
54
+ return `unknown filter key: ${key.slice(0, 32)}`;
55
+ }
56
+ if (raw['#h'] === undefined)
57
+ return 'every filter must name its rooms with #h';
58
+ const rooms = parseStringList(raw['#h'], '#h', ROOM_ID_PATTERN, MAX_ROOMS_PER_FILTER);
59
+ if (typeof rooms === 'string')
60
+ return rooms;
61
+ if (rooms.length === 0)
62
+ return '#h must name at least one room';
63
+ const filter = { '#h': [...new Set(rooms)] };
64
+ if (raw.ids !== undefined) {
65
+ const ids = parseStringList(raw.ids, 'ids', HEX_PREFIX, MAX_VALUES_PER_LIST);
66
+ if (typeof ids === 'string')
67
+ return ids;
68
+ filter.ids = ids;
69
+ }
70
+ if (raw.authors !== undefined) {
71
+ const authors = parseStringList(raw.authors, 'authors', HEX_64, MAX_VALUES_PER_LIST);
72
+ if (typeof authors === 'string')
73
+ return authors;
74
+ filter.authors = authors;
75
+ }
76
+ for (const key of ['#p', '#e']) {
77
+ if (raw[key] !== undefined) {
78
+ const values = parseStringList(raw[key], key, HEX_64, MAX_VALUES_PER_LIST);
79
+ if (typeof values === 'string')
80
+ return values;
81
+ filter[key] = values;
82
+ }
83
+ }
84
+ if (raw.kinds !== undefined) {
85
+ if (!Array.isArray(raw.kinds) || raw.kinds.length > MAX_KINDS) {
86
+ return `kinds must be an array of at most ${MAX_KINDS} integers`;
87
+ }
88
+ if (!raw.kinds.every((kind) => Number.isInteger(kind) && kind >= 0 && kind <= 65_535)) {
89
+ return 'kinds contains an invalid value';
90
+ }
91
+ filter.kinds = [...raw.kinds];
92
+ }
93
+ for (const key of ['since', 'until']) {
94
+ if (raw[key] !== undefined) {
95
+ const timestamp = parseTimestamp(raw[key], key);
96
+ if (typeof timestamp === 'string')
97
+ return timestamp;
98
+ filter[key] = timestamp;
99
+ }
100
+ }
101
+ if (raw.limit !== undefined) {
102
+ const limit = parseTimestamp(raw.limit, 'limit');
103
+ if (typeof limit === 'string')
104
+ return limit;
105
+ filter.limit = Math.min(limit, MAX_ROOM_HISTORY_LIMIT);
106
+ }
107
+ return filter;
108
+ }
109
+ /** Validate untrusted subscription filters. */
110
+ export function parseRoomFilters(value) {
111
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_ROOM_FILTERS) {
112
+ return { ok: false, reason: `filters must be an array of 1 to ${MAX_ROOM_FILTERS} filters` };
113
+ }
114
+ const filters = [];
115
+ for (const item of value) {
116
+ const parsed = parseOne(item);
117
+ if (typeof parsed === 'string')
118
+ return { ok: false, reason: parsed };
119
+ filters.push(parsed);
120
+ }
121
+ return { ok: true, filters };
122
+ }
123
+ function hasTagValue(event, name, wanted) {
124
+ return event.tags.some((tag) => tag[0] === name && typeof tag[1] === 'string' && wanted.includes(tag[1]));
125
+ }
126
+ export function roomFilterMatches(filter, event) {
127
+ if (filter.kinds && !filter.kinds.includes(event.kind))
128
+ return false;
129
+ if (filter.authors && !filter.authors.includes(event.pubkey))
130
+ return false;
131
+ if (filter.since !== undefined && event.created_at < filter.since)
132
+ return false;
133
+ if (filter.until !== undefined && event.created_at > filter.until)
134
+ return false;
135
+ if (filter.ids && !filter.ids.some((prefix) => event.id.startsWith(prefix)))
136
+ return false;
137
+ if (!hasTagValue(event, 'h', filter['#h']))
138
+ return false;
139
+ if (filter['#p'] && !hasTagValue(event, 'p', filter['#p']))
140
+ return false;
141
+ if (filter['#e'] && !hasTagValue(event, 'e', filter['#e']))
142
+ return false;
143
+ return true;
144
+ }
145
+ export function roomFiltersMatch(filters, event) {
146
+ return filters.some((filter) => roomFilterMatches(filter, event));
147
+ }
148
+ /** Every room a subscription could read; the ACL is checked against this set. */
149
+ export function roomsReferenced(filters) {
150
+ return [...new Set(filters.flatMap((filter) => filter['#h']))];
151
+ }
152
+ //# sourceMappingURL=room-filter.js.map