@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
@@ -49,8 +49,24 @@ import { EventEmitter } from 'events';
49
49
  import { WebSocket } from 'ws';
50
50
  import { logger } from '../utils/logger.js';
51
51
  import { ReconnectionManager, } from '../channels/reconnection-manager.js';
52
+ /** Close a superseded socket without letting ws emit an unhandled error while CONNECTING. */
53
+ function closeReplacedSocket(ws) {
54
+ ws.removeAllListeners();
55
+ ws.on('error', () => {
56
+ /* ws emits when close() interrupts a CONNECTING handshake */
57
+ });
58
+ try {
59
+ if (ws.readyState === 0 /* CONNECTING */ || ws.readyState === 1 /* OPEN */) {
60
+ ws.close();
61
+ }
62
+ }
63
+ catch {
64
+ /* best-effort replacement cleanup */
65
+ }
66
+ }
52
67
  export class FleetListener extends EventEmitter {
53
68
  ws = null;
69
+ cancelConnectAttempt = null;
54
70
  connected = false;
55
71
  authenticated = false;
56
72
  // Set true once we have completed an `authenticated` handshake at least
@@ -156,49 +172,83 @@ export class FleetListener extends EventEmitter {
156
172
  return this.connectInternal();
157
173
  }
158
174
  async connectInternal() {
175
+ const replacingSocket = this.ws !== null;
176
+ // Reject and clean up an in-flight attempt before replacing its socket.
177
+ // In particular, ws.close() on CONNECTING emits an error; the replacement
178
+ // cleanup installs a sink so that cannot become an uncaught exception.
179
+ this.cancelConnectAttempt?.();
180
+ this.cancelConnectAttempt = null;
159
181
  // Cleanup any leftover ws from a previous attempt. Important for the
160
182
  // reconnect path so the new ws's events don't interleave with the
161
183
  // old one's. Mirrors the discord adapter's reconnect cleanup.
162
184
  if (this.ws) {
163
- try {
164
- this.ws.removeAllListeners();
165
- if (this.ws.readyState === 1 /* OPEN */) {
166
- this.ws.close();
167
- }
168
- }
169
- catch {
170
- /* ignore */
171
- }
185
+ closeReplacedSocket(this.ws);
172
186
  this.ws = null;
173
187
  }
188
+ if (replacingSocket) {
189
+ this.connected = false;
190
+ this.authenticated = false;
191
+ this.rejectPendingRequests('connection replaced');
192
+ }
174
193
  return new Promise((resolve, reject) => {
175
194
  let settled = false;
195
+ let cancelAttempt = null;
196
+ let connectTimer = null;
197
+ let authTimer = null;
198
+ const clearAttemptTimers = () => {
199
+ if (connectTimer) {
200
+ clearTimeout(connectTimer);
201
+ connectTimer = null;
202
+ }
203
+ if (authTimer) {
204
+ clearTimeout(authTimer);
205
+ authTimer = null;
206
+ }
207
+ };
176
208
  const settle = (err) => {
177
209
  if (settled)
178
210
  return;
179
211
  settled = true;
212
+ clearAttemptTimers();
213
+ if (cancelAttempt && this.cancelConnectAttempt === cancelAttempt) {
214
+ this.cancelConnectAttempt = null;
215
+ }
180
216
  if (err)
181
217
  reject(err);
182
218
  else
183
219
  resolve();
184
220
  };
185
- const connectTimer = setTimeout(() => {
221
+ const ws = new WebSocket(this.options.url);
222
+ this.ws = ws;
223
+ cancelAttempt = () => {
224
+ if (this.ws === ws)
225
+ this.ws = null;
226
+ settle(new Error('Fleet listener connection attempt replaced'));
227
+ closeReplacedSocket(ws);
228
+ };
229
+ this.cancelConnectAttempt = cancelAttempt;
230
+ connectTimer = setTimeout(() => {
186
231
  settle(new Error(`Fleet listener connect timeout (${this.options.connectTimeoutMs ?? 10_000}ms)`));
187
232
  try {
188
- this.ws?.close();
233
+ ws.close();
189
234
  }
190
235
  catch {
191
236
  /* ignore */
192
237
  }
193
238
  }, this.options.connectTimeoutMs ?? 10_000);
194
- let authTimer = null;
195
- this.ws = new WebSocket(this.options.url);
196
- this.ws.on('open', () => {
197
- clearTimeout(connectTimer);
239
+ ws.on('open', () => {
240
+ if (this.ws !== ws)
241
+ return;
242
+ if (connectTimer) {
243
+ clearTimeout(connectTimer);
244
+ connectTimer = null;
245
+ }
198
246
  this.connected = true;
199
247
  this.emit('connected');
200
248
  });
201
- this.ws.on('message', (data) => {
249
+ ws.on('message', (data) => {
250
+ if (this.ws !== ws)
251
+ return;
202
252
  let msg;
203
253
  try {
204
254
  msg = JSON.parse(data.toString());
@@ -212,12 +262,24 @@ export class FleetListener extends EventEmitter {
212
262
  clearTimeout(authTimer);
213
263
  authTimer = null;
214
264
  }
265
+ }, () => {
266
+ if (settled || authTimer)
267
+ return;
268
+ authTimer = setTimeout(() => {
269
+ settle(new Error(`Fleet listener auth timeout (${this.options.authTimeoutMs ?? 5_000}ms)`));
270
+ try {
271
+ ws.close();
272
+ }
273
+ catch {
274
+ /* ignore */
275
+ }
276
+ }, this.options.authTimeoutMs ?? 5_000);
215
277
  });
216
278
  });
217
- this.ws.on('close', () => {
218
- if (authTimer)
219
- clearTimeout(authTimer);
220
- clearTimeout(connectTimer);
279
+ ws.on('close', () => {
280
+ if (this.ws !== ws)
281
+ return;
282
+ clearAttemptTimers();
221
283
  this.connected = false;
222
284
  this.authenticated = false;
223
285
  this.emit('disconnected');
@@ -250,36 +312,15 @@ export class FleetListener extends EventEmitter {
250
312
  this.scheduleReconnectDeferred();
251
313
  }
252
314
  });
253
- this.ws.on('error', (err) => {
254
- clearTimeout(connectTimer);
255
- if (authTimer)
256
- clearTimeout(authTimer);
315
+ ws.on('error', (err) => {
316
+ if (this.ws !== ws)
317
+ return;
257
318
  this.emit('error', err);
258
319
  settle(err instanceof Error ? err : new Error(String(err)));
259
320
  });
260
- // After 'connected' from the server, send the auth message. Set up
261
- // an auth timeout in case the server never responds.
262
- this.once('connected', () => {
263
- // 'connected' here is OUR emitted event when ws opened; we still
264
- // need to wait for the SERVER's 'connected' message. The
265
- // handleIncomingMessage path does the auth-send — see below.
266
- });
267
- // Bound auth wait: schedule the timeout after we send authenticate.
268
- // We'll trigger this from handleIncomingMessage after sending auth.
269
- this.once('__internal:auth-sent', () => {
270
- authTimer = setTimeout(() => {
271
- settle(new Error(`Fleet listener auth timeout (${this.options.authTimeoutMs ?? 5_000}ms)`));
272
- try {
273
- this.ws?.close();
274
- }
275
- catch {
276
- /* ignore */
277
- }
278
- }, this.options.authTimeoutMs ?? 5_000);
279
- });
280
321
  });
281
322
  }
282
- handleIncomingMessage(msg, settle, clearAuthTimer) {
323
+ handleIncomingMessage(msg, settle, clearAuthTimer, startAuthTimer) {
283
324
  // Server's welcome → send authenticate.
284
325
  if (msg.type === 'connected') {
285
326
  const auth = {};
@@ -288,7 +329,7 @@ export class FleetListener extends EventEmitter {
288
329
  if (this.options.jwt)
289
330
  auth.token = this.options.jwt;
290
331
  this.send('authenticate', auth);
291
- this.emit('__internal:auth-sent');
332
+ startAuthTimer();
292
333
  return;
293
334
  }
294
335
  if (msg.type === 'authenticated') {
@@ -19,6 +19,8 @@
19
19
  * full listener module (avoids pulling ws at handler-load time).
20
20
  */
21
21
  export interface FleetListenerPublicAPI {
22
+ /** Actual transport state when supplied by the listener. */
23
+ isConnected?: () => boolean;
22
24
  disconnect: () => Promise<void>;
23
25
  getReconnectAttempts: () => number;
24
26
  isReconnecting: () => boolean;
@@ -0,0 +1,245 @@
1
+ import { z } from 'zod';
2
+ export declare const resourceSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ kind: z.ZodEnum<["inference", "comfyui", "storage", "rag", "database", "docker", "code-explorer", "camera", "microphone"]>;
5
+ hostId: z.ZodString;
6
+ declaredCapabilities: z.ZodArray<z.ZodString, "many">;
7
+ endpointRef: z.ZodString;
8
+ healthPath: z.ZodEnum<["/health", "/healthz", "/api/health", "/v1/models", "/api/tags", "/system_stats", "/_ping"]>;
9
+ permissions: z.ZodObject<{
10
+ probe: z.ZodBoolean;
11
+ use: z.ZodBoolean;
12
+ }, "strict", z.ZodTypeAny, {
13
+ use: boolean;
14
+ probe: boolean;
15
+ }, {
16
+ use: boolean;
17
+ probe: boolean;
18
+ }>;
19
+ ttlMs: z.ZodDefault<z.ZodNumber>;
20
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
21
+ }, "strict", z.ZodTypeAny, {
22
+ timeoutMs: number;
23
+ id: string;
24
+ kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
25
+ ttlMs: number;
26
+ permissions: {
27
+ use: boolean;
28
+ probe: boolean;
29
+ };
30
+ hostId: string;
31
+ declaredCapabilities: string[];
32
+ endpointRef: string;
33
+ healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
34
+ }, {
35
+ id: string;
36
+ kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
37
+ permissions: {
38
+ use: boolean;
39
+ probe: boolean;
40
+ };
41
+ hostId: string;
42
+ declaredCapabilities: string[];
43
+ endpointRef: string;
44
+ healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
45
+ timeoutMs?: number | undefined;
46
+ ttlMs?: number | undefined;
47
+ }>;
48
+ export type Resource = z.infer<typeof resourceSchema>;
49
+ declare const observationSchema: z.ZodObject<{
50
+ state: z.ZodEnum<["online", "offline", "unknown"]>;
51
+ checkedAt: z.ZodNumber;
52
+ lastSeen: z.ZodNullable<z.ZodNumber>;
53
+ latencyMs: z.ZodNullable<z.ZodNumber>;
54
+ endpointFingerprint: z.ZodString;
55
+ reason: z.ZodString;
56
+ }, "strict", z.ZodTypeAny, {
57
+ reason: string;
58
+ state: "unknown" | "offline" | "online";
59
+ lastSeen: number | null;
60
+ latencyMs: number | null;
61
+ checkedAt: number;
62
+ endpointFingerprint: string;
63
+ }, {
64
+ reason: string;
65
+ state: "unknown" | "offline" | "online";
66
+ lastSeen: number | null;
67
+ latencyMs: number | null;
68
+ checkedAt: number;
69
+ endpointFingerprint: string;
70
+ }>;
71
+ export type Observation = z.infer<typeof observationSchema>;
72
+ declare const entrySchema: z.ZodObject<{
73
+ resource: z.ZodObject<{
74
+ id: z.ZodString;
75
+ kind: z.ZodEnum<["inference", "comfyui", "storage", "rag", "database", "docker", "code-explorer", "camera", "microphone"]>;
76
+ hostId: z.ZodString;
77
+ declaredCapabilities: z.ZodArray<z.ZodString, "many">;
78
+ endpointRef: z.ZodString;
79
+ healthPath: z.ZodEnum<["/health", "/healthz", "/api/health", "/v1/models", "/api/tags", "/system_stats", "/_ping"]>;
80
+ permissions: z.ZodObject<{
81
+ probe: z.ZodBoolean;
82
+ use: z.ZodBoolean;
83
+ }, "strict", z.ZodTypeAny, {
84
+ use: boolean;
85
+ probe: boolean;
86
+ }, {
87
+ use: boolean;
88
+ probe: boolean;
89
+ }>;
90
+ ttlMs: z.ZodDefault<z.ZodNumber>;
91
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
92
+ }, "strict", z.ZodTypeAny, {
93
+ timeoutMs: number;
94
+ id: string;
95
+ kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
96
+ ttlMs: number;
97
+ permissions: {
98
+ use: boolean;
99
+ probe: boolean;
100
+ };
101
+ hostId: string;
102
+ declaredCapabilities: string[];
103
+ endpointRef: string;
104
+ healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
105
+ }, {
106
+ id: string;
107
+ kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
108
+ permissions: {
109
+ use: boolean;
110
+ probe: boolean;
111
+ };
112
+ hostId: string;
113
+ declaredCapabilities: string[];
114
+ endpointRef: string;
115
+ healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
116
+ timeoutMs?: number | undefined;
117
+ ttlMs?: number | undefined;
118
+ }>;
119
+ observation: z.ZodNullable<z.ZodObject<{
120
+ state: z.ZodEnum<["online", "offline", "unknown"]>;
121
+ checkedAt: z.ZodNumber;
122
+ lastSeen: z.ZodNullable<z.ZodNumber>;
123
+ latencyMs: z.ZodNullable<z.ZodNumber>;
124
+ endpointFingerprint: z.ZodString;
125
+ reason: z.ZodString;
126
+ }, "strict", z.ZodTypeAny, {
127
+ reason: string;
128
+ state: "unknown" | "offline" | "online";
129
+ lastSeen: number | null;
130
+ latencyMs: number | null;
131
+ checkedAt: number;
132
+ endpointFingerprint: string;
133
+ }, {
134
+ reason: string;
135
+ state: "unknown" | "offline" | "online";
136
+ lastSeen: number | null;
137
+ latencyMs: number | null;
138
+ checkedAt: number;
139
+ endpointFingerprint: string;
140
+ }>>;
141
+ }, "strict", z.ZodTypeAny, {
142
+ resource: {
143
+ timeoutMs: number;
144
+ id: string;
145
+ kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
146
+ ttlMs: number;
147
+ permissions: {
148
+ use: boolean;
149
+ probe: boolean;
150
+ };
151
+ hostId: string;
152
+ declaredCapabilities: string[];
153
+ endpointRef: string;
154
+ healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
155
+ };
156
+ observation: {
157
+ reason: string;
158
+ state: "unknown" | "offline" | "online";
159
+ lastSeen: number | null;
160
+ latencyMs: number | null;
161
+ checkedAt: number;
162
+ endpointFingerprint: string;
163
+ } | null;
164
+ }, {
165
+ resource: {
166
+ id: string;
167
+ kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
168
+ permissions: {
169
+ use: boolean;
170
+ probe: boolean;
171
+ };
172
+ hostId: string;
173
+ declaredCapabilities: string[];
174
+ endpointRef: string;
175
+ healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
176
+ timeoutMs?: number | undefined;
177
+ ttlMs?: number | undefined;
178
+ };
179
+ observation: {
180
+ reason: string;
181
+ state: "unknown" | "offline" | "online";
182
+ lastSeen: number | null;
183
+ latencyMs: number | null;
184
+ checkedAt: number;
185
+ endpointFingerprint: string;
186
+ } | null;
187
+ }>;
188
+ export type ResourceStatus = z.infer<typeof entrySchema> & {
189
+ state: 'online' | 'offline' | 'unknown' | 'stale';
190
+ reason: string;
191
+ /** No heuristic RAM or inferred utilisation. This tranche has no load collector. */
192
+ load: null;
193
+ usageConfirmed: false;
194
+ };
195
+ /** Declarations and observations only: never the resolved endpoint value or the internal fingerprint. */
196
+ export declare function publicResourceStatus(entry: ResourceStatus): {
197
+ resource: {
198
+ timeoutMs: number;
199
+ id: string;
200
+ kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
201
+ ttlMs: number;
202
+ permissions: {
203
+ use: boolean;
204
+ probe: boolean;
205
+ };
206
+ hostId: string;
207
+ declaredCapabilities: string[];
208
+ endpointRef: string;
209
+ healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
210
+ };
211
+ state: "unknown" | "offline" | "online" | "stale";
212
+ reason: string;
213
+ checkedAt: number | null;
214
+ lastSeen: number | null;
215
+ latencyMs: number | null;
216
+ load: null;
217
+ usageConfirmed: false;
218
+ };
219
+ export type PublicResourceStatus = ReturnType<typeof publicResourceStatus>;
220
+ /** The RagChat connector reads this reference only; a selection never redirects it. */
221
+ export declare const RAGCHAT_ENDPOINT_REF = "RAGCHAT_BASE_URL";
222
+ export declare function selectionWarning(selected: Pick<ResourceStatus, 'resource'> | null): string | undefined;
223
+ export declare class ResourceCatalog {
224
+ readonly filename: string;
225
+ private readonly now;
226
+ constructor(filename?: string, now?: () => number);
227
+ private read;
228
+ private update;
229
+ add(input: unknown): Promise<Resource>;
230
+ remove(id: string): Promise<{
231
+ removed: string;
232
+ }>;
233
+ private status;
234
+ list(): Promise<ResourceStatus[]>;
235
+ probe(id: string): Promise<ResourceStatus>;
236
+ select(capability: string, kind?: Resource['kind']): Promise<{
237
+ selected: ResourceStatus | null;
238
+ excluded: Array<{
239
+ id: string;
240
+ reason: string;
241
+ }>;
242
+ reason: string;
243
+ }>;
244
+ }
245
+ export {};
@@ -0,0 +1,211 @@
1
+ /** Explicit resources; health is not proof that a model/capability works. */
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { createHash } from 'node:crypto';
6
+ import { z } from 'zod';
7
+ import { writeJsonAtomic } from '../utils/atomic-write.js';
8
+ const identifier = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,95}$/);
9
+ export const resourceSchema = z.object({
10
+ id: identifier,
11
+ kind: z.enum(['inference', 'comfyui', 'storage', 'rag', 'database', 'docker', 'code-explorer', 'camera', 'microphone']),
12
+ hostId: identifier,
13
+ declaredCapabilities: z.array(identifier).min(1).max(32),
14
+ endpointRef: z.string().regex(/^[A-Z][A-Z0-9_]{0,95}$/),
15
+ healthPath: z.enum(['/health', '/healthz', '/api/health', '/v1/models', '/api/tags', '/system_stats', '/_ping']),
16
+ permissions: z.object({ probe: z.boolean(), use: z.boolean() }).strict(),
17
+ ttlMs: z.number().int().min(1000).max(3600000).default(60000),
18
+ timeoutMs: z.number().int().min(50).max(5000).default(1000),
19
+ }).strict();
20
+ const observationSchema = z.object({
21
+ state: z.enum(['online', 'offline', 'unknown']),
22
+ checkedAt: z.number().nonnegative(),
23
+ lastSeen: z.number().nonnegative().nullable(),
24
+ latencyMs: z.number().nonnegative().nullable(),
25
+ endpointFingerprint: z.string(),
26
+ reason: z.string(),
27
+ }).strict();
28
+ const entrySchema = z.object({ resource: resourceSchema, observation: observationSchema.nullable() }).strict();
29
+ const storeSchema = z.object({ version: z.literal(1), entries: z.array(entrySchema).max(100) }).strict();
30
+ /** Declarations and observations only: never the resolved endpoint value or the internal fingerprint. */
31
+ export function publicResourceStatus(entry) {
32
+ return { resource: entry.resource, state: entry.state, reason: entry.reason,
33
+ checkedAt: entry.observation?.checkedAt ?? null, lastSeen: entry.observation?.lastSeen ?? null,
34
+ latencyMs: entry.observation?.latencyMs ?? null, load: entry.load, usageConfirmed: entry.usageConfirmed };
35
+ }
36
+ /** The RagChat connector reads this reference only; a selection never redirects it. */
37
+ export const RAGCHAT_ENDPOINT_REF = 'RAGCHAT_BASE_URL';
38
+ export function selectionWarning(selected) {
39
+ if (!selected || selected.resource.kind !== 'rag' || selected.resource.endpointRef === RAGCHAT_ENDPOINT_REF)
40
+ return undefined;
41
+ return `ragchat_search interroge toujours ${RAGCHAT_ENDPOINT_REF} ; cette ressource référence ${selected.resource.endpointRef} et la sélection ne redirige pas le connecteur.`;
42
+ }
43
+ const allowedPaths = {
44
+ inference: ['/health', '/healthz', '/v1/models', '/api/tags'],
45
+ comfyui: ['/system_stats'],
46
+ rag: ['/health', '/healthz', '/api/health'],
47
+ docker: ['/_ping'],
48
+ 'code-explorer': ['/health', '/healthz', '/api/health'],
49
+ };
50
+ function endpoint(resource) {
51
+ const raw = process.env[resource.endpointRef];
52
+ if (!raw)
53
+ throw new Error('ENDPOINT_UNCONFIGURED');
54
+ let url;
55
+ try {
56
+ url = new URL(raw);
57
+ }
58
+ catch {
59
+ throw new Error('ENDPOINT_INVALID');
60
+ }
61
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== '/') {
62
+ throw new Error('ENDPOINT_INVALID');
63
+ }
64
+ url.pathname = resource.healthPath;
65
+ return { url, fingerprint: createHash('sha256').update(url.href).digest('hex') };
66
+ }
67
+ export class ResourceCatalog {
68
+ filename;
69
+ now;
70
+ constructor(filename = path.join(os.homedir(), '.codebuddy', 'resources', 'catalog.json'), now = Date.now) {
71
+ this.filename = filename;
72
+ this.now = now;
73
+ }
74
+ async read() {
75
+ try {
76
+ const stat = await fs.lstat(this.filename);
77
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024)
78
+ throw new Error('INVALID_CATALOG_FILE');
79
+ const parsed = storeSchema.safeParse(JSON.parse(await fs.readFile(this.filename, 'utf8')));
80
+ if (!parsed.success || new Set(parsed.data.entries.map(e => e.resource.id)).size !== parsed.data.entries.length) {
81
+ throw new Error('INVALID_CATALOG');
82
+ }
83
+ return parsed.data;
84
+ }
85
+ catch (error) {
86
+ if (error.code === 'ENOENT')
87
+ return { version: 1, entries: [] };
88
+ throw new Error('INVALID_CATALOG: existing data was not replaced');
89
+ }
90
+ }
91
+ async update(action) {
92
+ const directory = path.dirname(this.filename);
93
+ await fs.mkdir(directory, { recursive: true, mode: 0o700 });
94
+ const stat = await fs.lstat(directory);
95
+ if (!stat.isDirectory() || stat.isSymbolicLink())
96
+ throw new Error('INVALID_CATALOG_DIRECTORY');
97
+ await fs.chmod(directory, 0o700);
98
+ const lock = `${this.filename}.lock`;
99
+ try {
100
+ await fs.mkdir(lock, { mode: 0o700 });
101
+ }
102
+ catch {
103
+ throw new Error('CATALOG_BUSY: another writer or unrecovered lock');
104
+ }
105
+ try {
106
+ const store = await this.read();
107
+ const result = await action(store);
108
+ await writeJsonAtomic(this.filename, store, { mode: 0o600 });
109
+ return result;
110
+ }
111
+ finally {
112
+ await fs.rmdir(lock);
113
+ }
114
+ }
115
+ async add(input) {
116
+ const parsed = resourceSchema.safeParse(input);
117
+ if (!parsed.success)
118
+ throw new Error('INVALID_RESOURCE: check schema; inline endpoints and extra fields are forbidden');
119
+ return this.update(async (store) => {
120
+ if (store.entries.some(e => e.resource.id === parsed.data.id))
121
+ throw new Error('RESOURCE_EXISTS');
122
+ if (store.entries.length >= 100)
123
+ throw new Error('CATALOG_FULL');
124
+ store.entries.push({ resource: parsed.data, observation: null });
125
+ return parsed.data;
126
+ });
127
+ }
128
+ async remove(id) {
129
+ return this.update(async (store) => {
130
+ const index = store.entries.findIndex(e => e.resource.id === id);
131
+ if (index < 0)
132
+ throw new Error('RESOURCE_NOT_FOUND');
133
+ store.entries.splice(index, 1);
134
+ return { removed: id };
135
+ });
136
+ }
137
+ status(entry) {
138
+ const { resource, observation } = entry;
139
+ let state = observation?.state ?? 'unknown';
140
+ let reason = observation?.reason ?? 'NEVER_PROBED';
141
+ if (observation && (this.now() - observation.checkedAt >= resource.ttlMs || observation.checkedAt > this.now())) {
142
+ state = 'stale';
143
+ reason = 'OBSERVATION_EXPIRED';
144
+ }
145
+ try {
146
+ if (observation && observation.endpointFingerprint !== endpoint(resource).fingerprint) {
147
+ state = 'unknown';
148
+ reason = 'ENDPOINT_CHANGED';
149
+ }
150
+ }
151
+ catch {
152
+ state = 'unknown';
153
+ reason = 'ENDPOINT_UNCONFIGURED_OR_INVALID';
154
+ }
155
+ return { ...entry, state, reason, load: null, usageConfirmed: false };
156
+ }
157
+ async list() { return (await this.read()).entries.map(e => this.status(e)); }
158
+ async probe(id) {
159
+ // Lock covers the bounded probe so a later probe cannot be overwritten by an older completion.
160
+ return this.update(async (store) => {
161
+ const entry = store.entries.find(e => e.resource.id === id);
162
+ if (!entry)
163
+ throw new Error('RESOURCE_NOT_FOUND');
164
+ const r = entry.resource;
165
+ if (!r.permissions.probe)
166
+ throw new Error('PROBE_NOT_PERMITTED');
167
+ if (!allowedPaths[r.kind]?.includes(r.healthPath))
168
+ throw new Error('NO_READ_ONLY_PROBE_FOR_KIND_PATH');
169
+ const target = endpoint(r);
170
+ const start = this.now();
171
+ let state = 'offline';
172
+ let reason = 'PROBE_FAILED';
173
+ try {
174
+ const response = await fetch(target.url, { method: 'GET', redirect: 'manual', signal: AbortSignal.timeout(r.timeoutMs) });
175
+ state = response.ok ? 'online' : 'offline';
176
+ reason = response.ok ? 'HTTP_HEALTH_OK_NOT_USAGE_PROOF' : `HTTP_${response.status}`;
177
+ // Never parse service content, model lists, database contents or device frames.
178
+ await response.body?.cancel();
179
+ }
180
+ catch {
181
+ state = 'offline';
182
+ reason = 'UNREACHABLE_OR_TIMEOUT';
183
+ }
184
+ const checkedAt = this.now();
185
+ entry.observation = { state, reason, checkedAt, lastSeen: state === 'online' ? checkedAt
186
+ : entry.observation?.endpointFingerprint === target.fingerprint ? entry.observation.lastSeen : null,
187
+ latencyMs: state === 'online' ? Math.max(0, checkedAt - start) : null, endpointFingerprint: target.fingerprint };
188
+ return this.status(entry);
189
+ });
190
+ }
191
+ async select(capability, kind) {
192
+ if (!identifier.safeParse(capability).success)
193
+ throw new Error('INVALID_CAPABILITY');
194
+ const excluded = [];
195
+ const candidates = (await this.list()).filter(e => {
196
+ const reason = kind && e.resource.kind !== kind ? 'KIND_MISMATCH'
197
+ : !e.resource.declaredCapabilities.includes(capability) ? 'CAPABILITY_NOT_DECLARED'
198
+ : !e.resource.permissions.use ? 'USE_NOT_PERMITTED'
199
+ : e.state !== 'online' ? e.reason : null;
200
+ if (reason)
201
+ excluded.push({ id: e.resource.id, reason });
202
+ return !reason;
203
+ });
204
+ // Load unknown: do not fabricate free capacity. Stable deterministic latency/id choice only.
205
+ candidates.sort((a, b) => (a.observation?.latencyMs ?? Infinity) - (b.observation?.latencyMs ?? Infinity)
206
+ || a.resource.id.localeCompare(b.resource.id));
207
+ return { selected: candidates[0] ?? null, excluded,
208
+ reason: candidates.length ? 'FRESH_HEALTH_DECLARED_CAPABILITY_LOAD_UNKNOWN' : 'NO_ELIGIBLE_RESOURCE' };
209
+ }
210
+ }
211
+ //# sourceMappingURL=resource-catalog.js.map