@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
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import os from 'os';
4
4
  import { getSettingsManager } from "../utils/settings-manager.js";
5
5
  import { logger } from '../utils/logger.js';
6
- import { readJsonAtomicSync, writeJsonAtomicSync } from '../utils/atomic-write.js';
6
+ import { readJsonAtomicSync, readJsonAtomicSyncReadOnly, writeJsonAtomicSync } from '../utils/atomic-write.js';
7
7
  /**
8
8
  * Resolve ${ENV_VAR} references in env values from process.env
9
9
  */
@@ -14,7 +14,7 @@ function resolveEnvVars(env) {
14
14
  for (const [key, value] of Object.entries(env)) {
15
15
  const match = value.match(/^\$\{(\w+)\}$/);
16
16
  const varName = match?.[1];
17
- resolved[key] = varName !== undefined ? (process.env[varName] || '') : value;
17
+ resolved[key] = varName !== undefined ? (process.env[varName] || value) : value;
18
18
  }
19
19
  return resolved;
20
20
  }
@@ -30,11 +30,27 @@ function resolveServerEnv(server) {
30
30
  }
31
31
  return server;
32
32
  }
33
+ /** `mcpServers` of a project `.codebuddy/settings.json` read directly (explicit `cwd`). */
34
+ function readProjectSettingsServers(projectDir) {
35
+ const settingsPath = path.join(projectDir, '.codebuddy', 'settings.json');
36
+ if (!fs.existsSync(settingsPath))
37
+ return undefined;
38
+ try {
39
+ const settings = readJsonAtomicSync(settingsPath, null, { mode: 0o600 });
40
+ const servers = settings?.mcpServers;
41
+ return servers && typeof servers === 'object' && !Array.isArray(servers) ? servers : undefined;
42
+ }
43
+ catch (error) {
44
+ logger.warn('Failed to load project settings MCP servers', { error });
45
+ return undefined;
46
+ }
47
+ }
33
48
  export function loadMCPConfig(options = {}) {
34
49
  const servers = [];
35
50
  const seenServers = new Set();
36
51
  // 1. First, try project-level .codebuddy/mcp.json (highest priority, committable)
37
- const projectMCPPath = path.join(process.cwd(), '.codebuddy', 'mcp.json');
52
+ const projectDir = options.cwd ?? process.cwd();
53
+ const projectMCPPath = path.join(projectDir, '.codebuddy', 'mcp.json');
38
54
  if (fs.existsSync(projectMCPPath)) {
39
55
  try {
40
56
  const projectMCP = readJsonAtomicSync(projectMCPPath, {});
@@ -56,11 +72,13 @@ export function loadMCPConfig(options = {}) {
56
72
  logger.warn('Failed to load project MCP config', { error });
57
73
  }
58
74
  }
59
- // 2. Then, try project settings (.codebuddy/settings.json)
60
- const manager = getSettingsManager();
61
- const projectSettings = manager.loadProjectSettings();
62
- if (projectSettings.mcpServers) {
63
- for (const [name, config] of Object.entries(projectSettings.mcpServers)) {
75
+ // 2. Then, try project settings (.codebuddy/settings.json). Without an explicit `cwd`
76
+ // the settings manager keeps its historical path; with one, that project's file is read.
77
+ const projectSettingsServers = options.cwd === undefined
78
+ ? getSettingsManager().loadProjectSettings().mcpServers
79
+ : readProjectSettingsServers(projectDir);
80
+ if (projectSettingsServers) {
81
+ for (const [name, config] of Object.entries(projectSettingsServers)) {
64
82
  if (!seenServers.has(name)) {
65
83
  const serverConfig = resolveServerEnv({ ...config, name });
66
84
  if (serverConfig.enabled !== false || options.includeDisabled) {
@@ -92,6 +110,101 @@ export function loadMCPConfig(options = {}) {
92
110
  }
93
111
  return { servers };
94
112
  }
113
+ function isServerMap(value) {
114
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
115
+ }
116
+ function readOnlyServersFromFile(filePath, label, warnings, keys) {
117
+ const outcome = readJsonAtomicSyncReadOnly(filePath, isServerMap);
118
+ if (outcome.status === 'missing')
119
+ return undefined;
120
+ if (outcome.status === 'unreadable') {
121
+ warnings.push(`${label} is unreadable`);
122
+ return undefined;
123
+ }
124
+ if (outcome.status === 'corrupt') {
125
+ warnings.push(`${label} is corrupt or not a JSON object`);
126
+ return undefined;
127
+ }
128
+ const parsed = outcome.value;
129
+ if (keys === 'mcpServers-only') {
130
+ // The runtime never reads a `servers` key here: report it instead of counting it.
131
+ if (parsed.servers !== undefined) {
132
+ warnings.push(`${label} has a "servers" key, which is not read at runtime; use mcpServers`);
133
+ }
134
+ if (parsed.mcpServers === undefined)
135
+ return undefined;
136
+ if (!isServerMap(parsed.mcpServers)) {
137
+ warnings.push(`${label} has an invalid mcpServers value (expected an object)`);
138
+ return undefined;
139
+ }
140
+ return parsed.mcpServers;
141
+ }
142
+ // Same selection as the runtime (`mcpServers || servers`).
143
+ const servers = parsed.mcpServers || parsed.servers;
144
+ if (!servers)
145
+ return undefined;
146
+ if (!isServerMap(servers)) {
147
+ warnings.push(`${label} has an invalid mcpServers/servers value (expected an object)`);
148
+ return undefined;
149
+ }
150
+ return servers;
151
+ }
152
+ /**
153
+ * Strictly read-only MCP configuration loader for diagnostics. Reads the same
154
+ * three sources with the same precedence, disabled masking, and env resolution
155
+ * as `loadMCPConfig`, but never restores/renames/chmods/creates config or
156
+ * backups/temporaries. Corrupt or unreadable sources yield sanitized warnings
157
+ * instead of being silently treated as "no server configured". Missing config
158
+ * is normal and produces no warning.
159
+ */
160
+ export function loadMCPConfigReadOnly(options = {}) {
161
+ const servers = [];
162
+ const seenServers = new Set();
163
+ const warnings = [];
164
+ const projectDir = options.cwd ?? process.cwd();
165
+ const projectMCPPath = path.join(projectDir, '.codebuddy', 'mcp.json');
166
+ const projectMCP = readOnlyServersFromFile(projectMCPPath, 'project .codebuddy/mcp.json', warnings, 'mcpServers-or-servers');
167
+ if (projectMCP) {
168
+ for (const [name, config] of Object.entries(projectMCP)) {
169
+ if (seenServers.has(name))
170
+ continue;
171
+ const serverConfig = resolveServerEnv({ ...config, name });
172
+ if (serverConfig.enabled === false && !options.includeDisabled) {
173
+ seenServers.add(name);
174
+ continue;
175
+ }
176
+ servers.push(serverConfig);
177
+ seenServers.add(name);
178
+ }
179
+ }
180
+ const projectSettingsPath = path.join(projectDir, '.codebuddy', 'settings.json');
181
+ const projectSettings = readOnlyServersFromFile(projectSettingsPath, 'project .codebuddy/settings.json', warnings, 'mcpServers-only');
182
+ if (projectSettings) {
183
+ for (const [name, config] of Object.entries(projectSettings)) {
184
+ if (seenServers.has(name))
185
+ continue;
186
+ const serverConfig = resolveServerEnv({ ...config, name });
187
+ if (serverConfig.enabled !== false || options.includeDisabled) {
188
+ servers.push(serverConfig);
189
+ }
190
+ seenServers.add(name);
191
+ }
192
+ }
193
+ const userMCPPath = path.join(os.homedir(), '.codebuddy', 'mcp.json');
194
+ const userMCP = readOnlyServersFromFile(userMCPPath, 'user ~/.codebuddy/mcp.json', warnings, 'mcpServers-or-servers');
195
+ if (userMCP) {
196
+ for (const [name, config] of Object.entries(userMCP)) {
197
+ if (seenServers.has(name))
198
+ continue;
199
+ const serverConfig = resolveServerEnv({ ...config, name });
200
+ if (serverConfig.enabled !== false || options.includeDisabled) {
201
+ servers.push(serverConfig);
202
+ }
203
+ seenServers.add(name);
204
+ }
205
+ }
206
+ return { servers, warnings };
207
+ }
95
208
  export function saveMCPConfig(config) {
96
209
  const manager = getSettingsManager();
97
210
  const mcpServers = {};
@@ -0,0 +1,12 @@
1
+ import type { MCPServerConfig } from './types.js';
2
+ export type MCPImportSource = 'hermes' | 'openclaw';
3
+ export interface MCPImportResult {
4
+ servers: MCPServerConfig[];
5
+ warnings: string[];
6
+ rejected: string[];
7
+ }
8
+ /** Supported subset: exact names and star globs. Unsupported exclusions must never broaden exposure. */
9
+ export declare function validateMCPToolFilter(filter: MCPServerConfig['toolFilter']): void;
10
+ export declare function mcpToolAllowed(name: string, filter: MCPServerConfig['toolFilter']): boolean;
11
+ /** Pure import: never resolve env, connect, run commands or transfer credentials. */
12
+ export declare function normalizeMCPImports(input: unknown, source: MCPImportSource): MCPImportResult;
@@ -0,0 +1,114 @@
1
+ import { SECRET_PATTERNS } from '../security/secret-patterns.js';
2
+ const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
3
+ const sensitiveKey = /KEY|TOKEN|SECRET|PASSWORD|AUTHORIZATION|COOKIE/i;
4
+ const reference = /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/;
5
+ const hasSecret = (value) => SECRET_PATTERNS.some(p => new RegExp(p.pattern.source, p.pattern.flags.replace('g', '')).test(value));
6
+ /** Supported subset: exact names and star globs. Unsupported exclusions must never broaden exposure. */
7
+ export function validateMCPToolFilter(filter) {
8
+ if (!filter)
9
+ return;
10
+ for (const patterns of [filter.include, filter.exclude]) {
11
+ if (patterns !== undefined && (!Array.isArray(patterns) || patterns.some(p => typeof p !== 'string' || !/^[A-Za-z0-9_.:*-]+$/.test(p)))) {
12
+ throw new Error('Unsupported MCP tool filter; only names and * are supported');
13
+ }
14
+ }
15
+ }
16
+ export function mcpToolAllowed(name, filter) {
17
+ validateMCPToolFilter(filter);
18
+ const matches = (pattern) => new RegExp(`^${pattern.split('*').map(p => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*')}$`).test(name);
19
+ return (!filter?.include || filter.include.some(matches)) && !filter?.exclude?.some(matches);
20
+ }
21
+ /** Pure import: never resolve env, connect, run commands or transfer credentials. */
22
+ export function normalizeMCPImports(input, source) {
23
+ const result = { servers: [], warnings: [], rejected: [] };
24
+ if (!record(input)) {
25
+ result.rejected.push('MCP server map must be an object');
26
+ return result;
27
+ }
28
+ for (const [name, raw] of Object.entries(input)) {
29
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(name) || !record(raw)) {
30
+ result.rejected.push('Invalid MCP server entry');
31
+ continue;
32
+ }
33
+ const warn = (message) => result.warnings.push(`${name}: ${message}`);
34
+ try {
35
+ const clean = (key, value) => {
36
+ if (typeof value !== 'string')
37
+ throw new Error('Expected string configuration value');
38
+ if (reference.test(value) || /^Bearer \$\{[A-Za-z_][A-Za-z0-9_]*\}$/i.test(value))
39
+ return value;
40
+ if (sensitiveKey.test(key) || /^(?:--)?[^=]*(?:token|secret|password|authorization|api[_-]?key)[^=]*=/i.test(value) || hasSecret(value)) {
41
+ const envName = `MCP_IMPORT_${name}_${key}`.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase();
42
+ warn(`secret not copied; configure environment ${envName}`);
43
+ return '${' + envName + '}';
44
+ }
45
+ return value;
46
+ };
47
+ const map = (key) => {
48
+ if (raw[key] === undefined)
49
+ return undefined;
50
+ if (!record(raw[key]))
51
+ throw new Error(`Invalid ${key}`);
52
+ return Object.fromEntries(Object.entries(raw[key]).map(([k, v]) => [k, clean(`${key}_${k}`, v)]));
53
+ };
54
+ const type = raw.transport ?? raw.type;
55
+ let transport;
56
+ if (type === 'stdio' || (type === undefined && typeof raw.command === 'string')) {
57
+ if (typeof raw.command !== 'string' || !raw.command.trim())
58
+ throw new Error('stdio requires command');
59
+ if (raw.args !== undefined && (!Array.isArray(raw.args) || raw.args.some(a => typeof a !== 'string')))
60
+ throw new Error('Invalid args');
61
+ const args = (raw.args ?? []);
62
+ transport = { type: 'stdio', inheritEnv: false, command: clean('command', raw.command), args: args.map((v, i) => clean(sensitiveKey.test(args[i - 1] ?? '') ? `arg_secret_${i}` : `arg_${i}`, v)), env: map('env') };
63
+ if (raw.cwd !== undefined)
64
+ transport.cwd = clean('cwd', raw.cwd);
65
+ }
66
+ else {
67
+ const mapped = type === undefined ? (source === 'hermes' ? 'streamable_http' : 'sse')
68
+ : type === 'http' || type === 'streamable-http' || type === 'streamable_http' ? 'streamable_http'
69
+ : type === 'sse' || type === 'sse_sdk' ? 'sse' : undefined;
70
+ if (!mapped)
71
+ throw new Error('Unsupported transport');
72
+ if (typeof raw.url !== 'string')
73
+ throw new Error('Remote transport requires URL');
74
+ if (!reference.test(raw.url)) {
75
+ const url = new URL(raw.url);
76
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash || url.search)
77
+ throw new Error('Remote URL must not contain credentials, query or fragment; use environment reference');
78
+ }
79
+ transport = { type: mapped, url: raw.url, headers: map('headers') };
80
+ if (type === undefined && source === 'hermes')
81
+ warn('streamable HTTP selected; implicit SSE fallback is not imported');
82
+ }
83
+ const server = { name, transport, enabled: raw.enabled !== false };
84
+ const filter = raw.toolFilter ?? raw.tool_filter ?? raw.tools;
85
+ if (filter !== undefined) {
86
+ if (!record(filter))
87
+ throw new Error('Invalid tool filter');
88
+ const allowedKeys = filter === raw.tools ? ['include', 'exclude', 'resources', 'prompts'] : ['include', 'exclude'];
89
+ if (Object.keys(filter).some(key => !allowedKeys.includes(key)))
90
+ throw new Error('Unsupported tool filter fields');
91
+ if (filter.resources !== undefined || filter.prompts !== undefined)
92
+ warn('resource/prompt utilities are not imported; Buddy imports native tools only');
93
+ if (filter.include !== undefined || filter.exclude !== undefined) {
94
+ server.toolFilter = { ...(filter.include !== undefined ? { include: typeof filter.include === 'string' ? [filter.include] : filter.include } : {}),
95
+ ...(filter.exclude !== undefined ? { exclude: typeof filter.exclude === 'string' ? [filter.exclude] : filter.exclude } : {}) };
96
+ validateMCPToolFilter(server.toolFilter);
97
+ // OpenClaw treats an empty include as unrestricted, unlike Hermes.
98
+ if (source === 'openclaw' && server.toolFilter.include?.length === 0)
99
+ delete server.toolFilter.include;
100
+ }
101
+ }
102
+ if (raw.auth !== undefined || raw.oauth !== undefined) {
103
+ server.enabled = false;
104
+ warn('authentication settings not imported; server disabled until operator configures authentication in Buddy');
105
+ }
106
+ result.servers.push(server);
107
+ }
108
+ catch {
109
+ result.rejected.push(`${name}: unsupported or unsafe configuration; no values copied`);
110
+ }
111
+ }
112
+ return result;
113
+ }
114
+ //# sourceMappingURL=import-normalize.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared MCP OAuth markers.
3
+ *
4
+ * Kept in a leaf module so `mcp-oauth.ts` and `mcp-oauth-provider.ts` can share
5
+ * them without an import cycle.
6
+ */
7
+ /** Marker stored as tokenUrl: refresh is handled by the SDK, never by MCPOAuthManager.getValidToken(). */
8
+ export declare const SDK_MANAGED_TOKEN_URL = "sdk-managed";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared MCP OAuth markers.
3
+ *
4
+ * Kept in a leaf module so `mcp-oauth.ts` and `mcp-oauth-provider.ts` can share
5
+ * them without an import cycle.
6
+ */
7
+ /** Marker stored as tokenUrl: refresh is handled by the SDK, never by MCPOAuthManager.getValidToken(). */
8
+ export const SDK_MANAGED_TOKEN_URL = 'sdk-managed';
9
+ //# sourceMappingURL=mcp-oauth-constants.js.map
@@ -0,0 +1,80 @@
1
+ /**
2
+ * OAuth provider bridge for HTTP MCP transports.
3
+ *
4
+ * Adapts the SDK `OAuthClientProvider` contract (RFC 9728 discovery, PKCE,
5
+ * URL-based client IDs / dynamic registration handled by the SDK itself) onto
6
+ * the existing encrypted token store of `mcp-oauth.ts`. No parallel OAuth
7
+ * implementation: PKCE, discovery and token exchange come from the SDK; the
8
+ * local callback server, browser opening and token persistence come from the
9
+ * existing manager.
10
+ */
11
+ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
12
+ import type { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
13
+ import { SDK_MANAGED_TOKEN_URL } from './mcp-oauth-constants.js';
14
+ import { type CallbackSession } from './mcp-oauth.js';
15
+ export { SDK_MANAGED_TOKEN_URL };
16
+ export interface MCPTransportOAuthConfig {
17
+ type: 'oauth';
18
+ /** Key of the stored token; defaults to the server URL host. */
19
+ serverId?: string;
20
+ /** SEP-991 URL-based client id (HTTPS document). Used when the server advertises support. */
21
+ clientMetadataUrl?: string;
22
+ /** Pre-registered public client id (token_endpoint_auth_method none). */
23
+ clientId?: string;
24
+ scopes?: string[];
25
+ /** Loopback redirect; default http://localhost:19836/callback (same as mcp-oauth.ts). */
26
+ redirectUri?: string;
27
+ /** When false, an authorization requirement fails closed instead of opening a browser. */
28
+ interactive?: boolean;
29
+ }
30
+ export interface OAuthWaitOptions {
31
+ signal?: AbortSignal;
32
+ }
33
+ export interface OAuthInteraction {
34
+ openUrl: (url: string) => Promise<void>;
35
+ waitForCode: (redirectUri: string, state: string, opts?: OAuthWaitOptions) => Promise<string>;
36
+ /** Production default: bind the callback port before openUrl. Tests may omit this. */
37
+ beginWaitForCode?: (redirectUri: string, state: string, opts?: OAuthWaitOptions) => CallbackSession;
38
+ }
39
+ /** Tests inject a fake browser; production keeps the default loopback flow. */
40
+ export declare function setMCPOAuthInteraction(next: OAuthInteraction | null): void;
41
+ interface FinishAuthTransport {
42
+ finishAuth(authorizationCode: string): Promise<void>;
43
+ }
44
+ export declare class MCPStoredOAuthProvider implements OAuthClientProvider {
45
+ private readonly serverId;
46
+ private readonly auth;
47
+ private verifier?;
48
+ private stateValue?;
49
+ private client?;
50
+ private transport?;
51
+ /** Single in-flight authorization: concurrent SDK auth() calls share it. */
52
+ private pending?;
53
+ private flowAbort?;
54
+ private callbackSession?;
55
+ /** Set by abortAuthorization so a late successful callback cannot finishAuth. */
56
+ private authCancelled;
57
+ constructor(serverId: string, auth: MCPTransportOAuthConfig);
58
+ attachTransport(transport: FinishAuthTransport): void;
59
+ get redirectUrl(): string;
60
+ get clientMetadataUrl(): string | undefined;
61
+ get clientMetadata(): OAuthClientMetadata;
62
+ state(): string;
63
+ clientInformation(): OAuthClientInformationMixed | undefined;
64
+ saveClientInformation(info: OAuthClientInformationMixed): void;
65
+ tokens(): OAuthTokens | undefined;
66
+ saveTokens(tokens: OAuthTokens): void;
67
+ abortAuthorization(reason?: Error): void;
68
+ redirectToAuthorization(authorizationUrl: URL): Promise<void>;
69
+ private throwIfAuthCancelled;
70
+ /**
71
+ * Open the system browser. A slow opener is raced against abort so
72
+ * removeServer does not stay stuck in openUrl; a failed opener still
73
+ * prints the URL so the user can complete the loopback callback by hand.
74
+ */
75
+ private openAuthorizationUrl;
76
+ saveCodeVerifier(codeVerifier: string): void;
77
+ codeVerifier(): string;
78
+ invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier'): void;
79
+ }
80
+ export declare function createOAuthProvider(url: string, auth: MCPTransportOAuthConfig): MCPStoredOAuthProvider;
@@ -0,0 +1,241 @@
1
+ /**
2
+ * OAuth provider bridge for HTTP MCP transports.
3
+ *
4
+ * Adapts the SDK `OAuthClientProvider` contract (RFC 9728 discovery, PKCE,
5
+ * URL-based client IDs / dynamic registration handled by the SDK itself) onto
6
+ * the existing encrypted token store of `mcp-oauth.ts`. No parallel OAuth
7
+ * implementation: PKCE, discovery and token exchange come from the SDK; the
8
+ * local callback server, browser opening and token persistence come from the
9
+ * existing manager.
10
+ */
11
+ import * as crypto from 'crypto';
12
+ import { logger } from '../utils/logger.js';
13
+ import { SDK_MANAGED_TOKEN_URL } from './mcp-oauth-constants.js';
14
+ import { getMCPOAuthManager, OAuthCallbackCancelledError, openBrowser, startCallbackServer, startCallbackSession, } from './mcp-oauth.js';
15
+ export { SDK_MANAGED_TOKEN_URL };
16
+ const DEFAULT_REDIRECT = 'http://localhost:19836/callback';
17
+ function waitUntilAborted(signal) {
18
+ if (signal.aborted)
19
+ return Promise.resolve();
20
+ return new Promise((resolve) => {
21
+ signal.addEventListener('abort', () => resolve(), { once: true });
22
+ });
23
+ }
24
+ function storedClientFromMixed(info) {
25
+ const client = { client_id: info.client_id };
26
+ if (info.client_secret)
27
+ client.client_secret = info.client_secret;
28
+ if (info.client_id_issued_at !== undefined)
29
+ client.client_id_issued_at = info.client_id_issued_at;
30
+ if (info.client_secret_expires_at !== undefined)
31
+ client.client_secret_expires_at = info.client_secret_expires_at;
32
+ if ('redirect_uris' in info && info.redirect_uris)
33
+ client.redirect_uris = info.redirect_uris.map(String);
34
+ if ('token_endpoint_auth_method' in info && info.token_endpoint_auth_method) {
35
+ client.token_endpoint_auth_method = info.token_endpoint_auth_method;
36
+ }
37
+ if ('grant_types' in info && info.grant_types)
38
+ client.grant_types = info.grant_types;
39
+ if ('response_types' in info && info.response_types)
40
+ client.response_types = info.response_types;
41
+ if ('client_name' in info && info.client_name)
42
+ client.client_name = info.client_name;
43
+ if ('scope' in info && info.scope)
44
+ client.scope = info.scope;
45
+ return client;
46
+ }
47
+ function defaultInteraction() {
48
+ return {
49
+ openUrl: (url) => openBrowser(url),
50
+ waitForCode: async (redirectUri, state, opts) => (await startCallbackServer(redirectUri, state, 120_000, opts?.signal)).code,
51
+ beginWaitForCode: (redirectUri, state, opts) => startCallbackSession(redirectUri, state, 120_000, opts?.signal),
52
+ };
53
+ }
54
+ let interaction = defaultInteraction();
55
+ /** Tests inject a fake browser; production keeps the default loopback flow. */
56
+ export function setMCPOAuthInteraction(next) {
57
+ interaction = next ?? defaultInteraction();
58
+ }
59
+ export class MCPStoredOAuthProvider {
60
+ serverId;
61
+ auth;
62
+ verifier;
63
+ stateValue;
64
+ client;
65
+ transport;
66
+ /** Single in-flight authorization: concurrent SDK auth() calls share it. */
67
+ pending;
68
+ flowAbort;
69
+ callbackSession;
70
+ /** Set by abortAuthorization so a late successful callback cannot finishAuth. */
71
+ authCancelled = false;
72
+ constructor(serverId, auth) {
73
+ this.serverId = serverId;
74
+ this.auth = auth;
75
+ }
76
+ attachTransport(transport) {
77
+ this.transport = transport;
78
+ }
79
+ get redirectUrl() {
80
+ return this.auth.redirectUri ?? DEFAULT_REDIRECT;
81
+ }
82
+ get clientMetadataUrl() {
83
+ return this.auth.clientMetadataUrl;
84
+ }
85
+ get clientMetadata() {
86
+ return {
87
+ client_name: 'Code Buddy',
88
+ redirect_uris: [this.redirectUrl],
89
+ grant_types: ['authorization_code', 'refresh_token'],
90
+ response_types: ['code'],
91
+ token_endpoint_auth_method: 'none',
92
+ ...(this.auth.scopes?.length ? { scope: this.auth.scopes.join(' ') } : {}),
93
+ };
94
+ }
95
+ state() {
96
+ this.stateValue = crypto.randomBytes(16).toString('hex');
97
+ return this.stateValue;
98
+ }
99
+ clientInformation() {
100
+ if (this.auth.clientId)
101
+ return { client_id: this.auth.clientId };
102
+ if (this.client)
103
+ return this.client;
104
+ const stored = getMCPOAuthManager().getStoredClientInformation(this.serverId);
105
+ return stored ?? undefined;
106
+ }
107
+ saveClientInformation(info) {
108
+ this.client = info;
109
+ getMCPOAuthManager().storeClientInformation(this.serverId, storedClientFromMixed(info));
110
+ }
111
+ tokens() {
112
+ const stored = getMCPOAuthManager().getStoredToken(this.serverId);
113
+ if (!stored)
114
+ return undefined;
115
+ const { token } = stored;
116
+ return {
117
+ access_token: token.accessToken,
118
+ token_type: 'bearer',
119
+ ...(token.refreshToken ? { refresh_token: token.refreshToken } : {}),
120
+ expires_in: Math.max(0, Math.floor((token.expiresAt - Date.now()) / 1000)),
121
+ ...(token.scopes.length ? { scope: token.scopes.join(' ') } : {}),
122
+ };
123
+ }
124
+ saveTokens(tokens) {
125
+ const clientId = this.clientInformation()?.client_id ?? '';
126
+ getMCPOAuthManager().storeToken(this.serverId, {
127
+ accessToken: tokens.access_token,
128
+ refreshToken: tokens.refresh_token,
129
+ expiresAt: Date.now() + (tokens.expires_in ?? 3600) * 1000,
130
+ scopes: tokens.scope ? tokens.scope.split(' ') : (this.auth.scopes ?? []),
131
+ }, { clientId, clientSecret: undefined, authorizationUrl: '', tokenUrl: SDK_MANAGED_TOKEN_URL, scopes: this.auth.scopes ?? [] });
132
+ }
133
+ abortAuthorization(reason) {
134
+ this.authCancelled = true;
135
+ this.flowAbort?.abort();
136
+ this.callbackSession?.abort(reason);
137
+ }
138
+ redirectToAuthorization(authorizationUrl) {
139
+ if (this.auth.interactive === false) {
140
+ return Promise.reject(new Error(`MCP server "${this.serverId}" requires OAuth authorization; run the connection interactively to sign in.`));
141
+ }
142
+ if (!this.transport) {
143
+ return Promise.reject(new Error('OAuth provider is not attached to a transport'));
144
+ }
145
+ if (this.pending)
146
+ return this.pending;
147
+ this.authCancelled = false;
148
+ this.flowAbort = new AbortController();
149
+ const signal = this.flowAbort.signal;
150
+ const url = authorizationUrl.toString();
151
+ this.pending = (async () => {
152
+ const state = this.stateValue ?? this.state();
153
+ const opts = { signal };
154
+ let code;
155
+ if (interaction.beginWaitForCode) {
156
+ const session = interaction.beginWaitForCode(this.redirectUrl, state, opts);
157
+ this.callbackSession = session;
158
+ await session.listening;
159
+ this.throwIfAuthCancelled(signal);
160
+ logger.info(`MCP "${this.serverId}": opening browser for OAuth authorization`);
161
+ await this.openAuthorizationUrl(url, signal);
162
+ this.throwIfAuthCancelled(signal);
163
+ code = (await session.result).code;
164
+ }
165
+ else {
166
+ const codePromise = interaction.waitForCode(this.redirectUrl, state, opts);
167
+ logger.info(`MCP "${this.serverId}": opening browser for OAuth authorization`);
168
+ await this.openAuthorizationUrl(url, signal);
169
+ this.throwIfAuthCancelled(signal);
170
+ code = await codePromise;
171
+ }
172
+ this.throwIfAuthCancelled(signal);
173
+ await this.transport.finishAuth(code);
174
+ })().finally(() => {
175
+ this.pending = undefined;
176
+ this.callbackSession = undefined;
177
+ this.flowAbort = undefined;
178
+ });
179
+ return this.pending;
180
+ }
181
+ throwIfAuthCancelled(signal) {
182
+ if (this.authCancelled || signal.aborted) {
183
+ throw new OAuthCallbackCancelledError();
184
+ }
185
+ }
186
+ /**
187
+ * Open the system browser. A slow opener is raced against abort so
188
+ * removeServer does not stay stuck in openUrl; a failed opener still
189
+ * prints the URL so the user can complete the loopback callback by hand.
190
+ */
191
+ async openAuthorizationUrl(url, signal) {
192
+ this.throwIfAuthCancelled(signal);
193
+ const aborted = waitUntilAborted(signal);
194
+ aborted.catch(() => undefined);
195
+ const opener = interaction.openUrl(url).then(() => 'opened', () => 'failed');
196
+ const outcome = await Promise.race([
197
+ opener.then((kind) => ({ kind })),
198
+ aborted.then(() => ({ kind: 'aborted' })),
199
+ ]);
200
+ if (outcome.kind === 'aborted' || this.authCancelled) {
201
+ throw new OAuthCallbackCancelledError();
202
+ }
203
+ if (outcome.kind === 'failed') {
204
+ logger.warn(`MCP "${this.serverId}": browser could not be opened; open this URL manually:\n${url}`);
205
+ }
206
+ }
207
+ saveCodeVerifier(codeVerifier) {
208
+ // A concurrent auth() while the browser flow is pending must not replace the
209
+ // verifier the pending flow will exchange its code with.
210
+ if (this.pending)
211
+ return;
212
+ this.verifier = codeVerifier;
213
+ }
214
+ codeVerifier() {
215
+ if (!this.verifier)
216
+ throw new Error('No PKCE code verifier saved');
217
+ return this.verifier;
218
+ }
219
+ invalidateCredentials(scope) {
220
+ if (scope === 'tokens') {
221
+ getMCPOAuthManager().clearTokens(this.serverId);
222
+ return;
223
+ }
224
+ if (scope === 'all' || scope === 'client') {
225
+ getMCPOAuthManager().removeToken(this.serverId);
226
+ this.client = undefined;
227
+ }
228
+ if (scope === 'all' || scope === 'verifier')
229
+ this.verifier = undefined;
230
+ }
231
+ }
232
+ export function createOAuthProvider(url, auth) {
233
+ const serverId = auth.serverId ?? new URL(url).host;
234
+ // Fail closed before the SDK performs discovery or client registration: without a
235
+ // stored token, a non-interactive connection can never complete.
236
+ if (auth.interactive === false && !getMCPOAuthManager().getStoredToken(serverId)) {
237
+ throw new Error(`MCP server "${serverId}" requires OAuth authorization; run the connection interactively to sign in.`);
238
+ }
239
+ return new MCPStoredOAuthProvider(serverId, auth);
240
+ }
241
+ //# sourceMappingURL=mcp-oauth-provider.js.map