@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,9 @@
1
+ export interface DeviceSessionIdentity {
2
+ readonly deviceId?: string;
3
+ readonly profile?: 'agent' | 'companion';
4
+ readonly identity?: 'owner';
5
+ readonly amr?: readonly string[];
6
+ }
7
+ /** Companion integration seam: signed identity of the current WS turn only. */
8
+ export declare function getDeviceSessionIdentity(): DeviceSessionIdentity | undefined;
9
+ export declare function withDeviceSessionIdentity<T>(identity: DeviceSessionIdentity, run: () => Promise<T>): Promise<T>;
@@ -0,0 +1,13 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const context = new AsyncLocalStorage();
3
+ /** Companion integration seam: signed identity of the current WS turn only. */
4
+ export function getDeviceSessionIdentity() {
5
+ return context.getStore();
6
+ }
7
+ export function withDeviceSessionIdentity(identity, run) {
8
+ return context.run(Object.freeze({
9
+ ...identity,
10
+ ...(identity.amr ? { amr: Object.freeze([...identity.amr]) } : {}),
11
+ }), run);
12
+ }
13
+ //# sourceMappingURL=device-session-context.js.map
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ export declare const PAIRING_TTL_MS: number;
3
+ export declare const DEVICE_NONCE_TTL_MS = 60000;
4
+ declare const deviceSchema: z.ZodObject<{
5
+ deviceId: z.ZodString;
6
+ deviceName: z.ZodString;
7
+ publicKeyJwk: z.ZodObject<{
8
+ kty: z.ZodLiteral<"EC">;
9
+ crv: z.ZodLiteral<"P-256">;
10
+ x: z.ZodString;
11
+ y: z.ZodString;
12
+ }, "strip", z.ZodTypeAny, {
13
+ y: string;
14
+ x: string;
15
+ kty: "EC";
16
+ crv: "P-256";
17
+ }, {
18
+ y: string;
19
+ x: string;
20
+ kty: "EC";
21
+ crv: "P-256";
22
+ }>;
23
+ createdAt: z.ZodString;
24
+ updatedAt: z.ZodString;
25
+ lastVerifiedAt: z.ZodOptional<z.ZodString>;
26
+ revokedAt: z.ZodOptional<z.ZodString>;
27
+ }, "strip", z.ZodTypeAny, {
28
+ updatedAt: string;
29
+ createdAt: string;
30
+ deviceId: string;
31
+ deviceName: string;
32
+ publicKeyJwk: {
33
+ y: string;
34
+ x: string;
35
+ kty: "EC";
36
+ crv: "P-256";
37
+ };
38
+ lastVerifiedAt?: string | undefined;
39
+ revokedAt?: string | undefined;
40
+ }, {
41
+ updatedAt: string;
42
+ createdAt: string;
43
+ deviceId: string;
44
+ deviceName: string;
45
+ publicKeyJwk: {
46
+ y: string;
47
+ x: string;
48
+ kty: "EC";
49
+ crv: "P-256";
50
+ };
51
+ lastVerifiedAt?: string | undefined;
52
+ revokedAt?: string | undefined;
53
+ }>;
54
+ export type AuthDevice = z.infer<typeof deviceSchema>;
55
+ export type DeviceAudit = (action: 'device_register' | 'device_verify' | 'device_revoke', allowed: boolean) => void;
56
+ export declare class DeviceAuthError extends Error {
57
+ readonly status: number;
58
+ constructor(status: number);
59
+ }
60
+ /** Only public keys persist; nonces are bounded, process-local and lost on restart. */
61
+ export declare class DeviceAuthStore {
62
+ readonly file: string;
63
+ private readonly now;
64
+ private readonly nonces;
65
+ private readonly audit;
66
+ constructor(file?: string, now?: () => number, audit?: DeviceAudit);
67
+ private update;
68
+ list(): AuthDevice[];
69
+ isActive(deviceId: string): boolean;
70
+ /** Local CLI only. There is deliberately no HTTP endpoint for issuing codes. */
71
+ createPairing(): {
72
+ pairingCode: string;
73
+ expiresAt: string;
74
+ };
75
+ register(pairingCode: unknown, deviceName: unknown, publicKeyJwk: unknown): Promise<{
76
+ deviceId: string;
77
+ }>;
78
+ challenge(deviceId: unknown): {
79
+ nonce: string;
80
+ expiresAt: string;
81
+ };
82
+ verify(deviceId: unknown, nonce: unknown, signature: unknown, secret: string): Promise<{
83
+ token: string;
84
+ }>;
85
+ revoke(deviceId: string): void;
86
+ rename(deviceId: string, deviceName: string): void;
87
+ }
88
+ export declare function getDeviceAuthStore(): DeviceAuthStore;
89
+ export {};
@@ -0,0 +1,261 @@
1
+ import { createHash, randomBytes, randomInt, randomUUID, webcrypto } from 'node:crypto';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { auditLogger } from '../../security/audit-logger.js';
6
+ import { readDeviceStoreFile, updateDeviceStoreFile } from '../../utils/device-store-file.js';
7
+ import { generateToken } from './jwt.js';
8
+ export const PAIRING_TTL_MS = 10 * 60_000;
9
+ export const DEVICE_NONCE_TTL_MS = 60_000;
10
+ const CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
11
+ const publicKeySchema = z.object({
12
+ kty: z.literal('EC'), crv: z.literal('P-256'),
13
+ x: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
14
+ y: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
15
+ });
16
+ const nameSchema = z.string().trim().min(1).max(80).regex(/^[^\p{Cc}\p{Cf}<>]*$/u);
17
+ const deviceSchema = z.object({
18
+ deviceId: z.string().uuid(), deviceName: nameSchema,
19
+ publicKeyJwk: publicKeySchema,
20
+ createdAt: z.string().datetime(), updatedAt: z.string().datetime(),
21
+ lastVerifiedAt: z.string().datetime().optional(), revokedAt: z.string().datetime().optional(),
22
+ });
23
+ const authDataSchema = z.object({
24
+ devices: z.array(deviceSchema),
25
+ pairings: z.array(z.object({ hash: z.string(), expiresAt: z.number() })),
26
+ });
27
+ export class DeviceAuthError extends Error {
28
+ status;
29
+ constructor(status) {
30
+ super(status === 400 ? 'Invalid pairing request' : status === 401 ? 'Authentication failed' : 'Device authentication unavailable');
31
+ this.status = status;
32
+ }
33
+ }
34
+ function authData(envelope) {
35
+ if (envelope.deviceAuth === undefined)
36
+ return { devices: [], pairings: [] };
37
+ const parsed = authDataSchema.safeParse(envelope.deviceAuth);
38
+ if (!parsed.success)
39
+ throw new DeviceAuthError(503);
40
+ return parsed.data;
41
+ }
42
+ function pairingHash(code) {
43
+ return createHash('sha256').update(code).digest('hex');
44
+ }
45
+ /** Accept JOSE/P1363 and Android's native, minimally encoded ASN.1 DER. */
46
+ function signatureCandidates(encoded) {
47
+ if (encoded.length > 100 || !/^[A-Za-z0-9_+/-]+={0,2}$/.test(encoded))
48
+ throw new DeviceAuthError(401);
49
+ const bytes = Buffer.from(encoded, 'base64url');
50
+ const normalized = encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
51
+ if (bytes.toString('base64url') !== normalized)
52
+ throw new DeviceAuthError(401);
53
+ const candidates = bytes.length === 64 ? [bytes] : [];
54
+ // P-256 DER always uses short lengths (at most 72 bytes in total).
55
+ if (bytes.length < 8 || bytes.length > 72 || bytes[0] !== 0x30 || bytes[1] !== bytes.length - 2)
56
+ return candidates;
57
+ let cursor = 2;
58
+ const raw = Buffer.alloc(64);
59
+ for (let component = 0; component < 2; component++) {
60
+ if (bytes[cursor++] !== 0x02)
61
+ return candidates;
62
+ const length = bytes[cursor++];
63
+ if (!length || length > 33 || cursor + length > bytes.length)
64
+ return candidates;
65
+ let integer = bytes.subarray(cursor, cursor + length);
66
+ cursor += length;
67
+ const first = integer[0];
68
+ if (first === undefined || (first & 0x80) !== 0)
69
+ return candidates;
70
+ if (integer.length > 1 && first === 0) {
71
+ const second = integer[1];
72
+ if (second === undefined || (second & 0x80) === 0)
73
+ return candidates;
74
+ integer = integer.subarray(1);
75
+ }
76
+ if (integer.length > 32)
77
+ return candidates;
78
+ integer.copy(raw, (component + 1) * 32 - integer.length);
79
+ }
80
+ if (cursor === bytes.length)
81
+ candidates.push(raw);
82
+ return candidates;
83
+ }
84
+ /** Only public keys persist; nonces are bounded, process-local and lost on restart. */
85
+ export class DeviceAuthStore {
86
+ file;
87
+ now;
88
+ nonces = new Map();
89
+ audit;
90
+ constructor(file = join(homedir(), '.codebuddy', 'devices.json'), now = Date.now, audit) {
91
+ this.file = file;
92
+ this.now = now;
93
+ if (audit)
94
+ this.audit = audit;
95
+ else {
96
+ auditLogger.init({ logDir: join(dirname(file), 'audit') });
97
+ this.audit = (action, allowed) => auditLogger.log({
98
+ action, decision: allowed ? 'allow' : 'block', source: 'device-auth',
99
+ });
100
+ }
101
+ }
102
+ update(fn) {
103
+ return updateDeviceStoreFile(this.file, envelope => {
104
+ const data = authData(envelope);
105
+ const result = fn(data);
106
+ envelope.deviceAuth = data;
107
+ return result;
108
+ });
109
+ }
110
+ list() {
111
+ return authData(readDeviceStoreFile(this.file)).devices;
112
+ }
113
+ isActive(deviceId) {
114
+ try {
115
+ return this.list().some(device => device.deviceId === deviceId && !device.revokedAt);
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ }
121
+ /** Local CLI only. There is deliberately no HTTP endpoint for issuing codes. */
122
+ createPairing() {
123
+ return this.update(data => {
124
+ data.pairings = data.pairings.filter(code => code.expiresAt > this.now());
125
+ if (data.pairings.length >= 20)
126
+ throw new DeviceAuthError(503);
127
+ let pairingCode;
128
+ do {
129
+ pairingCode = Array.from({ length: 8 }, () => CODE_ALPHABET[randomInt(CODE_ALPHABET.length)]).join('');
130
+ } while (data.pairings.some(code => code.hash === pairingHash(pairingCode)));
131
+ const expiresAt = this.now() + PAIRING_TTL_MS;
132
+ data.pairings.push({ hash: pairingHash(pairingCode), expiresAt });
133
+ return { pairingCode, expiresAt: new Date(expiresAt).toISOString() };
134
+ });
135
+ }
136
+ async register(pairingCode, deviceName, publicKeyJwk) {
137
+ let allowed = false;
138
+ try {
139
+ const name = nameSchema.safeParse(deviceName);
140
+ const key = publicKeySchema.safeParse(publicKeyJwk);
141
+ if (typeof pairingCode !== 'string' || !/^[A-HJ-NP-Z2-9]{8}$/.test(pairingCode)
142
+ || !name.success || !key.success || (publicKeyJwk && typeof publicKeyJwk === 'object' && 'd' in publicKeyJwk)) {
143
+ throw new DeviceAuthError(400);
144
+ }
145
+ try {
146
+ await webcrypto.subtle.importKey('jwk', key.data, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
147
+ }
148
+ catch {
149
+ throw new DeviceAuthError(400);
150
+ }
151
+ const result = this.update(data => {
152
+ const index = data.pairings.findIndex(code => code.hash === pairingHash(pairingCode) && code.expiresAt > this.now());
153
+ if (index < 0)
154
+ throw new DeviceAuthError(400);
155
+ data.pairings.splice(index, 1);
156
+ const deviceId = randomUUID();
157
+ const date = new Date(this.now()).toISOString();
158
+ data.devices.push({ deviceId, deviceName: name.data, publicKeyJwk: key.data, createdAt: date, updatedAt: date });
159
+ return { deviceId };
160
+ });
161
+ allowed = true;
162
+ return result;
163
+ }
164
+ finally {
165
+ this.audit('device_register', allowed);
166
+ }
167
+ }
168
+ challenge(deviceId) {
169
+ if (typeof deviceId !== 'string' || !this.isActive(deviceId))
170
+ throw new DeviceAuthError(401);
171
+ for (const [id, entry] of this.nonces)
172
+ if (entry.expiresAt <= this.now())
173
+ this.nonces.delete(id);
174
+ if (this.nonces.size >= 1000 && !this.nonces.has(deviceId))
175
+ throw new DeviceAuthError(503);
176
+ const nonce = randomBytes(32).toString('base64url');
177
+ const expiresAt = this.now() + DEVICE_NONCE_TTL_MS;
178
+ this.nonces.set(deviceId, { nonce, expiresAt });
179
+ return { nonce, expiresAt: new Date(expiresAt).toISOString() };
180
+ }
181
+ async verify(deviceId, nonce, signature, secret) {
182
+ let allowed = false;
183
+ try {
184
+ if (typeof deviceId !== 'string' || typeof nonce !== 'string' || typeof signature !== 'string')
185
+ throw new DeviceAuthError(401);
186
+ const challenge = this.nonces.get(deviceId);
187
+ if (!challenge || challenge.nonce !== nonce)
188
+ throw new DeviceAuthError(401);
189
+ // Consume before any async crypto work, including failed signature attempts.
190
+ this.nonces.delete(deviceId);
191
+ if (challenge.expiresAt <= this.now())
192
+ throw new DeviceAuthError(401);
193
+ const candidates = signatureCandidates(signature);
194
+ const device = this.list().find(item => item.deviceId === deviceId && !item.revokedAt);
195
+ if (!device)
196
+ throw new DeviceAuthError(401);
197
+ const key = await webcrypto.subtle.importKey('jwk', device.publicKeyJwk, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
198
+ let valid = false;
199
+ for (const candidate of candidates) {
200
+ if (await webcrypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, candidate, Buffer.from(`${deviceId}.${nonce}`, 'utf8'))) {
201
+ valid = true;
202
+ break;
203
+ }
204
+ }
205
+ if (!valid)
206
+ throw new DeviceAuthError(401);
207
+ if (!secret || secret === 'change-me-in-production')
208
+ throw new DeviceAuthError(503);
209
+ // Re-read inside the transaction: revoke may have run during verification.
210
+ const token = this.update(data => {
211
+ const current = data.devices.find(item => item.deviceId === deviceId && !item.revokedAt);
212
+ if (!current)
213
+ throw new DeviceAuthError(401);
214
+ current.lastVerifiedAt = new Date(this.now()).toISOString();
215
+ return generateToken({
216
+ sub: deviceId, type: 'user', scopes: ['chat', 'chat:stream', 'sessions', 'tools'],
217
+ amr: ['biometric', 'device'], profile: 'agent', identity: 'owner',
218
+ }, secret, '1h');
219
+ });
220
+ allowed = true;
221
+ return { token };
222
+ }
223
+ finally {
224
+ this.audit('device_verify', allowed);
225
+ }
226
+ }
227
+ revoke(deviceId) {
228
+ let allowed = false;
229
+ try {
230
+ this.update(data => {
231
+ const device = data.devices.find(item => item.deviceId === deviceId);
232
+ if (!device)
233
+ throw new DeviceAuthError(401);
234
+ device.revokedAt ??= new Date(this.now()).toISOString();
235
+ device.updatedAt = device.revokedAt;
236
+ });
237
+ this.nonces.delete(deviceId);
238
+ allowed = true;
239
+ }
240
+ finally {
241
+ this.audit('device_revoke', allowed);
242
+ }
243
+ }
244
+ rename(deviceId, deviceName) {
245
+ const name = nameSchema.safeParse(deviceName);
246
+ if (!name.success)
247
+ throw new DeviceAuthError(400);
248
+ this.update(data => {
249
+ const device = data.devices.find(item => item.deviceId === deviceId);
250
+ if (!device)
251
+ throw new DeviceAuthError(401);
252
+ device.deviceName = name.data;
253
+ device.updatedAt = new Date(this.now()).toISOString();
254
+ });
255
+ }
256
+ }
257
+ let store;
258
+ export function getDeviceAuthStore() {
259
+ return store ??= new DeviceAuthStore();
260
+ }
261
+ //# sourceMappingURL=device-store.js.map
@@ -0,0 +1,3 @@
1
+ import type { JwtPayload } from '../types.js';
2
+ /** Legacy tokens do not touch the device store. */
3
+ export declare function isDeviceAccessTokenActive(payload: JwtPayload): boolean;
@@ -0,0 +1,6 @@
1
+ import { getDeviceAuthStore } from './device-store.js';
2
+ /** Legacy tokens do not touch the device store. */
3
+ export function isDeviceAccessTokenActive(payload) {
4
+ return !Array.isArray(payload.amr) || !payload.amr.includes('device') || (Number.isFinite(payload.exp) && payload.exp * 1000 > Date.now() && getDeviceAuthStore().isActive(payload.sub));
5
+ }
6
+ //# sourceMappingURL=device-token.js.map
@@ -146,6 +146,10 @@ export function refreshToken(token, secret, expiresIn = '24h') {
146
146
  if (!payload) {
147
147
  return null;
148
148
  }
149
+ // Device tokens require a fresh key proof after one hour. In particular,
150
+ // never turn them into legacy tokens that lose the revocation marker.
151
+ if (Array.isArray(payload.amr) && payload.amr.includes('device'))
152
+ return null;
149
153
  return generateToken({
150
154
  sub: payload.sub,
151
155
  scopes: payload.scopes,
@@ -1,13 +1,3 @@
1
- /**
2
- * API Server
3
- *
4
- * Main entry point for the Code Buddy REST API and WebSocket server.
5
- *
6
- * Usage:
7
- * npm run server
8
- * # or
9
- * codebuddy server --port 3000
10
- */
11
1
  import { Application } from 'express';
12
2
  import { Server as HttpServer } from 'http';
13
3
  import type { ServerConfig } from './types.js';
@@ -1,3 +1,4 @@
1
+ import { createA2AJsonRpcRoutes } from './routes/a2a-jsonrpc.js';
1
2
  /**
2
3
  * API Server
3
4
  *
@@ -16,6 +17,7 @@ import cors from 'cors';
16
17
  import { createServer } from 'http';
17
18
  import { createServer as createHttpsServer, Server as HttpsServer } from 'https';
18
19
  import { resolveServerTlsOptions } from './tls-config.js';
20
+ import { createDeviceAuthRoutes } from './routes/device-auth.js';
19
21
  const _require = createRequire(import.meta.url);
20
22
  let SERVER_VERSION = '0.0.0';
21
23
  try {
@@ -176,6 +178,10 @@ function createApp(config, cognitiveHub) {
176
178
  ],
177
179
  }));
178
180
  }
181
+ // Separate opt-in peer bearer auth, bounded parser and rate limit; existing REST/JWT routes unchanged.
182
+ if (process.env.CODEBUDDY_A2A_PEERS) {
183
+ app.use(createA2AJsonRpcRoutes({ publicUrl: process.env.CODEBUDDY_A2A_PUBLIC_URL ?? `http://127.0.0.1:${config.port}/a2a/v1` }));
184
+ }
179
185
  // Body parsing
180
186
  app.use(express.json({ limit: config.maxRequestSize }));
181
187
  app.use(express.urlencoded({ extended: true, limit: config.maxRequestSize }));
@@ -203,6 +209,8 @@ function createApp(config, cognitiveHub) {
203
209
  res.status(404).end();
204
210
  });
205
211
  }
212
+ // Android pairs with a local code and proves key possession before JWT auth.
213
+ app.use('/api/auth/device', createDeviceAuthRoutes(config.jwtSecret));
206
214
  // Authentication (applied after public health/metrics/mobile endpoints)
207
215
  app.use(createAuthMiddleware(config));
208
216
  // A2A routes (auth-based, exempt from CSRF) — must be mounted BEFORE CSRF middleware
@@ -1191,6 +1199,22 @@ export async function startServer(userConfig = {}) {
1191
1199
  logger.info(`Metrics Dashboard: ${baseUrl}/api/metrics/dashboard`);
1192
1200
  logger.info(`Docs: ${baseUrl}/api/docs`);
1193
1201
  logger.info(`WebSocket: ${config.websocketEnabled ? 'Enabled (/ws)' : 'Disabled'}`);
1202
+ // Fleet rooms (opt-in): persistent signed messages between members. Wired
1203
+ // after listen so auth audiences use the port actually bound.
1204
+ if (config.websocketEnabled && process.env.CODEBUDDY_FLEET_ROOMS === 'true') {
1205
+ try {
1206
+ const address = server.address();
1207
+ const port = address && typeof address === 'object' ? address.port : config.port;
1208
+ const { startFleetRooms } = await import('../fleet/rooms/room-server.js');
1209
+ server._fleetRooms =
1210
+ startFleetRooms({ port });
1211
+ }
1212
+ catch (error) {
1213
+ logger.warn('[fleet-rooms] not enabled', {
1214
+ error: error instanceof Error ? error.message : String(error),
1215
+ });
1216
+ }
1217
+ }
1194
1218
  // Sensory nervous-system bridge (opt-in): ingress for the Rust buddy-sense
1195
1219
  // daemon → internal event bus → reactions. Loopback-only.
1196
1220
  if (process.env.CODEBUDDY_SENSORY === 'true' && !sensoryWired) {
@@ -1236,6 +1260,9 @@ export async function startServer(userConfig = {}) {
1236
1260
  logger.warn(`[respond-policy] ${warning}`);
1237
1261
  }
1238
1262
  const responseDecider = createResponseDecider({ chimeIn: responsePolicy.chimeIn });
1263
+ const { setVoiceResponseDecider } = await import('../sensory/voice-loop.js');
1264
+ setVoiceResponseDecider(responseDecider);
1265
+ sensoryTeardown.push(() => setVoiceResponseDecider(undefined));
1239
1266
  {
1240
1267
  const { shouldWireVisionReaction, wireVisionReaction } = await import('../sensory/vision-reaction.js');
1241
1268
  if (shouldWireVisionReaction({ camera: process.env.CODEBUDDY_SENSORY_CAMERA, token: sensoryToken })) {
@@ -2044,6 +2071,7 @@ export async function stopServer(server) {
2044
2071
  const unwireCognition = server
2045
2072
  ._unwireCognition;
2046
2073
  unwireCognition?.();
2074
+ server._fleetRooms?.stop();
2047
2075
  // Detach the channel-A2A bridge handler + shut down the
2048
2076
  // ChannelManager so polling loops (Telegram, Discord, ...) stop.
2049
2077
  const bridgeStop = server
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { validateApiKey, hasScope as _hasScope } from '../auth/api-keys.js';
7
7
  import { verifyToken } from '../auth/jwt.js';
8
+ import { isDeviceAccessTokenActive } from '../auth/device-token.js';
8
9
  import { API_ERRORS } from '../types.js';
9
10
  // Re-export for external use
10
11
  export const hasScope = _hasScope;
@@ -84,7 +85,7 @@ export function createAuthMiddleware(config) {
84
85
  }
85
86
  // Try JWT token
86
87
  const payload = verifyToken(token, config.jwtSecret);
87
- if (!payload) {
88
+ if (!payload || !isDeviceAccessTokenActive(payload)) {
88
89
  return res.status(401).json({
89
90
  ...API_ERRORS.UNAUTHORIZED,
90
91
  message: 'Invalid or expired token',
@@ -195,7 +196,7 @@ export function optionalAuth(config) {
195
196
  }
196
197
  // Try JWT
197
198
  const payload = verifyToken(token, config.jwtSecret);
198
- if (payload) {
199
+ if (payload && isDeviceAccessTokenActive(payload)) {
199
200
  req.auth = {
200
201
  keyId: payload.type === 'api_key' ? payload.sub : undefined,
201
202
  userId: payload.sub,
@@ -0,0 +1,9 @@
1
+ import { Router } from 'express';
2
+ import { type A2APeers } from '../../protocols/a2a/peer-config.js';
3
+ import type { TaskExecutor } from '../../protocols/a2a/index.js';
4
+ export declare function createA2AJsonRpcRoutes(options: {
5
+ publicUrl: string;
6
+ peers?: A2APeers;
7
+ executor?: TaskExecutor;
8
+ timeoutMs?: number;
9
+ }): Router;
@@ -0,0 +1,66 @@
1
+ import express, { Router } from 'express';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { A2AJsonRpcAdapter, rpcError } from '../../protocols/a2a/jsonrpc-v1.js';
4
+ import { authenticateA2APeer, readA2APeers, validateA2AUrl } from '../../protocols/a2a/peer-config.js';
5
+ import { createCodeBuddyTaskExecutor } from '../../protocols/a2a/codebuddy-executor.js';
6
+ import { buildCodeBuddyAgentCard } from './a2a-protocol.js';
7
+ import { initializeToolRegistry } from '../../codebuddy/tools.js';
8
+ import { getPeerMethodHandler } from '../websocket/peer-method-registry.js';
9
+ import { createRouteRateLimiter } from '../middleware/rate-limit.js';
10
+ export function createA2AJsonRpcRoutes(options) {
11
+ const peers = options.peers ?? readA2APeers();
12
+ const url = validateA2AUrl(options.publicUrl).href;
13
+ const router = Router();
14
+ const identities = new WeakMap();
15
+ const allowedTools = ['view_file', 'list_directory', 'search'];
16
+ const executor = options.executor ?? createCodeBuddyTaskExecutor({ allowedTools, executeTool: async (name, args, task) => {
17
+ const invoke = getPeerMethodHandler('peer.tool.invoke');
18
+ if (!invoke)
19
+ return { success: false, error: 'Read-only peer bridge unavailable' };
20
+ // Reuse all three existing gates: operator allowlist, fleetSafe, real workspace containment.
21
+ const result = await invoke({ tool: name, args }, { connectionId: `a2a:${task.metadata?.peerId ?? 'unknown'}`,
22
+ scopes: ['peer:invoke'], traceId: randomUUID(), depth: 0 });
23
+ if (!result || typeof result !== 'object' || typeof result.output !== 'string' || result.error || result.success === false) {
24
+ return { success: false, error: 'Read-only peer bridge returned an invalid or failed result' };
25
+ }
26
+ return { success: true, output: result.output };
27
+ } });
28
+ if (!options.executor)
29
+ initializeToolRegistry();
30
+ const adapter = new A2AJsonRpcAdapter(executor, options.timeoutMs, Object.keys(peers).length);
31
+ const localCard = buildCodeBuddyAgentCard();
32
+ const card = { name: localCard.name, description: 'Code Buddy read-only A2A text subset; filesystem access requires configured peer workspace and permissions.',
33
+ version: localCard.version, supportedInterfaces: [{ url, protocolBinding: 'JSONRPC', protocolVersion: '1.0' }],
34
+ capabilities: { streaming: false, pushNotifications: false }, defaultInputModes: ['text/plain'], defaultOutputModes: ['text/plain'],
35
+ skills: localCard.skills.filter(skill => ['code-search', 'code-read'].includes(skill.id)),
36
+ securitySchemes: { bearer: { type: 'http', scheme: 'bearer' } }, security: [{ bearer: [] }] };
37
+ router.get(['/.well-known/agent-card.json', '/.well-known/agent.json'], (_req, res) => res.json(card));
38
+ router.post('/a2a/v1', (req, res, next) => {
39
+ const peer = authenticateA2APeer(peers, req.headers.authorization);
40
+ if (!peer) {
41
+ res.status(401).json({ error: 'A2A peer authentication required' });
42
+ return;
43
+ }
44
+ identities.set(req, peer);
45
+ next();
46
+ }, createRouteRateLimiter({ windowMs: 60000, maxRequests: 10, keyPrefix: 'a2a-jsonrpc-v1', keyGenerator: req => identities.get(req) ?? 'unauthorized' }), express.json({ limit: '1mb' }), async (req, res, next) => {
47
+ try {
48
+ const version = req.headers['a2a-version'];
49
+ if (Array.isArray(version)) {
50
+ res.json(rpcError(req.body?.id, -32602, 'Invalid A2A-Version'));
51
+ return;
52
+ }
53
+ res.json(await adapter.handle(identities.get(req), req.body, version));
54
+ }
55
+ catch (error) {
56
+ next(error);
57
+ }
58
+ });
59
+ const errors = (error, _req, res, _next) => {
60
+ const tooLarge = error?.type === 'entity.too.large';
61
+ res.status(tooLarge ? 413 : 400).json(rpcError(null, tooLarge ? -32600 : -32700, tooLarge ? 'Body exceeds 1 MiB' : 'Invalid A2A request'));
62
+ };
63
+ router.use(errors);
64
+ return router;
65
+ }
66
+ //# sourceMappingURL=a2a-jsonrpc.js.map
@@ -9,4 +9,16 @@
9
9
  * - POST /api/a2a/tasks/:id/cancel — Cancel a task
10
10
  */
11
11
  import { Router } from 'express';
12
+ import { type AgentCard } from '../../protocols/a2a/index.js';
13
+ /**
14
+ * Code Buddy's local AgentCard. The skills declared here are the ONLY
15
+ * capabilities a remote peer can invoke against this instance via
16
+ * /tasks/send. They must stay in lockstep with the fleet-safe tool list
17
+ * (see `src/tools/metadata.ts:fleetSafe`) — declaring `code-edit` here
18
+ * while the executor only exposes read tools would be a dishonest card.
19
+ *
20
+ * Mutating skills (write/exec) intentionally NOT exposed in V1; they
21
+ * require an upgraded scope + per-peer quota that lives in the V2 backlog.
22
+ */
23
+ export declare function buildCodeBuddyAgentCard(): AgentCard;
12
24
  export declare function createA2AProtocolRoutes(): Router;
@@ -24,7 +24,7 @@ import { initializeToolRegistry } from '../../codebuddy/tools.js';
24
24
  * Mutating skills (write/exec) intentionally NOT exposed in V1; they
25
25
  * require an upgraded scope + per-peer quota that lives in the V2 backlog.
26
26
  */
27
- function buildCodeBuddyAgentCard() {
27
+ export function buildCodeBuddyAgentCard() {
28
28
  return createAgentCard({
29
29
  name: 'Code Buddy',
30
30
  description: 'Multi-provider AI coding agent. A2A inbound surface is read-only: search, view, web fetch, codebase analysis, reasoning. Mutating skills (edit/exec) require an upgraded scope and are not exposed by default.',
@@ -0,0 +1,4 @@
1
+ import { Router } from 'express';
2
+ import { type DeviceAuthStore } from '../auth/device-store.js';
3
+ /** Public proof-of-possession endpoints; code issuance remains local to the CLI. */
4
+ export declare function createDeviceAuthRoutes(secret: string, store?: DeviceAuthStore): Router;
@@ -0,0 +1,40 @@
1
+ import { Router } from 'express';
2
+ import { DeviceAuthError, getDeviceAuthStore } from '../auth/device-store.js';
3
+ import { createRouteRateLimiter } from '../middleware/rate-limit.js';
4
+ /** Public proof-of-possession endpoints; code issuance remains local to the CLI. */
5
+ export function createDeviceAuthRoutes(secret, store) {
6
+ const router = Router();
7
+ router.use((_req, res, next) => {
8
+ res.setHeader('Cache-Control', 'no-store');
9
+ next();
10
+ });
11
+ const getStore = () => store ?? getDeviceAuthStore();
12
+ for (const route of ['register', 'challenge', 'verify']) {
13
+ const limit = createRouteRateLimiter({
14
+ maxRequests: 10, windowMs: 60_000, keyPrefix: `device-auth:${route}`,
15
+ // Do not trust arbitrary X-Forwarded-For on these unauthenticated routes.
16
+ keyGenerator: req => req.socket.remoteAddress ?? 'unknown',
17
+ handler: (_req, res) => { res.status(429).json({ error: 'Too many requests' }); },
18
+ });
19
+ const handle = async (req, res) => {
20
+ const body = req.body && typeof req.body === 'object' && !Array.isArray(req.body)
21
+ ? req.body : {};
22
+ try {
23
+ const service = getStore();
24
+ const result = route === 'register'
25
+ ? await service.register(body.pairingCode, body.deviceName, body.publicKeyJwk)
26
+ : route === 'challenge'
27
+ ? service.challenge(body.deviceId)
28
+ : await service.verify(body.deviceId, body.nonce, body.signature, secret);
29
+ res.json(result);
30
+ }
31
+ catch (error) {
32
+ const failure = error instanceof DeviceAuthError ? error : new DeviceAuthError(503);
33
+ res.status(failure.status).json({ error: failure.message });
34
+ }
35
+ };
36
+ router.post(`/${route}`, limit, handle);
37
+ }
38
+ return router;
39
+ }
40
+ //# sourceMappingURL=device-auth.js.map