@mycadet/assistant 0.12.7 → 0.12.9

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 (904) hide show
  1. package/README.md +1 -1
  2. package/docs/activation-funnel-telemetry.md +6 -6
  3. package/docs/cadet-doctor.md +7 -7
  4. package/docs/desktop-browser-cli.md +7 -7
  5. package/docs/skills.md +2 -2
  6. package/docs/voice-input-diagnostics.md +1 -1
  7. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +2 -2
  8. package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  9. package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  10. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +2 -2
  11. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  12. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  13. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  14. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  15. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  16. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  17. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  18. package/node_modules/@mycadet/ces-client/package.json +1 -1
  19. package/node_modules/@mycadet/ces-client/src/__tests__/ces-client.test.ts +14 -14
  20. package/node_modules/@mycadet/ces-client/src/rpc-client.ts +1 -1
  21. package/node_modules/@mycadet/credential-storage/src/oauth-runtime.ts +1 -1
  22. package/node_modules/@mycadet/credential-storage/src/static-credentials.ts +1 -1
  23. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  24. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  25. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  26. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +2 -2
  27. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  28. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  29. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  30. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  31. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  32. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  33. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  34. package/node_modules/@mycadet/gateway-client/src/binding-strength-contract.ts +5 -5
  35. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +1 -1
  36. package/node_modules/@mycadet/gateway-client/src/inbound-contract.ts +2 -2
  37. package/node_modules/@mycadet/gateway-client/src/outbound-contract.ts +1 -1
  38. package/node_modules/@mycadet/service-contracts/package.json +2 -2
  39. package/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  40. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  41. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  42. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  43. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  44. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  45. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  46. package/openapi.yaml +6804 -799
  47. package/package.json +2 -2
  48. package/scripts/build-test-fixtures.ts +1 -1
  49. package/scripts/probe-skill-retrieval.ts +1 -1
  50. package/scripts/sync-llm-catalog.ts +2 -2
  51. package/scripts/voice-ttft-spike.ts +2 -2
  52. package/src/__tests__/adaptive-thinking-repair.test.ts +4 -4
  53. package/src/__tests__/agent-loop-override-profile.test.ts +4 -4
  54. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +1 -1
  55. package/src/__tests__/anthropic-provider.test.ts +2 -2
  56. package/src/__tests__/available-skills.test.ts +11 -12
  57. package/src/__tests__/avatar-identity-sync.test.ts +1 -1
  58. package/src/__tests__/byok-default-profile-ensure.test.ts +7 -7
  59. package/src/__tests__/call-domain.test.ts +1 -1
  60. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +2 -2
  61. package/src/__tests__/catalog-files.test.ts +10 -10
  62. package/src/__tests__/catalog-install-normalize.test.ts +3 -3
  63. package/src/__tests__/channel-availability-routes.test.ts +8 -8
  64. package/src/__tests__/channel-readiness-routes.test.ts +4 -4
  65. package/src/__tests__/channel-readiness-service.test.ts +14 -14
  66. package/src/__tests__/channel-readiness-telegram-remote.test.ts +1 -1
  67. package/src/__tests__/chat-reveal-guard-priming.test.ts +5 -6
  68. package/src/__tests__/checker.test.ts +6 -6
  69. package/src/__tests__/clawhub.test.ts +1 -1
  70. package/src/__tests__/{platform-callback-registration.test.ts → cloud-callback-registration.test.ts} +66 -90
  71. package/src/__tests__/{platform-client-verify-credential.test.ts → cloud-client-verify-credential.test.ts} +14 -14
  72. package/src/__tests__/{managed-credential-catalog-cli.test.ts → cloud-credential-catalog-cli.test.ts} +41 -41
  73. package/src/__tests__/{managed-fallback-dispatch.test.ts → cloud-fallback-dispatch.test.ts} +21 -16
  74. package/src/__tests__/{managed-profile-guard.test.ts → cloud-profile-guard.test.ts} +77 -75
  75. package/src/__tests__/{platform-proxy-context.test.ts → cloud-proxy-context.test.ts} +44 -44
  76. package/src/__tests__/{managed-speech-defaults.test.ts → cloud-speech-defaults.test.ts} +41 -41
  77. package/src/__tests__/{managed-store.test.ts → cloud-store.test.ts} +99 -99
  78. package/src/__tests__/compaction-events.test.ts +7 -5
  79. package/src/__tests__/config-loader-backfill.test.ts +65 -65
  80. package/src/__tests__/config-loader-cloud-defaults.test.ts +373 -0
  81. package/src/__tests__/config-loader-corrupt.test.ts +1 -1
  82. package/src/__tests__/config-loader-validation-reset-notice.test.ts +1 -1
  83. package/src/__tests__/config-schema.test.ts +3 -3
  84. package/src/__tests__/{config-set-platform-guard.test.ts → config-set-cloud-guard.test.ts} +6 -6
  85. package/src/__tests__/config-set-route.test.ts +12 -8
  86. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -8
  87. package/src/__tests__/config-watcher.test.ts +1 -1
  88. package/src/__tests__/contacts-write.test.ts +1 -1
  89. package/src/__tests__/conversation-agent-loop.test.ts +2 -2
  90. package/src/__tests__/conversation-app-control-instantiation.test.ts +18 -18
  91. package/src/__tests__/conversation-error.test.ts +45 -45
  92. package/src/__tests__/conversation-inference-profile-route.test.ts +4 -4
  93. package/src/__tests__/conversation-rendered-delegation-state.test.ts +4 -4
  94. package/src/__tests__/conversation-skill-tools.test.ts +7 -8
  95. package/src/__tests__/conversation-slash.test.ts +2 -2
  96. package/src/__tests__/conversation-speed-override.test.ts +8 -10
  97. package/src/__tests__/conversation-summarize-up-to.test.ts +4 -4
  98. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -1
  99. package/src/__tests__/conversation-wait-for-idle.test.ts +7 -5
  100. package/src/__tests__/credential-execution-api-key-propagation.test.ts +5 -5
  101. package/src/__tests__/{credential-execution-managed-contract.test.ts → credential-execution-cloud-contract.test.ts} +24 -24
  102. package/src/__tests__/credential-health-service.test.ts +28 -40
  103. package/src/__tests__/credential-routes.test.ts +85 -165
  104. package/src/__tests__/credential-security-invariants.test.ts +6 -7
  105. package/src/__tests__/custom-profile-ensure.test.ts +1 -1
  106. package/src/__tests__/date-context.test.ts +1 -1
  107. package/src/__tests__/default-profile-catalog-fallback.test.ts +15 -15
  108. package/src/__tests__/delete-managed-skill-tool.test.ts +8 -8
  109. package/src/__tests__/discord-callback-round-trip.test.ts +1 -1
  110. package/src/__tests__/disk-usage.test.ts +3 -3
  111. package/src/__tests__/email-invite-adapter.test.ts +5 -5
  112. package/src/__tests__/{embedding-managed-proxy-selection.test.ts → embedding-cloud-proxy-selection.test.ts} +44 -43
  113. package/src/__tests__/emit-signal-routing-intent.test.ts +13 -15
  114. package/src/__tests__/events-dev-bypass-actor.test.ts +2 -2
  115. package/src/__tests__/fallback-breaker.test.ts +7 -7
  116. package/src/__tests__/feature-flag-test-helpers.ts +106 -0
  117. package/src/__tests__/gateway-flag-listener.test.ts +1 -1
  118. package/src/__tests__/gemini-image-service.test.ts +2 -2
  119. package/src/__tests__/gemini-provider.test.ts +5 -5
  120. package/src/__tests__/get-skill-detail-audit.test.ts +134 -288
  121. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  122. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +5 -5
  123. package/src/__tests__/host-cu-routes-targeted.test.ts +8 -8
  124. package/src/__tests__/image-credentials.test.ts +13 -13
  125. package/src/__tests__/inbound-admitted-introduction-nudge.test.ts +14 -6
  126. package/src/__tests__/inbound-trust-verdict.test.ts +14 -6
  127. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +11 -11
  128. package/src/__tests__/inference-profile-session-handler.test.ts +7 -7
  129. package/src/__tests__/inference-profile-session-ipc.test.ts +5 -5
  130. package/src/__tests__/inference-profile-validation-jev.test.ts +1 -1
  131. package/src/__tests__/init-feature-flag-overrides.test.ts +40 -24
  132. package/src/__tests__/input-repairs.test.ts +1 -1
  133. package/src/__tests__/install-skill-routing.test.ts +10 -8
  134. package/src/__tests__/integration-status.test.ts +5 -5
  135. package/src/__tests__/internal-telemetry-routes.test.ts +3 -3
  136. package/src/__tests__/invite-routes-http.test.ts +1 -1
  137. package/src/__tests__/llm-catalog-parity.test.ts +5 -7
  138. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -2
  139. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -3
  140. package/src/__tests__/llm-resolver.test.ts +1 -1
  141. package/src/__tests__/llm-schema-fallback-profile.test.ts +25 -19
  142. package/src/__tests__/llm-usage-store.test.ts +1 -1
  143. package/src/__tests__/managed-skill-lifecycle.test.ts +12 -12
  144. package/src/__tests__/mcp-abort-signal.test.ts +1 -2
  145. package/src/__tests__/mcp-client-auth.test.ts +1 -2
  146. package/src/__tests__/mcp-config-secret-boundary.test.ts +44 -40
  147. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -2
  148. package/src/__tests__/media-generate-image.test.ts +30 -30
  149. package/src/__tests__/memory-config-file-parity.test.ts +6 -6
  150. package/src/__tests__/migration-export-to-gcs.test.ts +4 -4
  151. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  152. package/src/__tests__/mtime-cache.test.ts +2 -2
  153. package/src/__tests__/{notification-platform-adapter.test.ts → notification-cloud-adapter.test.ts} +39 -39
  154. package/src/__tests__/notification-deep-link.test.ts +40 -40
  155. package/src/__tests__/oauth-apps-routes.test.ts +3 -3
  156. package/src/__tests__/oauth-cli.test.ts +9 -9
  157. package/src/__tests__/oauth-commands-routes.test.ts +16 -16
  158. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -2
  159. package/src/__tests__/oauth-provider-serializer.test.ts +11 -11
  160. package/src/__tests__/oauth-providers-routes.test.ts +12 -12
  161. package/src/__tests__/oauth-store.test.ts +1 -1
  162. package/src/__tests__/oauth2-gateway-transport.test.ts +3 -3
  163. package/src/__tests__/onboarding-persona-write.test.ts +2 -2
  164. package/src/__tests__/openai-image-service.test.ts +2 -2
  165. package/src/__tests__/openai-responses-cutover-guard.test.ts +2 -2
  166. package/src/__tests__/openai-responses-provider.test.ts +1 -1
  167. package/src/__tests__/path-policy.test.ts +4 -4
  168. package/src/__tests__/personal-memory-auth-bypass.test.ts +1 -1
  169. package/src/__tests__/platform-bash-auto-approve.test.ts +18 -18
  170. package/src/__tests__/plugin-api-model-profiles.test.ts +1 -1
  171. package/src/__tests__/plugin-api-provider.test.ts +1 -1
  172. package/src/__tests__/plugin-api-store-credential.test.ts +4 -1
  173. package/src/__tests__/plugin-api-webhook-url.test.ts +4 -5
  174. package/src/__tests__/plugin-import-boundary-guard.test.ts +8 -39
  175. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -2
  176. package/src/__tests__/{provider-platform-proxy-integration.test.ts → provider-cloud-proxy-integration.test.ts} +70 -72
  177. package/src/__tests__/provider-commit-message-generator.test.ts +1 -1
  178. package/src/__tests__/provider-connections-backfill.test.ts +9 -9
  179. package/src/__tests__/provider-error-scenarios.test.ts +8 -8
  180. package/src/__tests__/provider-registry-ollama.test.ts +1 -1
  181. package/src/__tests__/provider-send-message-override-profile.test.ts +5 -5
  182. package/src/__tests__/registry.test.ts +3 -3
  183. package/src/__tests__/requests-mock.ts +972 -0
  184. package/src/__tests__/resolve-trust-class.test.ts +1 -1
  185. package/src/__tests__/resource-pressure-guard.test.ts +6 -6
  186. package/src/__tests__/resource-pressure-routes.test.ts +5 -5
  187. package/src/__tests__/retry-fallback-escalation.test.ts +5 -5
  188. package/src/__tests__/sanitize-config-for-transfer.test.ts +1 -1
  189. package/src/__tests__/scaffold-managed-skill-tool.test.ts +97 -97
  190. package/src/__tests__/schedule-store.test.ts +1 -1
  191. package/src/__tests__/search-skills-unified.test.ts +9 -7
  192. package/src/__tests__/{secret-routes-platform-proxy.test.ts → secret-routes-cloud-proxy.test.ts} +48 -95
  193. package/src/__tests__/secret-routes-scrub.test.ts +2 -59
  194. package/src/__tests__/secure-keys.test.ts +1 -1
  195. package/src/__tests__/skill-load-plugin-scope.test.ts +2 -2
  196. package/src/__tests__/skill-load-tool.test.ts +1 -1
  197. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -19
  198. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  199. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  200. package/src/__tests__/skills-file-content-endpoint.test.ts +222 -536
  201. package/src/__tests__/skills-files-catalog-fallback.test.ts +259 -577
  202. package/src/__tests__/skills.test.ts +1 -1
  203. package/src/__tests__/slim-skill-category.test.ts +11 -9
  204. package/src/__tests__/stt-stream-session.test.ts +3 -3
  205. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  206. package/src/__tests__/subagent-disposal.test.ts +5 -5
  207. package/src/__tests__/subagent-fork-notifications.test.ts +4 -4
  208. package/src/__tests__/subagent-fork-spawn.test.ts +5 -5
  209. package/src/__tests__/subagent-manager-notify.test.ts +5 -5
  210. package/src/__tests__/subagent-tool-gate-mode.test.ts +2 -2
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/telegram-bot-username-resolution.test.ts +34 -52
  213. package/src/__tests__/telegram-config.test.ts +16 -16
  214. package/src/__tests__/telephony-tts-guard.test.ts +13 -13
  215. package/src/__tests__/test-preload.ts +17 -7
  216. package/src/__tests__/tool-audit.test.ts +1 -1
  217. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +7 -5
  218. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  219. package/src/__tests__/turn-events-store.test.ts +1 -1
  220. package/src/__tests__/twilio-routes.test.ts +2 -1
  221. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  222. package/src/__tests__/unified-turn-context-client-os.test.ts +1 -1
  223. package/src/__tests__/validate-input.test.ts +5 -5
  224. package/src/__tests__/verification-outbound-delivery.test.ts +1 -1
  225. package/src/__tests__/voice-config-update.test.ts +15 -15
  226. package/src/__tests__/voice-ingress-preflight.test.ts +4 -4
  227. package/src/__tests__/web-search.test.ts +340 -859
  228. package/src/__tests__/workspace-git-service.test.ts +1 -1
  229. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +12 -12
  230. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +1 -1
  232. package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +1 -1
  234. package/src/__tests__/workspace-migration-110-flip-balanced-profile-to-together.test.ts +1 -1
  235. package/src/__tests__/workspace-migration-113-swap-balanced-profile-to-glm-5p2.test.ts +1 -1
  236. package/src/__tests__/workspace-migration-114-swap-quality-profile-to-opus.test.ts +1 -1
  237. package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +1 -1
  238. package/src/__tests__/workspace-migration-125-repoint-managed-connections-to-cadet.test.ts +3 -3
  239. package/src/__tests__/workspace-migration-126-strip-managed-profile-bodies.test.ts +9 -9
  240. package/src/__tests__/workspace-migration-127-backfill-default-provider.test.ts +11 -11
  241. package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +3 -3
  242. package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
  243. package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
  244. package/src/__tests__/workspace-migration-134-image-generation-mode-to-provider.test.ts +6 -6
  245. package/src/__tests__/workspace-migration-136-repair-stale-fireworks-kimi-model-id.test.ts +1 -1
  246. package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +1 -1
  247. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +1 -1
  248. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +1 -1
  249. package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +1 -1
  250. package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +1 -1
  251. package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +1 -1
  252. package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +1 -1
  253. package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +1 -1
  254. package/src/api/events/cloud-disconnected.ts +21 -0
  255. package/src/api/events/host-bash.ts +1 -1
  256. package/src/api/events/host-cu.ts +1 -1
  257. package/src/api/events/host-file.ts +1 -1
  258. package/src/api/events/notification-intent.ts +1 -1
  259. package/src/api/events/show-cloud-login.ts +18 -0
  260. package/src/api/index.ts +11 -11
  261. package/src/api/responses/home.ts +1 -1
  262. package/src/api/surfaces.ts +4 -4
  263. package/src/approvals/guardian-request-resolvers.ts +2 -2
  264. package/src/avatar/accent-backfill.ts +1 -1
  265. package/src/avatar/identity-avatar.ts +1 -1
  266. package/src/avatar/notification-avatar.test.ts +2 -2
  267. package/src/avatar/notification-avatar.ts +2 -2
  268. package/src/avatar/resvg-lazy.test.ts +1 -1
  269. package/src/avatar/resvg-lazy.ts +3 -3
  270. package/src/avatar/sync-avatar.test.ts +76 -76
  271. package/src/avatar/sync-avatar.ts +15 -15
  272. package/src/avatar/traits-png-sync.ts +1 -1
  273. package/src/background-wake/background-wake-routes.test.ts +39 -39
  274. package/src/background-wake/{platform-client.test.ts → cloud-client.test.ts} +11 -11
  275. package/src/background-wake/{platform-client.ts → cloud-client.ts} +14 -14
  276. package/src/background-wake/publisher.ts +4 -4
  277. package/src/background-wake/wake-intent-hooks.test.ts +3 -3
  278. package/src/calls/__tests__/call-controller.test.ts +2 -2
  279. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -1
  280. package/src/calls/call-domain.ts +1 -1
  281. package/src/calls/resolve-call-tts-provider.ts +6 -6
  282. package/src/calls/telephony-credential-preflight.ts +3 -3
  283. package/src/calls/telephony-tts-capability.ts +5 -6
  284. package/src/calls/tts-call-strategy.ts +1 -1
  285. package/src/calls/voice-escalation-judge.ts +1 -1
  286. package/src/calls/voice-ingress-preflight.ts +4 -4
  287. package/src/cli/bundled-modules.ts +1 -1
  288. package/src/cli/commands/__tests__/apps.test.ts +18 -53
  289. package/src/cli/commands/__tests__/plugins.test.ts +13 -13
  290. package/src/cli/commands/__tests__/tts.test.ts +1 -1
  291. package/src/cli/commands/auth.help.ts +3 -3
  292. package/src/cli/commands/auth.ts +2 -4
  293. package/src/cli/commands/browser.help.ts +2 -2
  294. package/src/cli/commands/channels/index.help.ts +2 -2
  295. package/src/cli/commands/channels/index.ts +1 -1
  296. package/src/cli/commands/channels/send.ts +1 -1
  297. package/src/cli/commands/cloud/__tests__/callback-routes-list.test.ts +2 -2
  298. package/src/cli/commands/cloud/__tests__/connect.test.ts +1 -1
  299. package/src/cli/commands/cloud/__tests__/disconnect.test.ts +3 -4
  300. package/src/cli/commands/cloud/connect.ts +2 -2
  301. package/src/cli/commands/cloud/credits-format.ts +1 -1
  302. package/src/cli/commands/cloud/index.help.ts +35 -35
  303. package/src/cli/commands/cloud/index.ts +1 -1
  304. package/src/cli/commands/config.ts +2 -2
  305. package/src/cli/commands/credentials.ts +10 -12
  306. package/src/cli/commands/domain.ts +1 -1
  307. package/src/cli/commands/email.help.ts +1 -1
  308. package/src/cli/commands/email.ts +2 -2
  309. package/src/cli/commands/gateway.help.ts +1 -1
  310. package/src/cli/commands/gateway.ts +1 -1
  311. package/src/cli/commands/image-generation.help.ts +1 -1
  312. package/src/cli/commands/inference-providers.ts +1 -1
  313. package/src/cli/commands/inference.help.ts +2 -2
  314. package/src/cli/commands/memory/index.help.ts +1 -1
  315. package/src/cli/commands/oauth/connect.test.ts +2 -2
  316. package/src/cli/commands/oauth/connect.ts +1 -1
  317. package/src/cli/commands/oauth/index.help.ts +5 -5
  318. package/src/cli/commands/oauth/providers.ts +2 -2
  319. package/src/cli/commands/oauth/shared.ts +3 -3
  320. package/src/cli/commands/plugins.help.ts +2 -3
  321. package/src/cli/commands/plugins.ts +5 -5
  322. package/src/cli/commands/roadmap.help.ts +1 -1
  323. package/src/cli/commands/telemetry.help.ts +2 -2
  324. package/src/cli/commands/telemetry.ts +2 -2
  325. package/src/cli/commands/tts.help.ts +3 -3
  326. package/src/cli/commands/webhooks.help.ts +9 -9
  327. package/src/cli/commands/webhooks.ts +1 -1
  328. package/src/cli/lib/__tests__/inspect-plugin.test.ts +8 -8
  329. package/src/cli/lib/__tests__/{install-from-platform.test.ts → install-from-cloud.test.ts} +54 -54
  330. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +18 -18
  331. package/src/cli/lib/__tests__/{plugin-catalog-platform.test.ts → plugin-catalog-cloud.test.ts} +2 -2
  332. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +1 -1
  333. package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +9 -9
  334. package/src/cli/lib/__tests__/plugin-details.test.ts +10 -10
  335. package/src/cli/lib/__tests__/plugins-install-offline.test.ts +24 -24
  336. package/src/cli/lib/bundled-marketplace.json +1 -1
  337. package/src/cli/lib/inspect-plugin.ts +2 -2
  338. package/src/cli/lib/{install-from-platform.ts → install-from-cloud.ts} +29 -29
  339. package/src/cli/lib/install-from-github.ts +4 -4
  340. package/src/cli/lib/plugin-catalog-cache.ts +7 -7
  341. package/src/cli/lib/{plugin-catalog-platform.ts → plugin-catalog-cloud.ts} +6 -6
  342. package/src/cli/lib/plugin-catalog-local.ts +2 -2
  343. package/src/cli/lib/publish-plugin.ts +9 -10
  344. package/src/cli/lib/search-plugins.ts +2 -2
  345. package/src/{platform → cloud}/client.test.ts +85 -86
  346. package/src/{platform → cloud}/client.ts +38 -46
  347. package/src/{platform/platform-patch-queue.test.ts → cloud/cloud-patch-queue.test.ts} +6 -9
  348. package/src/{platform/platform-patch-queue.ts → cloud/cloud-patch-queue.ts} +11 -11
  349. package/src/{platform/managed-speech.test.ts → cloud/cloud-speech.test.ts} +19 -19
  350. package/src/{platform/managed-speech.ts → cloud/cloud-speech.ts} +41 -41
  351. package/src/{platform → cloud}/consent-cache.test.ts +5 -5
  352. package/src/{platform → cloud}/consent-cache.ts +8 -8
  353. package/src/cloud/feature-gate.ts +8 -0
  354. package/src/{platform → cloud}/sync-identity.test.ts +4 -4
  355. package/src/{platform → cloud}/sync-identity.ts +6 -6
  356. package/src/config/__tests__/{platform-identity.test.ts → cloud-identity.test.ts} +54 -56
  357. package/src/config/__tests__/{platform-rehydration.test.ts → cloud-rehydration.test.ts} +47 -80
  358. package/src/config/__tests__/default-profile-catalog.test.ts +12 -12
  359. package/src/config/__tests__/default-provider.test.ts +4 -4
  360. package/src/config/__tests__/deployment-context-defaults.test.ts +23 -23
  361. package/src/config/__tests__/{jev-managed-profile.test.ts → jev-cloud-profile.test.ts} +3 -5
  362. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +2 -2
  363. package/src/config/__tests__/loader-last-known-good.test.ts +6 -6
  364. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +2 -2
  365. package/src/config/__tests__/profile-materialization.test.ts +7 -7
  366. package/src/config/__tests__/seed-default-provider.test.ts +17 -17
  367. package/src/config/__tests__/substrate-twin-shadowing.test.ts +36 -26
  368. package/src/config/__tests__/sync-gated-profiles.test.ts +20 -20
  369. package/src/config/__tests__/webhook-routing.test.ts +51 -51
  370. package/src/config/bundled-skills/computer-use/TOOLS.json +12 -12
  371. package/src/config/bundled-skills/messaging/SKILL.md +8 -8
  372. package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
  373. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  374. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  375. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -2
  376. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  377. package/src/config/bundled-skills/settings/tools/shared.ts +1 -1
  378. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +12 -12
  379. package/src/config/bundled-skills/skill-management/SKILL.md +3 -3
  380. package/src/config/bundled-skills/skill-management/TOOLS.json +8 -8
  381. package/src/config/bundled-skills/skill-management/tools/{delete-managed.ts → delete-cloud.ts} +2 -2
  382. package/src/config/bundled-skills/skill-management/tools/{scaffold-managed.ts → scaffold-cloud.ts} +2 -2
  383. package/src/config/call-site-defaults.ts +6 -6
  384. package/src/config/{platform-identity.ts → cloud-identity.ts} +39 -42
  385. package/src/config/cloud-rehydration.ts +35 -0
  386. package/src/config/{managed-speech-defaults.ts → cloud-speech-defaults.ts} +13 -13
  387. package/src/config/default-profile-catalog.ts +36 -38
  388. package/src/config/default-profile-names.ts +11 -11
  389. package/src/config/default-provider-resolution.ts +2 -2
  390. package/src/config/default-provider.ts +1 -1
  391. package/src/config/env-registry.ts +21 -26
  392. package/src/config/env.ts +33 -46
  393. package/src/config/feature-flag-registry.json +5 -5
  394. package/src/config/inference-profile-validation.ts +1 -1
  395. package/src/config/llm-resolver.ts +9 -10
  396. package/src/config/loader.ts +33 -17
  397. package/src/config/profile-materialization.ts +8 -8
  398. package/src/config/profile-text-generation.test.ts +1 -1
  399. package/src/config/sanitize-for-transfer.ts +1 -1
  400. package/src/config/schema.ts +6 -6
  401. package/src/config/schemas/__tests__/stt.test.ts +2 -2
  402. package/src/config/schemas/call-site-catalog.ts +1 -1
  403. package/src/config/schemas/channels.ts +8 -0
  404. package/src/config/schemas/{platform.ts → cloud.ts} +7 -6
  405. package/src/config/schemas/host-browser.ts +1 -0
  406. package/src/config/schemas/ingress.ts +29 -10
  407. package/src/config/schemas/llm.ts +36 -36
  408. package/src/config/schemas/memory-lifecycle.ts +11 -1
  409. package/src/config/schemas/services.ts +4 -4
  410. package/src/config/schemas/stt.ts +3 -3
  411. package/src/config/schemas/tts.ts +10 -5
  412. package/src/config/schemas/voice.ts +1 -1
  413. package/src/config/seed-inference-profiles.ts +19 -19
  414. package/src/config/skill-state.ts +2 -2
  415. package/src/config/skills.ts +4 -4
  416. package/src/config/substrate-twin-shadowing.ts +12 -17
  417. package/src/config/sync-gated-profiles.ts +8 -9
  418. package/src/config/webhook-routing.ts +19 -19
  419. package/src/contacts/contacts-write.ts +1 -1
  420. package/src/credential-execution/ces-runtime.ts +6 -6
  421. package/src/credential-execution/{managed-catalog.ts → cloud-catalog.ts} +24 -24
  422. package/src/credential-execution/prompted-credential.ts +2 -23
  423. package/src/credential-health/credential-health-service.ts +59 -70
  424. package/src/daemon/__tests__/can-spawn-subagents.test.ts +1 -1
  425. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +4 -4
  426. package/src/daemon/config-watcher.ts +2 -2
  427. package/src/daemon/conversation-agent-loop.ts +9 -9
  428. package/src/daemon/conversation-error.ts +36 -36
  429. package/src/daemon/conversation-skill-tools.ts +7 -7
  430. package/src/daemon/conversation-surfaces.ts +1 -1
  431. package/src/daemon/conversation-tool-setup.ts +2 -2
  432. package/src/daemon/conversation-turn-finalize.ts +1 -1
  433. package/src/daemon/conversation-workspace.ts +1 -1
  434. package/src/daemon/conversation.ts +1 -1
  435. package/src/daemon/credential-transcript-scrub.ts +32 -35
  436. package/src/daemon/daemon-readiness.ts +1 -1
  437. package/src/daemon/event-loop-watchdog.ts +2 -2
  438. package/src/daemon/handlers/config-ingress.ts +9 -9
  439. package/src/daemon/handlers/config-telegram.ts +6 -6
  440. package/src/daemon/handlers/skills.ts +20 -20
  441. package/src/daemon/host-cu-proxy.ts +1 -1
  442. package/src/daemon/lifecycle.ts +19 -19
  443. package/src/daemon/parse-actual-tokens-from-error.ts +3 -3
  444. package/src/daemon/providers-setup.ts +6 -6
  445. package/src/daemon/resource-pressure-guard-lifecycle.ts +1 -1
  446. package/src/daemon/resource-pressure-guard.ts +6 -6
  447. package/src/daemon/shutdown-handlers.ts +1 -1
  448. package/src/daemon/sqlite-corruption-watchdog.ts +3 -3
  449. package/src/daemon/trust-context.ts +1 -1
  450. package/src/desktop/desktop-automation-lease.ts +1 -1
  451. package/src/desktop/desktop-browser-endpoint.ts +2 -4
  452. package/src/desktop/desktop-computer-use-routing.test.ts +6 -6
  453. package/src/desktop/desktop-stream-bridge.test.ts +4 -4
  454. package/src/desktop/desktop-stream-bridge.ts +1 -1
  455. package/src/desktop/{virtual-desktop-platform.test.ts → virtual-desktop-cloud.test.ts} +6 -6
  456. package/src/desktop/virtual-desktop-feature.ts +7 -7
  457. package/src/email/byo-email-credential.ts +1 -1
  458. package/src/email/registered-inbox.test.ts +16 -16
  459. package/src/email/registered-inbox.ts +20 -20
  460. package/src/heartbeat/heartbeat-service.ts +1 -1
  461. package/src/hooks/hook-loader.ts +2 -2
  462. package/src/inbound/__tests__/oauth-callback-url.test.ts +2 -2
  463. package/src/inbound/__tests__/tunnel-probe.test.ts +8 -8
  464. package/src/inbound/{platform-callback-registration.ts → cloud-callback-registration.ts} +49 -60
  465. package/src/inbound/oauth-callback-url.ts +6 -6
  466. package/src/inbound/pod-webhook-claim.ts +2 -2
  467. package/src/inbound/public-ingress-urls.ts +3 -3
  468. package/src/inbound/tunnel-probe.ts +21 -15
  469. package/src/inbound/tunnel-webhooks-gate.ts +3 -3
  470. package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -1
  471. package/src/ipc/__tests__/email-ipc.test.ts +8 -8
  472. package/src/ipc/gateway-client.test.ts +55 -59
  473. package/src/ipc/gateway-flag-listener.ts +3 -3
  474. package/src/ipc/routes/db-proxy.ts +1 -1
  475. package/src/live-voice/__tests__/live-activity-reporter.test.ts +2 -2
  476. package/src/live-voice/__tests__/{live-voice-managed-speech-fallback.test.ts → live-voice-cloud-speech-fallback.test.ts} +15 -15
  477. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +7 -7
  478. package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
  479. package/src/live-voice/live-activity-reporter.ts +7 -7
  480. package/src/live-voice/live-voice-credential-preflight.ts +6 -6
  481. package/src/live-voice/live-voice-session.ts +3 -3
  482. package/src/live-voice/live-voice-tts.ts +1 -1
  483. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +4 -4
  484. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +1 -1
  485. package/src/mcp/mcp-oauth-provider.ts +2 -2
  486. package/src/media/gemini-image-service.ts +11 -13
  487. package/src/media/image-credentials.ts +9 -9
  488. package/src/media/openai-image-service.ts +1 -1
  489. package/src/media/types.ts +3 -3
  490. package/src/messaging/providers/channel-transport.ts +1 -1
  491. package/src/messaging/providers/discord/transport.ts +1 -1
  492. package/src/messaging/providers/gmail/adapter.ts +1 -1
  493. package/src/messaging/providers/gmail/client.ts +4 -4
  494. package/src/messaging/providers/outlook/adapter.ts +1 -1
  495. package/src/messaging/providers/slack/adapter.ts +1 -1
  496. package/src/messaging/providers/slack/transport.ts +1 -1
  497. package/src/messaging/providers/telegram-bot/transport.ts +1 -1
  498. package/src/monitoring/db-integrity-check.ts +1 -1
  499. package/src/monitoring/db-integrity-sample.ts +2 -2
  500. package/src/monitoring/mount-watch.ts +1 -1
  501. package/src/monitoring/oom-kill-reporter.ts +1 -1
  502. package/src/monitoring/worker.ts +5 -5
  503. package/src/notifications/README.md +2 -2
  504. package/src/notifications/__tests__/broadcaster.test.ts +9 -9
  505. package/src/notifications/__tests__/completion-policy.test.ts +1 -1
  506. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +4 -4
  507. package/src/notifications/activity-failed-dedupe.ts +1 -1
  508. package/src/notifications/adapters/{platform.ts → cloud.ts} +8 -8
  509. package/src/notifications/broadcaster.ts +10 -10
  510. package/src/notifications/decision-engine.ts +1 -1
  511. package/src/notifications/destination-resolver.ts +1 -1
  512. package/src/notifications/emit-signal.ts +6 -6
  513. package/src/notifications/types.ts +2 -2
  514. package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
  515. package/src/oauth/__tests__/{seed-providers-managed.test.ts → seed-providers-cloud.test.ts} +13 -13
  516. package/src/oauth/byo-connection.test.ts +2 -2
  517. package/src/oauth/{platform-connection.test.ts → cloud-connection.test.ts} +41 -43
  518. package/src/oauth/{platform-connection.ts → cloud-connection.ts} +19 -19
  519. package/src/oauth/connection-resolver.test.ts +59 -61
  520. package/src/oauth/connection-resolver.ts +22 -22
  521. package/src/oauth/connection.ts +1 -1
  522. package/src/oauth/oauth-store.ts +8 -8
  523. package/src/oauth/provider-serializer.ts +4 -4
  524. package/src/oauth/revoke.ts +1 -1
  525. package/src/oauth/seed-providers.ts +32 -32
  526. package/src/permissions/approval-provenance.ts +1 -1
  527. package/src/permissions/checker.test.ts +18 -16
  528. package/src/permissions/checker.ts +3 -3
  529. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  530. package/src/persistence/embeddings/embedding-backend.test.ts +16 -16
  531. package/src/persistence/embeddings/embedding-backend.ts +13 -13
  532. package/src/persistence/embeddings/embedding-gemini.test.ts +9 -12
  533. package/src/persistence/embeddings/embedding-gemini.ts +11 -11
  534. package/src/persistence/embeddings/embedding-runtime-manager.ts +2 -2
  535. package/src/persistence/jobs-store.ts +1 -1
  536. package/src/persistence/lifecycle-events-store.ts +1 -1
  537. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  538. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  539. package/src/persistence/migrations/224-oauth-providers-managed-service-is-paid.ts +1 -1
  540. package/src/persistence/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +1 -1
  541. package/src/persistence/migrations/296-rewrite-balanced-economy-profile-pins.ts +2 -2
  542. package/src/persistence/migrations/306-rewrite-frontier-profile-pins.test.ts +1 -1
  543. package/src/persistence/migrations/306-rewrite-frontier-profile-pins.ts +1 -1
  544. package/src/persistence/migrations/351-schedule-skill-script-handoff.ts +1 -1
  545. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +3 -3
  546. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +7 -7
  547. package/src/persistence/migrations/__tests__/319-remove-legacy-managed-connections.test.ts +2 -2
  548. package/src/persistence/schema/oauth.ts +1 -1
  549. package/src/plugin-api/index.ts +4 -4
  550. package/src/plugin-api/model-profiles.test.ts +1 -1
  551. package/src/plugin-api/model-profiles.ts +1 -1
  552. package/src/plugin-api/transcription-session.ts +1 -1
  553. package/src/plugin-api/vision-support.test.ts +1 -1
  554. package/src/plugin-api/webhook-url.ts +12 -12
  555. package/src/plugins/__tests__/source-fingerprint.test.ts +1 -1
  556. package/src/plugins/defaults/{platform-hosted → cloud-hosted}/__tests__/reengage.test.ts +5 -6
  557. package/src/plugins/defaults/cloud-hosted/package.json +14 -0
  558. package/src/plugins/defaults/{platform-hosted → cloud-hosted}/routes/reengage.ts +3 -3
  559. package/src/plugins/defaults/index.ts +6 -6
  560. package/src/plugins/defaults/main.ts +5 -5
  561. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +5 -5
  562. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +1 -1
  563. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +1 -1
  564. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -0
  565. package/src/plugins/defaults/memory/config.ts +4 -4
  566. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +2 -2
  567. package/src/plugins/defaults/memory/memory-retrospective-job.ts +7 -7
  568. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +5 -5
  569. package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +2 -2
  570. package/src/plugins/defaults/memory/src/__tests__/memory-v2-routes.test.ts +1 -1
  571. package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +1 -1
  572. package/src/plugins/defaults/memory/substrate/__tests__/boot-maintenance.test.ts +20 -20
  573. package/src/plugins/defaults/memory/substrate/__tests__/cli-command-store.test.ts +1 -1
  574. package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +3 -3
  575. package/src/plugins/defaults/memory/substrate/boot-maintenance.ts +17 -17
  576. package/src/plugins/defaults/memory/substrate/cli-command-store.ts +2 -2
  577. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +1 -1
  578. package/src/plugins/defaults/memory/substrate/skill-store.ts +4 -4
  579. package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +3 -3
  580. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +2 -2
  581. package/src/plugins/defaults/memory/v3/maintain-job.ts +10 -10
  582. package/src/plugins/defaults/memory/v3/orchestrate.ts +3 -3
  583. package/src/plugins/defaults/memory/worker.ts +5 -5
  584. package/src/plugins/defaults/workspace/injectors.ts +2 -2
  585. package/src/plugins/mtime-cache.ts +1 -1
  586. package/src/plugins/source-versions.ts +1 -1
  587. package/src/plugins/types.ts +1 -1
  588. package/src/prompts/__tests__/system-prompt.test.ts +1 -1
  589. package/src/prompts/normalize-onboarding.ts +1 -1
  590. package/src/prompts/persona-resolver.ts +1 -1
  591. package/src/prompts/templates/system-sections.ts +3 -3
  592. package/src/providers/__tests__/cadet-connection-routing.test.ts +7 -7
  593. package/src/providers/__tests__/cadet-mismatch-routing.test.ts +12 -12
  594. package/src/providers/__tests__/connection-model-compat.test.ts +2 -2
  595. package/src/providers/__tests__/dispatch-connection-routing.test.ts +10 -10
  596. package/src/providers/__tests__/inference.test.ts +2 -2
  597. package/src/providers/__tests__/preflight-resolved-config.test.ts +16 -16
  598. package/src/providers/__tests__/retry-callsite.test.ts +1 -1
  599. package/src/providers/__tests__/satellite-connection-routing.test.ts +4 -4
  600. package/src/providers/__tests__/search-provider-catalog.test.ts +2 -2
  601. package/src/providers/__tests__/voice-error-copy.test.ts +3 -3
  602. package/src/providers/anthropic/client.ts +1 -1
  603. package/src/providers/cadet/client.ts +1 -1
  604. package/src/providers/cadet-model-routing.test.ts +19 -19
  605. package/src/providers/cadet-model-routing.ts +23 -23
  606. package/src/providers/call-site-routing.ts +10 -10
  607. package/src/providers/{platform-proxy → cloud-proxy}/constants.ts +11 -11
  608. package/src/providers/{platform-proxy → cloud-proxy}/context.ts +26 -26
  609. package/src/providers/connection-resolution.ts +43 -43
  610. package/src/providers/fallback-breaker.ts +2 -2
  611. package/src/providers/fetch-provider-catalog.ts +3 -3
  612. package/src/providers/gemini/client.ts +5 -5
  613. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +3 -3
  614. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +8 -8
  615. package/src/providers/inference/__tests__/base-url-security.test.ts +4 -4
  616. package/src/providers/inference/adapter-factory.ts +35 -35
  617. package/src/providers/inference/auth.ts +10 -10
  618. package/src/providers/inference/backfill.ts +11 -11
  619. package/src/providers/inference/connection-availability.ts +24 -24
  620. package/src/providers/inference/connections.ts +18 -20
  621. package/src/providers/inference/profile-probe.ts +4 -4
  622. package/src/providers/inference/resolve-auth.ts +8 -8
  623. package/src/providers/jev/ask.test.ts +1 -1
  624. package/src/providers/jev/ask.ts +2 -2
  625. package/src/providers/model-catalog.ts +8 -9
  626. package/src/providers/openai/__tests__/api-error-normalization.test.ts +3 -3
  627. package/src/providers/openai/api-error-normalization.ts +2 -2
  628. package/src/providers/provider-availability.ts +4 -4
  629. package/src/providers/provider-env-vars.ts +1 -1
  630. package/src/providers/provider-secret-catalog.ts +1 -1
  631. package/src/providers/registry.ts +20 -20
  632. package/src/providers/retry.ts +26 -26
  633. package/src/providers/routing-identity.ts +10 -10
  634. package/src/providers/search-provider-catalog.ts +2 -2
  635. package/src/providers/speech-to-text/__tests__/{cadet-managed-realtime.test.ts → cadet-cloud-realtime.test.ts} +3 -3
  636. package/src/providers/speech-to-text/__tests__/{cadet-managed.test.ts → cadet-cloud.test.ts} +35 -35
  637. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +1 -1
  638. package/src/providers/speech-to-text/__tests__/resolve.test.ts +27 -27
  639. package/src/providers/speech-to-text/{cadet-managed-flux-realtime.ts → cadet-cloud-flux-realtime.ts} +6 -8
  640. package/src/providers/speech-to-text/{cadet-managed-realtime.ts → cadet-cloud-realtime.ts} +3 -3
  641. package/src/providers/speech-to-text/{cadet-managed.ts → cadet-cloud.ts} +18 -20
  642. package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +7 -7
  643. package/src/providers/speech-to-text/deepgram-flux-frames.ts +3 -3
  644. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +4 -4
  645. package/src/providers/speech-to-text/provider-catalog.ts +3 -3
  646. package/src/providers/speech-to-text/resolve.ts +26 -28
  647. package/src/providers/together/client.ts +1 -1
  648. package/src/providers/types.ts +4 -4
  649. package/src/providers/voice-error-copy.ts +1 -1
  650. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +10 -10
  651. package/src/runtime/agent-wake.ts +1 -1
  652. package/src/runtime/auth/__tests__/middleware.test.ts +1 -1
  653. package/src/runtime/auth/middleware.ts +1 -1
  654. package/src/runtime/auth/scopes.ts +1 -1
  655. package/src/runtime/capabilities.ts +1 -1
  656. package/src/runtime/channel-invite-transports/discord.ts +1 -1
  657. package/src/runtime/channel-invite-transports/email.ts +2 -2
  658. package/src/runtime/channel-readiness-service.ts +15 -13
  659. package/src/runtime/channel-reply-session.test.ts +1 -1
  660. package/src/runtime/channel-reply-session.ts +1 -1
  661. package/src/runtime/http-server.ts +4 -4
  662. package/src/runtime/introduction-policy.ts +4 -4
  663. package/src/runtime/migrations/__tests__/origin-mode.test.ts +14 -14
  664. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +1 -1
  665. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +2 -2
  666. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +3 -4
  667. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +6 -7
  668. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +1 -1
  669. package/src/runtime/migrations/migration-transport.ts +3 -3
  670. package/src/runtime/migrations/origin-mode.ts +6 -9
  671. package/src/runtime/migrations/vbundle-import-policy.ts +2 -2
  672. package/src/runtime/migrations/vbundle-importer.ts +4 -4
  673. package/src/runtime/migrations/vbundle-streaming-importer.ts +6 -6
  674. package/src/runtime/migrations/vbundle-validator.ts +1 -1
  675. package/src/runtime/routes/__tests__/{platform-credits-routes.test.ts → cloud-credits-routes.test.ts} +9 -9
  676. package/src/runtime/routes/__tests__/{platform-invoice-routes.test.ts → cloud-invoice-routes.test.ts} +13 -13
  677. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +1 -1
  678. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +126 -82
  679. package/src/runtime/routes/__tests__/default-plugin-routes.test.ts +5 -5
  680. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +10 -10
  681. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +18 -18
  682. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +18 -16
  683. package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
  684. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +1 -1
  685. package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +5 -20
  686. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -22
  687. package/src/runtime/routes/__tests__/oauth-proxy-routes.test.ts +27 -27
  688. package/src/runtime/routes/__tests__/plugins-routes.test.ts +5 -5
  689. package/src/runtime/routes/__tests__/ps-routes.test.ts +5 -3
  690. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +15 -15
  691. package/src/runtime/routes/__tests__/webhook-routes.test.ts +31 -31
  692. package/src/runtime/routes/app-management-routes.ts +1 -1
  693. package/src/runtime/routes/attachment-routes.ts +1 -1
  694. package/src/runtime/routes/auth-routes.ts +14 -14
  695. package/src/runtime/routes/avatar-routes.ts +0 -14
  696. package/src/runtime/routes/background-wake-routes.ts +4 -4
  697. package/src/runtime/routes/channel-availability-routes.ts +4 -4
  698. package/src/runtime/routes/client-routes.ts +5 -5
  699. package/src/runtime/routes/cloud-credentials.ts +45 -0
  700. package/src/runtime/routes/{platform-routes.ts → cloud-routes.ts} +86 -119
  701. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  702. package/src/runtime/routes/conversation-query-routes.ts +345 -667
  703. package/src/runtime/routes/credential-routes.ts +28 -30
  704. package/src/runtime/routes/default-provider-routes.ts +1 -1
  705. package/src/runtime/routes/desktop-setup-routes.test.ts +5 -5
  706. package/src/runtime/routes/desktop-setup-routes.ts +1 -1
  707. package/src/runtime/routes/domain-routes.ts +15 -21
  708. package/src/runtime/routes/email-routes.ts +13 -13
  709. package/src/runtime/routes/errors.ts +1 -1
  710. package/src/runtime/routes/gateway-status-routes.ts +1 -1
  711. package/src/runtime/routes/identity-routes.ts +3 -3
  712. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  713. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
  714. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  715. package/src/runtime/routes/index.ts +1 -1
  716. package/src/runtime/routes/inference-profile-availability-guard.ts +5 -5
  717. package/src/runtime/routes/inference-profiles-routes.ts +11 -11
  718. package/src/runtime/routes/inference-provider-connection-routes.ts +24 -24
  719. package/src/runtime/routes/ingress-status-routes.ts +1 -1
  720. package/src/runtime/routes/integrations/a2a.ts +2 -2
  721. package/src/runtime/routes/integrations/twilio.ts +21 -35
  722. package/src/runtime/routes/internal-telemetry-routes.ts +2 -2
  723. package/src/runtime/routes/live-voice-routes.test.ts +4 -4
  724. package/src/runtime/routes/live-voice-routes.ts +6 -6
  725. package/src/runtime/routes/migration-routes.ts +34 -40
  726. package/src/runtime/routes/oauth-commands-routes.ts +31 -31
  727. package/src/runtime/routes/oauth-providers.ts +5 -5
  728. package/src/runtime/routes/oauth-proxy-passthrough.test.ts +1 -1
  729. package/src/runtime/routes/oauth-proxy-passthrough.ts +8 -8
  730. package/src/runtime/routes/oauth-proxy-routes.ts +15 -17
  731. package/src/runtime/routes/oauth-request-hints.test.ts +1 -1
  732. package/src/runtime/routes/oauth-request-hints.ts +1 -1
  733. package/src/runtime/routes/plugins-routes.ts +9 -9
  734. package/src/runtime/routes/profiler-routes.ts +1 -1
  735. package/src/runtime/routes/ps-routes.ts +4 -0
  736. package/src/runtime/routes/roadmap-routes.ts +18 -20
  737. package/src/runtime/routes/secret-routes.ts +20 -61
  738. package/src/runtime/routes/settings-routes.ts +18 -23
  739. package/src/runtime/routes/telemetry-routes.ts +4 -4
  740. package/src/runtime/routes/tts-routes.ts +4 -4
  741. package/src/runtime/routes/user-route-resolution.ts +1 -1
  742. package/src/runtime/routes/webhook-routes.ts +18 -18
  743. package/src/runtime/routes/workflow-routes.ts +5 -5
  744. package/src/runtime/sync/resource-sync-events.ts +2 -2
  745. package/src/runtime/verification-outbound-actions.ts +4 -4
  746. package/src/schedule/integration-status.ts +6 -6
  747. package/src/schedule/run-script.ts +1 -1
  748. package/src/schedule/schedule-timezone.ts +1 -1
  749. package/src/schedule/worker.ts +5 -5
  750. package/src/security/auth-fallback-events-store.ts +1 -1
  751. package/src/security/oauth2.ts +4 -4
  752. package/src/skills/available-skills.ts +4 -4
  753. package/src/skills/catalog-files.ts +8 -8
  754. package/src/skills/catalog-install.ts +12 -12
  755. package/src/skills/categories-cache.ts +3 -3
  756. package/src/skills/clawhub.ts +1 -1
  757. package/src/skills/{platform-compatibility.test.ts → cloud-compatibility.test.ts} +7 -7
  758. package/src/skills/{platform-compatibility.ts → cloud-compatibility.ts} +2 -4
  759. package/src/skills/{managed-store.ts → cloud-store.ts} +21 -23
  760. package/src/skills/inline-command-runner.ts +1 -1
  761. package/src/skills/install-meta.ts +1 -1
  762. package/src/skills/path-classifier.ts +4 -4
  763. package/src/skills/skill-history.ts +4 -4
  764. package/src/skills/skillssh-registry.ts +1 -1
  765. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +1 -1
  766. package/src/stt/daemon-batch-transcriber.ts +7 -7
  767. package/src/stt/types.ts +3 -3
  768. package/src/subagent/manager.ts +15 -15
  769. package/src/subagent/types.ts +1 -1
  770. package/src/telegram/__tests__/webhook-health.test.ts +16 -16
  771. package/src/telegram/webhook-health.ts +10 -10
  772. package/src/telemetry/__tests__/config-setting-events-store.test.ts +1 -1
  773. package/src/telemetry/__tests__/outbox-test-harness.ts +2 -2
  774. package/src/telemetry/config-setting-events-store.ts +1 -1
  775. package/src/telemetry/config-setting-snapshot.ts +1 -1
  776. package/src/telemetry/memory-corpus-size.ts +1 -1
  777. package/src/telemetry/memory-tier-reporter.test.ts +2 -2
  778. package/src/telemetry/memory-tier-reporter.ts +3 -3
  779. package/src/telemetry/telemetry-event-sources.test.ts +2 -2
  780. package/src/telemetry/telemetry-event-sources.ts +9 -9
  781. package/src/telemetry/telemetry-events-outbox.ts +2 -2
  782. package/src/telemetry/telemetry-wire-validation.test.ts +1 -1
  783. package/src/telemetry/telemetry-wire-validation.ts +3 -3
  784. package/src/telemetry/telemetry-wire.generated.ts +1 -1
  785. package/src/telemetry/tool-audit.ts +1 -1
  786. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  787. package/src/telemetry/trace-collection-policy.ts +4 -4
  788. package/src/telemetry/turn-events-store.ts +1 -1
  789. package/src/telemetry/turn-outcome.ts +1 -1
  790. package/src/telemetry/turn-trace-store.ts +2 -2
  791. package/src/telemetry/types.test.ts +1 -1
  792. package/src/telemetry/types.ts +19 -19
  793. package/src/telemetry/usage-telemetry-reporter.test.ts +42 -42
  794. package/src/telemetry/usage-telemetry-reporter.ts +17 -17
  795. package/src/telemetry/watchdog-direct-emit.test.ts +9 -9
  796. package/src/telemetry/watchdog-direct-emit.ts +10 -10
  797. package/src/tools/__tests__/side-effect-abort-guard.test.ts +2 -2
  798. package/src/tools/browser/cdp-client/factory.ts +1 -1
  799. package/src/tools/computer-use/target.ts +2 -2
  800. package/src/tools/credentials/store.ts +2 -20
  801. package/src/tools/network/__tests__/{managed-search-proxy.test.ts → cloud-search-proxy.test.ts} +20 -20
  802. package/src/tools/network/__tests__/web-search.test.ts +14 -14
  803. package/src/tools/network/{managed-search-proxy.ts → cloud-search-proxy.ts} +13 -13
  804. package/src/tools/network/web-search.ts +9 -9
  805. package/src/tools/permission-checker.ts +2 -2
  806. package/src/tools/shared/input-misuse.ts +1 -1
  807. package/src/tools/shared/input-repairs.ts +4 -4
  808. package/src/tools/skills/{delete-managed.ts → delete-cloud.ts} +4 -4
  809. package/src/tools/skills/find-similar-skills.test.ts +7 -7
  810. package/src/tools/skills/find-similar-skills.ts +15 -15
  811. package/src/tools/skills/load.ts +6 -6
  812. package/src/tools/skills/{scaffold-managed.ts → scaffold-cloud.ts} +16 -16
  813. package/src/tools/skills/skill-script-runner.ts +2 -2
  814. package/src/tools/terminal/safe-env.ts +3 -3
  815. package/src/tools/tool-types.ts +2 -2
  816. package/src/tools/types.ts +2 -2
  817. package/src/tools/ui-surface/surface-shape-docs.ts +2 -2
  818. package/src/tools/workflows/manage-workflows.ts +1 -1
  819. package/src/tools/workspace-tools/loader.ts +1 -1
  820. package/src/tts/__tests__/cadet-provider.test.ts +5 -5
  821. package/src/tts/provider-catalog.ts +1 -1
  822. package/src/tts/providers/cadet-provider.ts +23 -23
  823. package/src/tts/providers/cadet-tts-socket.ts +2 -2
  824. package/src/tts/tts-config-resolver.ts +1 -1
  825. package/src/tts/tts-voice-field.ts +3 -3
  826. package/src/usage/subagent-attribution.ts +1 -1
  827. package/src/util/__tests__/process-table.test.ts +47 -0
  828. package/src/util/canonicalize-identity.ts +1 -1
  829. package/src/util/cgroup-cpu.ts +5 -5
  830. package/src/util/cgroup-memory.ts +2 -2
  831. package/src/util/container-cpu-sampler.ts +4 -4
  832. package/src/util/disk-usage.ts +4 -4
  833. package/src/util/errors.ts +3 -3
  834. package/src/util/layout.ts +479 -0
  835. package/src/util/oom-priority.ts +1 -1
  836. package/src/util/platform.ts +12 -468
  837. package/src/util/process-table.ts +39 -5
  838. package/src/util/provider-error-patterns.ts +2 -2
  839. package/src/watch/__tests__/watch-retro.test.ts +2 -2
  840. package/src/watch/watch-retro.ts +1 -1
  841. package/src/watcher/__tests__/telemetry.test.ts +2 -2
  842. package/src/watcher/telemetry.ts +1 -1
  843. package/src/windows-compiled-cli.ts +1 -1
  844. package/src/workspace/adaptive-thinking-repair.ts +4 -4
  845. package/src/workspace/byok-default-profile-ensure.ts +7 -7
  846. package/src/workspace/git-service.ts +2 -2
  847. package/src/workspace/migrations/006-services-config.ts +1 -1
  848. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +1 -1
  849. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +1 -1
  850. package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +1 -1
  851. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +1 -1
  852. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +4 -4
  853. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +3 -3
  854. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +4 -4
  855. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +5 -5
  856. package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +4 -4
  857. package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +4 -4
  858. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +5 -5
  859. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +1 -1
  860. package/src/workspace/migrations/106-drop-collect-usage-data.ts +1 -1
  861. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +4 -4
  862. package/src/workspace/migrations/109-swap-quality-profile-to-glm-5p2.ts +11 -11
  863. package/src/workspace/migrations/110-flip-balanced-profile-to-together.ts +6 -6
  864. package/src/workspace/migrations/113-swap-balanced-profile-to-glm-5p2.ts +5 -5
  865. package/src/workspace/migrations/114-swap-quality-profile-to-opus.ts +4 -4
  866. package/src/workspace/migrations/115-drop-frontier-profile.ts +5 -5
  867. package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +5 -5
  868. package/src/workspace/migrations/125-repoint-managed-connections-to-cadet.ts +3 -3
  869. package/src/workspace/migrations/126-strip-managed-profile-bodies.ts +2 -2
  870. package/src/workspace/migrations/127-backfill-default-provider.ts +6 -6
  871. package/src/workspace/migrations/130-speech-mode-to-provider.ts +4 -4
  872. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +1 -1
  873. package/src/workspace/migrations/132-web-search-mode-to-provider.ts +6 -6
  874. package/src/workspace/migrations/133-collapse-provider-connections.ts +3 -3
  875. package/src/workspace/migrations/134-image-generation-mode-to-provider.ts +4 -4
  876. package/src/workspace/migrations/136-repair-stale-fireworks-kimi-model-id.ts +2 -2
  877. package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +2 -2
  878. package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +2 -2
  879. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +4 -4
  880. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +1 -1
  881. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +2 -2
  882. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +2 -2
  883. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +2 -2
  884. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +1 -1
  885. package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +2 -2
  886. package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +2 -2
  887. package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +2 -2
  888. package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +2 -2
  889. package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +2 -2
  890. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +1 -1
  891. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +1 -1
  892. package/src/workspace/migrations/registry.ts +6 -6
  893. package/src/workspace/top-level-renderer.ts +1 -1
  894. package/src/workspace/turn-commit.ts +2 -2
  895. package/src/__tests__/config-loader-platform-defaults.test.ts +0 -606
  896. package/src/__tests__/mock-gateway-ipc.ts +0 -260
  897. package/src/api/events/platform-disconnected.ts +0 -21
  898. package/src/api/events/show-platform-login.ts +0 -20
  899. package/src/config/platform-rehydration.ts +0 -60
  900. package/src/platform/feature-gate.ts +0 -8
  901. package/src/plugins/defaults/platform-hosted/package.json +0 -14
  902. package/src/runtime/routes/platform-managed-credentials.ts +0 -50
  903. /package/src/__tests__/{gateway-client-managed-outbound.test.ts → gateway-client-cloud-outbound.test.ts} +0 -0
  904. /package/src/__tests__/{secure-keys-managed-failover.test.ts → secure-keys-cloud-failover.test.ts} +0 -0
@@ -1,196 +1,186 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
1
+ /**
2
+ * Web search tool (Brave / Perplexity / Tavily) driven through the REAL
3
+ * `web-search.ts` execute path: provider keys are seeded into the real
4
+ * secure-keys module (memory backend), provider selection goes through the
5
+ * real workspace config, and HTTP is mocked at the `globalThis.fetch`
6
+ * boundary with `mockHttp()`.
7
+ *
8
+ * This file used to inline-copy the tool's execute logic (three providers'
9
+ * worth of duplicated formatting/retry code) and test the copy; it now tests
10
+ * the production module so drift fails here instead of shipping.
11
+ */
12
+ import { beforeEach, describe, expect, test } from "bun:test";
13
+
14
+ import { credentialKey } from "../security/credential-key.js";
15
+ import { WEB_SEARCH_BACKEND_FAILURE_MESSAGE } from "../tools/network/web-search-error.js";
16
+ import { useMemorySecureKeys } from "./helpers/secure-keys.js";
17
+ import { setConfig } from "./helpers/set-config.js";
18
+ import { mockHttp } from "./requests-mock.js";
19
+
20
+ const keyStore = useMemorySecureKeys({
21
+ [credentialKey("brave", "api_key")]: "brave-key",
22
+ [credentialKey("perplexity", "api_key")]: "pplx-key",
23
+ [credentialKey("tavily", "api_key")]: "tvly-key",
24
+ });
2
25
 
3
- // No mock.module calls — this test file uses its own inline executeWebSearch
4
- // helper and does not import any production modules. Stray mock.module calls
5
- // here leak into the shared Bun test process and break other test files.
26
+ const { webSearchTool } = await import("../tools/network/web-search.js");
6
27
 
7
- describe("WebSearchTool", () => {
8
- const originalEnv = process.env;
9
- let originalFetch: typeof globalThis.fetch;
28
+ type Provider = "brave" | "perplexity" | "tavily";
10
29
 
11
- beforeEach(() => {
12
- process.env = { ...originalEnv };
13
- originalFetch = globalThis.fetch;
14
- });
15
-
16
- afterEach(() => {
17
- process.env = originalEnv;
18
- globalThis.fetch = originalFetch;
19
- });
30
+ async function executeWebSearch(
31
+ input: Record<string, unknown>,
32
+ provider: Provider,
33
+ ) {
34
+ setConfig("services", { "web-search": { provider } });
35
+ return webSearchTool.execute(input, {} as never);
36
+ }
20
37
 
21
- // Since the tool self-registers via module side effect, we test the core behaviors
22
- // by importing the module and testing the registered tool's execute method.
38
+ beforeEach(() => {
39
+ // Fresh keys per test — no-key tests clear the store for their duration
40
+ // and every test starts with all three providers seeded.
41
+ for (const [service, value] of Object.entries({
42
+ brave: "brave-key",
43
+ perplexity: "pplx-key",
44
+ tavily: "tvly-key",
45
+ })) {
46
+ keyStore[credentialKey(service, "api_key")] = value;
47
+ }
48
+ });
23
49
 
50
+ describe("WebSearchTool", () => {
24
51
  describe("API key resolution", () => {
25
52
  test("returns error when no API key is configured (brave)", async () => {
26
- delete process.env.BRAVE_API_KEY;
27
- const result = await executeWebSearch(
28
- { query: "test" },
29
- undefined,
30
- "brave",
31
- );
53
+ for (const key of Object.keys(keyStore)) {
54
+ delete keyStore[key];
55
+ }
56
+ const http = mockHttp();
57
+
58
+ const result = await executeWebSearch({ query: "test" }, "brave");
32
59
  expect(result.isError).toBe(true);
33
60
  expect(result.content).toContain("No web search API key configured");
61
+ // No key anywhere in the fallback chain — nothing is called.
62
+ expect(http.callCount()).toBe(0);
34
63
  });
35
64
 
36
65
  test("returns error when no API key is configured (perplexity)", async () => {
37
- delete process.env.PERPLEXITY_API_KEY;
38
- const result = await executeWebSearch(
39
- { query: "test" },
40
- undefined,
41
- "perplexity",
42
- );
66
+ for (const key of Object.keys(keyStore)) {
67
+ delete keyStore[key];
68
+ }
69
+ const http = mockHttp();
70
+
71
+ const result = await executeWebSearch({ query: "test" }, "perplexity");
43
72
  expect(result.isError).toBe(true);
44
73
  expect(result.content).toContain("No web search API key configured");
74
+ expect(http.callCount()).toBe(0);
45
75
  });
46
76
 
47
77
  test("returns error when no API key is configured (tavily)", async () => {
48
- delete process.env.TAVILY_API_KEY;
49
- const result = await executeWebSearch(
50
- { query: "test" },
51
- undefined,
52
- "tavily",
53
- );
78
+ for (const key of Object.keys(keyStore)) {
79
+ delete keyStore[key];
80
+ }
81
+ const http = mockHttp();
82
+
83
+ const result = await executeWebSearch({ query: "test" }, "tavily");
54
84
  expect(result.isError).toBe(true);
55
85
  expect(result.content).toContain("No web search API key configured");
86
+ expect(http.callCount()).toBe(0);
56
87
  });
57
88
  });
58
89
 
59
90
  describe("input validation", () => {
60
91
  test("rejects missing query", async () => {
61
- // Set up a mock fetch that should NOT be called
62
- let fetchCalled = false;
63
- globalThis.fetch = (async () => {
64
- fetchCalled = true;
65
- return new Response("{}", { status: 200 });
66
- }) as unknown as typeof fetch;
67
-
68
- process.env.BRAVE_API_KEY = "test-key";
69
-
70
- // We need to test the tool's execute method directly
71
- // Since bun:test module mocking is limited for re-imports,
72
- // we'll create a minimal test using the tool's logic
73
- const result = await executeWebSearch({}, "test-key", "brave");
92
+ const http = mockHttp();
93
+
94
+ const result = await executeWebSearch({}, "brave");
74
95
  expect(result.isError).toBe(true);
75
96
  expect(result.content).toContain("query is required");
76
- expect(fetchCalled).toBe(false);
97
+ expect(http.callCount()).toBe(0);
77
98
  });
78
99
 
79
100
  test("rejects non-string query", async () => {
80
- const result = await executeWebSearch(
81
- { query: 123 },
82
- "test-key",
83
- "brave",
84
- );
101
+ const http = mockHttp();
102
+
103
+ const result = await executeWebSearch({ query: 123 }, "brave");
85
104
  expect(result.isError).toBe(true);
86
105
  expect(result.content).toContain("query is required");
106
+ expect(http.callCount()).toBe(0);
87
107
  });
88
108
  });
89
109
 
90
110
  describe("Brave parameter handling", () => {
91
111
  test("clamps count to valid range", async () => {
92
- let capturedUrl = "";
93
- globalThis.fetch = (async (url: string) => {
94
- capturedUrl = url;
95
- return new Response(JSON.stringify({ web: { results: [] } }), {
96
- status: 200,
97
- headers: { "Content-Type": "application/json" },
98
- });
99
- }) as unknown as typeof fetch;
112
+ const http = mockHttp();
113
+ http.on("api.search.brave.com", {
114
+ status: 200,
115
+ body: { web: { results: [] } },
116
+ });
100
117
 
101
- await executeWebSearch({ query: "test", count: 50 }, "test-key", "brave");
102
- expect(capturedUrl).toContain("count=20");
118
+ await executeWebSearch({ query: "test", count: 50 }, "brave");
119
+ expect(http.lastCall()?.url).toContain("count=20");
103
120
 
104
- await executeWebSearch({ query: "test", count: -5 }, "test-key", "brave");
105
- expect(capturedUrl).toContain("count=1");
121
+ await executeWebSearch({ query: "test", count: -5 }, "brave");
122
+ expect(http.lastCall()?.url).toContain("count=1");
106
123
  });
107
124
 
108
125
  test("clamps offset to valid range", async () => {
109
- let capturedUrl = "";
110
- globalThis.fetch = (async (url: string) => {
111
- capturedUrl = url;
112
- return new Response(JSON.stringify({ web: { results: [] } }), {
113
- status: 200,
114
- headers: { "Content-Type": "application/json" },
115
- });
116
- }) as unknown as typeof fetch;
126
+ const http = mockHttp();
127
+ http.on("api.search.brave.com", {
128
+ status: 200,
129
+ body: { web: { results: [] } },
130
+ });
117
131
 
118
- await executeWebSearch(
119
- { query: "test", offset: 20 },
120
- "test-key",
121
- "brave",
122
- );
123
- expect(capturedUrl).toContain("offset=9");
132
+ await executeWebSearch({ query: "test", offset: 20 }, "brave");
133
+ expect(http.lastCall()?.url).toContain("offset=9");
124
134
  });
125
135
 
126
136
  test("includes freshness when valid", async () => {
127
- let capturedUrl = "";
128
- globalThis.fetch = (async (url: string) => {
129
- capturedUrl = url;
130
- return new Response(JSON.stringify({ web: { results: [] } }), {
131
- status: 200,
132
- headers: { "Content-Type": "application/json" },
133
- });
134
- }) as unknown as typeof fetch;
137
+ const http = mockHttp();
138
+ http.on("api.search.brave.com", {
139
+ status: 200,
140
+ body: { web: { results: [] } },
141
+ });
135
142
 
136
- await executeWebSearch(
137
- { query: "test", freshness: "pw" },
138
- "test-key",
139
- "brave",
140
- );
141
- expect(capturedUrl).toContain("freshness=pw");
143
+ await executeWebSearch({ query: "test", freshness: "pw" }, "brave");
144
+ expect(http.lastCall()?.url).toContain("freshness=pw");
142
145
  });
143
146
 
144
147
  test("ignores invalid freshness values", async () => {
145
- let capturedUrl = "";
146
- globalThis.fetch = (async (url: string) => {
147
- capturedUrl = url;
148
- return new Response(JSON.stringify({ web: { results: [] } }), {
149
- status: 200,
150
- headers: { "Content-Type": "application/json" },
151
- });
152
- }) as unknown as typeof fetch;
148
+ const http = mockHttp();
149
+ http.on("api.search.brave.com", {
150
+ status: 200,
151
+ body: { web: { results: [] } },
152
+ });
153
153
 
154
- await executeWebSearch(
155
- { query: "test", freshness: "invalid" },
156
- "test-key",
157
- "brave",
158
- );
159
- expect(capturedUrl).not.toContain("freshness");
154
+ await executeWebSearch({ query: "test", freshness: "invalid" }, "brave");
155
+ expect(http.lastCall()?.url).not.toContain("freshness");
160
156
  });
161
157
  });
162
158
 
163
159
  describe("Brave API responses", () => {
164
160
  test("formats results correctly", async () => {
165
- const mockResults = {
166
- web: {
167
- results: [
168
- {
169
- title: "Test Result",
170
- url: "https://example.com",
171
- description: "A test result",
172
- },
173
- {
174
- title: "Another Result",
175
- url: "https://other.com",
176
- description: "Another one",
177
- age: "2 days ago",
178
- },
179
- ],
161
+ const http = mockHttp();
162
+ http.on("api.search.brave.com", {
163
+ status: 200,
164
+ body: {
165
+ web: {
166
+ results: [
167
+ {
168
+ title: "Test Result",
169
+ url: "https://example.com",
170
+ description: "A test result",
171
+ },
172
+ {
173
+ title: "Another Result",
174
+ url: "https://other.com",
175
+ description: "Another one",
176
+ age: "2 days ago",
177
+ },
178
+ ],
179
+ },
180
180
  },
181
- };
182
-
183
- globalThis.fetch = (async () =>
184
- new Response(JSON.stringify(mockResults), {
185
- status: 200,
186
- headers: { "Content-Type": "application/json" },
187
- })) as unknown as typeof fetch;
181
+ });
188
182
 
189
- const result = await executeWebSearch(
190
- { query: "test" },
191
- "test-key",
192
- "brave",
193
- );
183
+ const result = await executeWebSearch({ query: "test" }, "brave");
194
184
  expect(result.isError).toBe(false);
195
185
  expect(result.content).toContain("Test Result");
196
186
  expect(result.content).toContain("https://example.com");
@@ -200,61 +190,51 @@ describe("WebSearchTool", () => {
200
190
  });
201
191
 
202
192
  test("handles empty results", async () => {
203
- globalThis.fetch = (async () =>
204
- new Response(JSON.stringify({ web: { results: [] } }), {
205
- status: 200,
206
- headers: { "Content-Type": "application/json" },
207
- })) as unknown as typeof fetch;
193
+ const http = mockHttp();
194
+ http.on("api.search.brave.com", {
195
+ status: 200,
196
+ body: { web: { results: [] } },
197
+ });
208
198
 
209
- const result = await executeWebSearch(
210
- { query: "noresults" },
211
- "test-key",
212
- "brave",
213
- );
199
+ const result = await executeWebSearch({ query: "noresults" }, "brave");
214
200
  expect(result.isError).toBe(false);
215
201
  expect(result.content).toContain("No results found");
216
202
  });
217
203
 
218
204
  test("handles missing web field", async () => {
219
- globalThis.fetch = (async () =>
220
- new Response(JSON.stringify({}), {
221
- status: 200,
222
- headers: { "Content-Type": "application/json" },
223
- })) as unknown as typeof fetch;
205
+ const http = mockHttp();
206
+ http.on("api.search.brave.com", { status: 200, body: {} });
224
207
 
225
- const result = await executeWebSearch(
226
- { query: "empty" },
227
- "test-key",
228
- "brave",
229
- );
208
+ const result = await executeWebSearch({ query: "empty" }, "brave");
230
209
  expect(result.isError).toBe(false);
231
210
  expect(result.content).toContain("No results found");
232
211
  });
233
212
 
234
213
  test("handles 401 unauthorized", async () => {
235
- globalThis.fetch = (async () =>
236
- new Response("Unauthorized", {
237
- status: 401,
238
- })) as unknown as typeof fetch;
214
+ const http = mockHttp();
215
+ http.on("api.search.brave.com", { status: 401, body: "Unauthorized" });
239
216
 
240
- const result = await executeWebSearch(
241
- { query: "test" },
242
- "bad-key",
243
- "brave",
244
- );
217
+ const result = await executeWebSearch({ query: "test" }, "brave");
245
218
  expect(result.isError).toBe(true);
246
219
  expect(result.content).toContain("Invalid or expired");
247
220
  });
248
221
 
249
222
  test("retries on 429 and succeeds", async () => {
250
- let callCount = 0;
251
- globalThis.fetch = (async () => {
252
- callCount++;
253
- if (callCount <= 2) {
254
- return new Response("Too Many Requests", { status: 429 });
255
- }
256
- return new Response(
257
- JSON.stringify({
223
+ const http = mockHttp();
224
+ http.on("api.search.brave.com", [
225
+ {
226
+ status: 429,
227
+ body: "Too Many Requests",
228
+ headers: { "Retry-After": "0" },
229
+ },
230
+ {
231
+ status: 429,
232
+ body: "Too Many Requests",
233
+ headers: { "Retry-After": "0" },
234
+ },
235
+ {
236
+ status: 200,
237
+ body: {
258
238
  web: {
259
239
  results: [
260
240
  {
@@ -264,120 +244,89 @@ describe("WebSearchTool", () => {
264
244
  },
265
245
  ],
266
246
  },
267
- }),
268
- { status: 200, headers: { "Content-Type": "application/json" } },
269
- );
270
- }) as unknown as typeof fetch;
247
+ },
248
+ },
249
+ ]);
271
250
 
272
- const result = await executeWebSearch(
273
- { query: "test" },
274
- "test-key",
275
- "brave",
276
- );
251
+ const result = await executeWebSearch({ query: "test" }, "brave");
277
252
  expect(result.isError).toBe(false);
278
- expect(result.content).toContain("Result");
279
- expect(callCount).toBe(3);
253
+ expect(result.content).toContain("Found it");
254
+ expect(http.callCount()).toBe(3);
280
255
  });
281
256
 
282
- test("returns error after exhausting 429 retries", async () => {
283
- globalThis.fetch = (async () =>
284
- new Response("Too Many Requests", {
285
- status: 429,
286
- })) as unknown as typeof fetch;
257
+ test("returns the friendly backend copy after exhausting 429 retries", async () => {
258
+ const http = mockHttp();
259
+ http.on("api.search.brave.com", {
260
+ status: 429,
261
+ body: "Too Many Requests",
262
+ headers: { "Retry-After": "0" },
263
+ });
287
264
 
288
- const result = await executeWebSearch(
289
- { query: "test" },
290
- "test-key",
291
- "brave",
292
- );
265
+ const result = await executeWebSearch({ query: "test" }, "brave");
293
266
  expect(result.isError).toBe(true);
294
- expect(result.content).toContain("rate limit");
295
- expect(result.content).toContain("after retries");
267
+ expect(result.content).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
268
+ // DEFAULT_MAX_RETRIES = 3 → the initial attempt plus three retries.
269
+ expect(http.callCount()).toBe(4);
296
270
  });
297
271
 
298
272
  test("respects Retry-After header on 429", async () => {
299
- let callCount = 0;
300
- const callTimestamps: number[] = [];
301
- globalThis.fetch = (async () => {
302
- callCount++;
303
- callTimestamps.push(Date.now());
304
- if (callCount === 1) {
305
- return new Response("Too Many Requests", {
306
- status: 429,
307
- headers: { "Retry-After": "0" },
308
- });
309
- }
310
- return new Response(JSON.stringify({ web: { results: [] } }), {
311
- status: 200,
312
- headers: { "Content-Type": "application/json" },
313
- });
314
- }) as unknown as typeof fetch;
273
+ const http = mockHttp();
274
+ http.on("api.search.brave.com", [
275
+ {
276
+ status: 429,
277
+ body: "Too Many Requests",
278
+ headers: { "Retry-After": "0" },
279
+ },
280
+ { status: 200, body: { web: { results: [] } } },
281
+ ]);
315
282
 
316
- const result = await executeWebSearch(
317
- { query: "test" },
318
- "test-key",
319
- "brave",
320
- );
283
+ const result = await executeWebSearch({ query: "test" }, "brave");
321
284
  expect(result.isError).toBe(false);
322
- expect(callCount).toBe(2);
285
+ expect(http.callCount()).toBe(2);
323
286
  });
324
287
 
325
- test("handles network errors", async () => {
326
- globalThis.fetch = (async () => {
327
- throw new Error("Network unreachable");
328
- }) as unknown as typeof fetch;
288
+ test("surfaces the friendly backend copy on network errors", async () => {
289
+ const http = mockHttp();
290
+ http.on("api.search.brave.com", new TypeError("fetch failed"));
329
291
 
330
- const result = await executeWebSearch(
331
- { query: "test" },
332
- "test-key",
333
- "brave",
334
- );
292
+ const result = await executeWebSearch({ query: "test" }, "brave");
335
293
  expect(result.isError).toBe(true);
336
- expect(result.content).toContain("Network unreachable");
294
+ expect(result.content).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
295
+ expect(http.callCount()).toBe(1);
337
296
  });
338
297
 
339
298
  test("sends correct headers", async () => {
340
- let capturedHeaders: Record<string, string> = {};
341
- globalThis.fetch = (async (_url: string, init: RequestInit) => {
342
- capturedHeaders = Object.fromEntries(
343
- Object.entries(init.headers as Record<string, string>),
344
- );
345
- return new Response(JSON.stringify({ web: { results: [] } }), {
346
- status: 200,
347
- headers: { "Content-Type": "application/json" },
348
- });
349
- }) as unknown as typeof fetch;
299
+ const http = mockHttp();
300
+ http.on("api.search.brave.com", {
301
+ status: 200,
302
+ body: { web: { results: [] } },
303
+ });
350
304
 
351
- await executeWebSearch({ query: "test" }, "my-api-key", "brave");
352
- expect(capturedHeaders["X-Subscription-Token"]).toBe("my-api-key");
353
- expect(capturedHeaders["Accept"]).toBe("application/json");
305
+ await executeWebSearch({ query: "test" }, "brave");
306
+ const req = http.lastCall();
307
+ expect(req?.headers["X-Subscription-Token"]).toBe("brave-key");
308
+ expect(req?.headers["Accept"]).toBe("application/json");
354
309
  });
355
310
 
356
311
  test("includes extra_snippets in output", async () => {
357
- const mockResults = {
358
- web: {
359
- results: [
360
- {
361
- title: "Snippet Test",
362
- url: "https://example.com",
363
- description: "Main description",
364
- extra_snippets: ["Extra snippet 1", "Extra snippet 2"],
365
- },
366
- ],
312
+ const http = mockHttp();
313
+ http.on("api.search.brave.com", {
314
+ status: 200,
315
+ body: {
316
+ web: {
317
+ results: [
318
+ {
319
+ title: "Snippet Test",
320
+ url: "https://example.com",
321
+ description: "Main description",
322
+ extra_snippets: ["Extra snippet 1", "Extra snippet 2"],
323
+ },
324
+ ],
325
+ },
367
326
  },
368
- };
369
-
370
- globalThis.fetch = (async () =>
371
- new Response(JSON.stringify(mockResults), {
372
- status: 200,
373
- headers: { "Content-Type": "application/json" },
374
- })) as unknown as typeof fetch;
327
+ });
375
328
 
376
- const result = await executeWebSearch(
377
- { query: "test" },
378
- "test-key",
379
- "brave",
380
- );
329
+ const result = await executeWebSearch({ query: "test" }, "brave");
381
330
  expect(result.isError).toBe(false);
382
331
  expect(result.content).toContain("Extra snippet 1");
383
332
  expect(result.content).toContain("Extra snippet 2");
@@ -386,31 +335,25 @@ describe("WebSearchTool", () => {
386
335
 
387
336
  describe("Perplexity API responses", () => {
388
337
  test("formats results with citations", async () => {
389
- const mockResponse = {
390
- choices: [
391
- {
392
- message: {
393
- content: "Perplexity found this information about the topic.",
338
+ const http = mockHttp();
339
+ http.on("api.perplexity.ai", {
340
+ status: 200,
341
+ body: {
342
+ choices: [
343
+ {
344
+ message: {
345
+ content: "Perplexity found this information about the topic.",
346
+ },
394
347
  },
395
- },
396
- ],
397
- citations: [
398
- "https://example.com/source1",
399
- "https://example.com/source2",
400
- ],
401
- };
402
-
403
- globalThis.fetch = (async () =>
404
- new Response(JSON.stringify(mockResponse), {
405
- status: 200,
406
- headers: { "Content-Type": "application/json" },
407
- })) as unknown as typeof fetch;
348
+ ],
349
+ citations: [
350
+ "https://example.com/source1",
351
+ "https://example.com/source2",
352
+ ],
353
+ },
354
+ });
408
355
 
409
- const result = await executeWebSearch(
410
- { query: "test" },
411
- "pplx-key",
412
- "perplexity",
413
- );
356
+ const result = await executeWebSearch({ query: "test" }, "perplexity");
414
357
  expect(result.isError).toBe(false);
415
358
  expect(result.content).toContain("Perplexity found this information");
416
359
  expect(result.content).toContain("https://example.com/source1");
@@ -418,19 +361,14 @@ describe("WebSearchTool", () => {
418
361
  });
419
362
 
420
363
  test("handles empty response", async () => {
421
- const mockResponse = {
422
- choices: [{ message: { content: "" } }],
423
- };
424
-
425
- globalThis.fetch = (async () =>
426
- new Response(JSON.stringify(mockResponse), {
427
- status: 200,
428
- headers: { "Content-Type": "application/json" },
429
- })) as unknown as typeof fetch;
364
+ const http = mockHttp();
365
+ http.on("api.perplexity.ai", {
366
+ status: 200,
367
+ body: { choices: [{ message: { content: "" } }] },
368
+ });
430
369
 
431
370
  const result = await executeWebSearch(
432
371
  { query: "noresults" },
433
- "pplx-key",
434
372
  "perplexity",
435
373
  );
436
374
  expect(result.isError).toBe(false);
@@ -438,112 +376,87 @@ describe("WebSearchTool", () => {
438
376
  });
439
377
 
440
378
  test("handles 401 unauthorized", async () => {
441
- globalThis.fetch = (async () =>
442
- new Response("Unauthorized", {
443
- status: 401,
444
- })) as unknown as typeof fetch;
379
+ const http = mockHttp();
380
+ http.on("api.perplexity.ai", { status: 401, body: "Unauthorized" });
445
381
 
446
- const result = await executeWebSearch(
447
- { query: "test" },
448
- "bad-key",
449
- "perplexity",
450
- );
382
+ const result = await executeWebSearch({ query: "test" }, "perplexity");
451
383
  expect(result.isError).toBe(true);
452
384
  expect(result.content).toContain("Invalid or expired");
453
385
  });
454
386
 
455
- test("sends correct headers", async () => {
456
- let capturedHeaders: Record<string, string> = {};
457
- let capturedBody: Record<string, unknown> = {};
458
- globalThis.fetch = (async (_url: string, init: RequestInit) => {
459
- capturedHeaders = Object.fromEntries(
460
- Object.entries(init.headers as Record<string, string>),
461
- );
462
- capturedBody = JSON.parse(init.body as string);
463
- return new Response(
464
- JSON.stringify({ choices: [{ message: { content: "result" } }] }),
465
- {
466
- status: 200,
467
- headers: { "Content-Type": "application/json" },
468
- },
469
- );
470
- }) as unknown as typeof fetch;
387
+ test("sends correct headers and body", async () => {
388
+ const http = mockHttp();
389
+ http.on("api.perplexity.ai", {
390
+ status: 200,
391
+ body: { choices: [{ message: { content: "result" } }] },
392
+ });
471
393
 
472
- await executeWebSearch(
473
- { query: "test query" },
474
- "pplx-my-key",
475
- "perplexity",
476
- );
477
- expect(capturedHeaders["Authorization"]).toBe("Bearer pplx-my-key");
478
- expect(capturedHeaders["Content-Type"]).toBe("application/json");
479
- expect(capturedBody.model).toBe("sonar");
480
- expect(
481
- (capturedBody.messages as Array<{ content: string }>)[0].content,
482
- ).toBe("test query");
394
+ await executeWebSearch({ query: "test query" }, "perplexity");
395
+ const req = http.lastCall();
396
+ expect(req?.headers["Authorization"]).toBe("Bearer pplx-key");
397
+ expect(req?.headers["Content-Type"]).toBe("application/json");
398
+ const body = req?.body as {
399
+ model: string;
400
+ messages: Array<{ content: string }>;
401
+ };
402
+ expect(body.model).toBe("sonar");
403
+ expect(body.messages[0]?.content).toBe("test query");
483
404
  });
484
405
 
485
406
  test("retries on 429 and succeeds", async () => {
486
- let callCount = 0;
487
- globalThis.fetch = (async () => {
488
- callCount++;
489
- if (callCount <= 2) {
490
- return new Response("Too Many Requests", { status: 429 });
491
- }
492
- return new Response(
493
- JSON.stringify({ choices: [{ message: { content: "Found it" } }] }),
494
- { status: 200, headers: { "Content-Type": "application/json" } },
495
- );
496
- }) as unknown as typeof fetch;
407
+ const http = mockHttp();
408
+ http.on("api.perplexity.ai", [
409
+ {
410
+ status: 429,
411
+ body: "Too Many Requests",
412
+ headers: { "Retry-After": "0" },
413
+ },
414
+ {
415
+ status: 429,
416
+ body: "Too Many Requests",
417
+ headers: { "Retry-After": "0" },
418
+ },
419
+ {
420
+ status: 200,
421
+ body: { choices: [{ message: { content: "Found it" } }] },
422
+ },
423
+ ]);
497
424
 
498
- const result = await executeWebSearch(
499
- { query: "test" },
500
- "pplx-key",
501
- "perplexity",
502
- );
425
+ const result = await executeWebSearch({ query: "test" }, "perplexity");
503
426
  expect(result.isError).toBe(false);
504
427
  expect(result.content).toContain("Found it");
505
- expect(callCount).toBe(3);
428
+ expect(http.callCount()).toBe(3);
506
429
  });
507
430
 
508
- test("handles network errors", async () => {
509
- globalThis.fetch = (async () => {
510
- throw new Error("Network unreachable");
511
- }) as unknown as typeof fetch;
431
+ test("surfaces the friendly backend copy on network errors", async () => {
432
+ const http = mockHttp();
433
+ http.on("api.perplexity.ai", new TypeError("fetch failed"));
512
434
 
513
- const result = await executeWebSearch(
514
- { query: "test" },
515
- "pplx-key",
516
- "perplexity",
517
- );
435
+ const result = await executeWebSearch({ query: "test" }, "perplexity");
518
436
  expect(result.isError).toBe(true);
519
- expect(result.content).toContain("Network unreachable");
437
+ expect(result.content).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
438
+ expect(http.callCount()).toBe(1);
520
439
  });
521
440
  });
522
441
 
523
442
  describe("Tavily API responses", () => {
524
443
  test("formats results", async () => {
525
- const mockResponse = {
526
- results: [
527
- {
528
- title: "Tavily Result",
529
- url: "https://example.com/tavily",
530
- content: "A Tavily search snippet.",
531
- score: 0.9234,
532
- },
533
- ],
534
- };
535
-
536
- globalThis.fetch = (async () =>
537
- new Response(JSON.stringify(mockResponse), {
538
- status: 200,
539
- headers: { "Content-Type": "application/json" },
540
- })) as unknown as typeof fetch;
444
+ const http = mockHttp();
445
+ http.on("api.tavily.com", {
446
+ status: 200,
447
+ body: {
448
+ results: [
449
+ {
450
+ title: "Tavily Result",
451
+ url: "https://example.com/tavily",
452
+ content: "A Tavily search snippet.",
453
+ score: 0.9234,
454
+ },
455
+ ],
456
+ },
457
+ });
541
458
 
542
- const result = await executeWebSearch(
543
- { query: "test" },
544
- "tvly-key",
545
- "tavily",
546
- );
459
+ const result = await executeWebSearch({ query: "test" }, "tavily");
547
460
  expect(result.isError).toBe(false);
548
461
  expect(result.content).toContain("Tavily Result");
549
462
  expect(result.content).toContain("https://example.com/tavily");
@@ -552,90 +465,69 @@ describe("WebSearchTool", () => {
552
465
  });
553
466
 
554
467
  test("handles empty response", async () => {
555
- globalThis.fetch = (async () =>
556
- new Response(JSON.stringify({ results: [] }), {
557
- status: 200,
558
- headers: { "Content-Type": "application/json" },
559
- })) as unknown as typeof fetch;
468
+ const http = mockHttp();
469
+ http.on("api.tavily.com", { status: 200, body: { results: [] } });
560
470
 
561
- const result = await executeWebSearch(
562
- { query: "noresults" },
563
- "tvly-key",
564
- "tavily",
565
- );
471
+ const result = await executeWebSearch({ query: "noresults" }, "tavily");
566
472
  expect(result.isError).toBe(false);
567
473
  expect(result.content).toContain("No results found");
568
474
  });
569
475
 
570
- test("maps count and freshness into Tavily request body", async () => {
571
- let capturedHeaders: Record<string, string> = {};
572
- let capturedBody: Record<string, unknown> = {};
573
- globalThis.fetch = (async (_url: string, init: RequestInit) => {
574
- capturedHeaders = Object.fromEntries(
575
- Object.entries(init.headers as Record<string, string>),
576
- );
577
- capturedBody = JSON.parse(init.body as string);
578
- return new Response(JSON.stringify({ results: [] }), {
579
- status: 200,
580
- headers: { "Content-Type": "application/json" },
581
- });
582
- }) as unknown as typeof fetch;
476
+ test("maps count and freshness into the Tavily request body", async () => {
477
+ const http = mockHttp();
478
+ http.on("api.tavily.com", { status: 200, body: { results: [] } });
583
479
 
584
480
  await executeWebSearch(
585
481
  { query: "test query", count: 50, freshness: "pw" },
586
- "tvly-my-key",
587
482
  "tavily",
588
483
  );
589
- expect(capturedHeaders.Authorization).toBe("Bearer tvly-my-key");
590
- expect(capturedHeaders["Content-Type"]).toBe("application/json");
591
- expect(capturedBody.query).toBe("test query");
592
- expect(capturedBody.search_depth).toBe("advanced");
593
- expect(capturedBody.max_results).toBe(20);
594
- expect(capturedBody.time_range).toBe("week");
484
+ const req = http.lastCall();
485
+ expect(req?.headers.Authorization).toBe("Bearer tvly-key");
486
+ expect(req?.headers["Content-Type"]).toBe("application/json");
487
+ const body = req?.body as Record<string, unknown>;
488
+ expect(body.query).toBe("test query");
489
+ expect(body.search_depth).toBe("advanced");
490
+ expect(body.max_results).toBe(20);
491
+ expect(body.time_range).toBe("week");
595
492
  });
596
493
 
597
494
  test("omits invalid freshness values", async () => {
598
- let capturedBody: Record<string, unknown> = {};
599
- globalThis.fetch = (async (_url: string, init: RequestInit) => {
600
- capturedBody = JSON.parse(init.body as string);
601
- return new Response(JSON.stringify({ results: [] }), {
602
- status: 200,
603
- headers: { "Content-Type": "application/json" },
604
- });
605
- }) as unknown as typeof fetch;
495
+ const http = mockHttp();
496
+ http.on("api.tavily.com", { status: 200, body: { results: [] } });
606
497
 
607
498
  await executeWebSearch(
608
499
  { query: "test query", freshness: "invalid" },
609
- "tvly-my-key",
610
500
  "tavily",
611
501
  );
612
- expect(capturedBody.time_range).toBeUndefined();
502
+ const body = http.lastCall()?.body as Record<string, unknown>;
503
+ expect(body.time_range).toBeUndefined();
613
504
  });
614
505
 
615
506
  test("handles 401 unauthorized", async () => {
616
- globalThis.fetch = (async () =>
617
- new Response("Unauthorized", {
618
- status: 401,
619
- })) as unknown as typeof fetch;
507
+ const http = mockHttp();
508
+ http.on("api.tavily.com", { status: 401, body: "Unauthorized" });
620
509
 
621
- const result = await executeWebSearch(
622
- { query: "test" },
623
- "bad-key",
624
- "tavily",
625
- );
510
+ const result = await executeWebSearch({ query: "test" }, "tavily");
626
511
  expect(result.isError).toBe(true);
627
512
  expect(result.content).toContain("Invalid or expired");
628
513
  });
629
514
 
630
515
  test("retries on 429 and succeeds", async () => {
631
- let callCount = 0;
632
- globalThis.fetch = (async () => {
633
- callCount++;
634
- if (callCount <= 2) {
635
- return new Response("Too Many Requests", { status: 429 });
636
- }
637
- return new Response(
638
- JSON.stringify({
516
+ const http = mockHttp();
517
+ http.on("api.tavily.com", [
518
+ {
519
+ status: 429,
520
+ body: "Too Many Requests",
521
+ headers: { "Retry-After": "0" },
522
+ },
523
+ {
524
+ status: 429,
525
+ body: "Too Many Requests",
526
+ headers: { "Retry-After": "0" },
527
+ },
528
+ {
529
+ status: 200,
530
+ body: {
639
531
  results: [
640
532
  {
641
533
  title: "Found it",
@@ -643,435 +535,24 @@ describe("WebSearchTool", () => {
643
535
  content: "Found it with Tavily",
644
536
  },
645
537
  ],
646
- }),
647
- { status: 200, headers: { "Content-Type": "application/json" } },
648
- );
649
- }) as unknown as typeof fetch;
538
+ },
539
+ },
540
+ ]);
650
541
 
651
- const result = await executeWebSearch(
652
- { query: "test" },
653
- "tvly-key",
654
- "tavily",
655
- );
542
+ const result = await executeWebSearch({ query: "test" }, "tavily");
656
543
  expect(result.isError).toBe(false);
657
544
  expect(result.content).toContain("Found it with Tavily");
658
- expect(callCount).toBe(3);
545
+ expect(http.callCount()).toBe(3);
659
546
  });
660
547
 
661
- test("handles network errors", async () => {
662
- globalThis.fetch = (async () => {
663
- throw new Error("Network unreachable");
664
- }) as unknown as typeof fetch;
548
+ test("surfaces the friendly backend copy on network errors", async () => {
549
+ const http = mockHttp();
550
+ http.on("api.tavily.com", new TypeError("fetch failed"));
665
551
 
666
- const result = await executeWebSearch(
667
- { query: "test" },
668
- "tvly-key",
669
- "tavily",
670
- );
552
+ const result = await executeWebSearch({ query: "test" }, "tavily");
671
553
  expect(result.isError).toBe(true);
672
- expect(result.content).toContain("Network unreachable");
554
+ expect(result.content).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
555
+ expect(http.callCount()).toBe(1);
673
556
  });
674
557
  });
675
558
  });
676
-
677
- interface BraveSearchResult {
678
- title: string;
679
- url: string;
680
- description?: string;
681
- age?: string;
682
- extra_snippets?: string[];
683
- }
684
-
685
- interface BraveSearchResponse {
686
- web?: { results: BraveSearchResult[] };
687
- }
688
-
689
- interface PerplexityResponse {
690
- choices?: Array<{ message: { content: string } }>;
691
- citations?: string[];
692
- }
693
-
694
- interface TavilySearchResult {
695
- title?: string;
696
- url?: string;
697
- content?: string;
698
- score?: number;
699
- }
700
-
701
- interface TavilySearchResponse {
702
- results?: TavilySearchResult[];
703
- }
704
-
705
- /**
706
- * Helper that exercises the web search logic directly, bypassing module
707
- * registration concerns. This replicates the core execute path from
708
- * web-search.ts to test it in isolation.
709
- */
710
- async function executeWebSearch(
711
- input: Record<string, unknown>,
712
- apiKey?: string,
713
- provider: "brave" | "perplexity" | "tavily" = "brave",
714
- ): Promise<{ content: string; isError: boolean }> {
715
- const query = input.query;
716
- if (!query || typeof query !== "string") {
717
- return {
718
- content: "Error: query is required and must be a string",
719
- isError: true,
720
- };
721
- }
722
-
723
- if (!apiKey) {
724
- return {
725
- content:
726
- "Error: No web search API key configured. Set it via `keys set perplexity <key>`, `keys set brave <key>`, or `keys set tavily <key>`, or configure it from the Settings page under API Keys.",
727
- isError: true,
728
- };
729
- }
730
-
731
- if (provider === "perplexity") {
732
- return executePerplexitySearchHelper(query as string, apiKey);
733
- }
734
-
735
- if (provider === "tavily") {
736
- return executeTavilySearchHelper(input, query as string, apiKey);
737
- }
738
-
739
- return executeBraveSearchHelper(input, query as string, apiKey);
740
- }
741
-
742
- async function executeBraveSearchHelper(
743
- input: Record<string, unknown>,
744
- query: string,
745
- apiKey: string,
746
- ): Promise<{ content: string; isError: boolean }> {
747
- const count =
748
- typeof input.count === "number"
749
- ? Math.min(20, Math.max(1, Math.round(input.count)))
750
- : 10;
751
- const offset =
752
- typeof input.offset === "number"
753
- ? Math.min(9, Math.max(0, Math.round(input.offset)))
754
- : 0;
755
-
756
- const params = new URLSearchParams({
757
- q: query,
758
- count: String(count),
759
- offset: String(offset),
760
- });
761
-
762
- const validFreshness = ["pd", "pw", "pm", "py"];
763
- if (
764
- typeof input.freshness === "string" &&
765
- validFreshness.includes(input.freshness)
766
- ) {
767
- params.set("freshness", input.freshness);
768
- }
769
-
770
- const url = `https://api.search.brave.com/res/v1/web/search?${params.toString()}`;
771
-
772
- const MAX_RETRIES = 3;
773
- const BASE_DELAY_MS = 1; // Use 1ms in tests to avoid slow tests
774
-
775
- try {
776
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
777
- const response = await fetch(url, {
778
- headers: {
779
- Accept: "application/json",
780
- "Accept-Encoding": "gzip",
781
- "X-Subscription-Token": apiKey,
782
- },
783
- });
784
-
785
- if (response.ok) {
786
- const data = (await response.json()) as BraveSearchResponse;
787
- const results = data.web?.results ?? [];
788
-
789
- if (results.length === 0) {
790
- return {
791
- content: `No results found for "${query}".`,
792
- isError: false,
793
- };
794
- }
795
-
796
- const lines: string[] = [`Web search results for "${query}":\n`];
797
- for (let i = 0; i < results.length; i++) {
798
- const r = results[i];
799
- lines.push(`${i + 1}. ${r.title}`);
800
- lines.push(` URL: ${r.url}`);
801
- if (r.description) {
802
- lines.push(` ${r.description}`);
803
- }
804
- if (r.age) {
805
- lines.push(` Age: ${r.age}`);
806
- }
807
- if (r.extra_snippets && r.extra_snippets.length > 0) {
808
- for (const snippet of r.extra_snippets) {
809
- lines.push(` > ${snippet}`);
810
- }
811
- }
812
- lines.push("");
813
- }
814
-
815
- return { content: lines.join("\n"), isError: false };
816
- }
817
-
818
- await response.text();
819
-
820
- if (response.status === 401 || response.status === 403) {
821
- return {
822
- content: "Error: Invalid or expired Brave Search API key",
823
- isError: true,
824
- };
825
- }
826
-
827
- if (response.status === 429 && attempt < MAX_RETRIES) {
828
- const retryAfter = response.headers.get("retry-after");
829
- const delayMs =
830
- retryAfter && !isNaN(Number(retryAfter))
831
- ? Number(retryAfter) * 1000
832
- : BASE_DELAY_MS * Math.pow(2, attempt);
833
- await new Promise((resolve) => setTimeout(resolve, delayMs));
834
- continue;
835
- }
836
-
837
- if (response.status === 429) {
838
- return {
839
- content:
840
- "Error: Brave Search rate limit exceeded after retries. Try again shortly.",
841
- isError: true,
842
- };
843
- }
844
- return {
845
- content: `Error: Brave Search API returned status ${response.status}`,
846
- isError: true,
847
- };
848
- }
849
-
850
- return {
851
- content:
852
- "Error: Brave Search rate limit exceeded after retries. Try again shortly.",
853
- isError: true,
854
- };
855
- } catch (err) {
856
- const msg = err instanceof Error ? err.message : String(err);
857
- return { content: `Error: Web search failed: ${msg}`, isError: true };
858
- }
859
- }
860
-
861
- async function executePerplexitySearchHelper(
862
- query: string,
863
- apiKey: string,
864
- ): Promise<{ content: string; isError: boolean }> {
865
- const MAX_RETRIES = 3;
866
- const BASE_DELAY_MS = 1;
867
-
868
- try {
869
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
870
- const response = await fetch(
871
- "https://api.perplexity.ai/chat/completions",
872
- {
873
- method: "POST",
874
- headers: {
875
- "Content-Type": "application/json",
876
- Authorization: `Bearer ${apiKey}`,
877
- },
878
- body: JSON.stringify({
879
- model: "sonar",
880
- messages: [{ role: "user", content: query }],
881
- }),
882
- },
883
- );
884
-
885
- if (response.ok) {
886
- const data = (await response.json()) as PerplexityResponse;
887
- const content = data.choices?.[0]?.message?.content;
888
- if (!content) {
889
- return {
890
- content: `No results found for "${query}".`,
891
- isError: false,
892
- };
893
- }
894
-
895
- const lines: string[] = [`Web search results for "${query}":\n`];
896
- lines.push(content);
897
-
898
- if (data.citations && data.citations.length > 0) {
899
- lines.push("\nSources:");
900
- for (let i = 0; i < data.citations.length; i++) {
901
- lines.push(` [${i + 1}] ${data.citations[i]}`);
902
- }
903
- }
904
-
905
- return { content: lines.join("\n"), isError: false };
906
- }
907
-
908
- await response.text();
909
-
910
- if (response.status === 401 || response.status === 403) {
911
- return {
912
- content: "Error: Invalid or expired Perplexity API key",
913
- isError: true,
914
- };
915
- }
916
-
917
- if (response.status === 429 && attempt < MAX_RETRIES) {
918
- const retryAfter = response.headers.get("retry-after");
919
- const delayMs =
920
- retryAfter && !isNaN(Number(retryAfter))
921
- ? Number(retryAfter) * 1000
922
- : BASE_DELAY_MS * Math.pow(2, attempt);
923
- await new Promise((resolve) => setTimeout(resolve, delayMs));
924
- continue;
925
- }
926
-
927
- if (response.status === 429) {
928
- return {
929
- content:
930
- "Error: Perplexity rate limit exceeded after retries. Try again shortly.",
931
- isError: true,
932
- };
933
- }
934
- return {
935
- content: `Error: Perplexity API returned status ${response.status}`,
936
- isError: true,
937
- };
938
- }
939
-
940
- return {
941
- content:
942
- "Error: Perplexity rate limit exceeded after retries. Try again shortly.",
943
- isError: true,
944
- };
945
- } catch (err) {
946
- const msg = err instanceof Error ? err.message : String(err);
947
- return { content: `Error: Web search failed: ${msg}`, isError: true };
948
- }
949
- }
950
-
951
- function tavilyTimeRangeForFreshness(
952
- freshness: string | undefined,
953
- ): "day" | "week" | "month" | "year" | undefined {
954
- switch (freshness) {
955
- case "pd":
956
- return "day";
957
- case "pw":
958
- return "week";
959
- case "pm":
960
- return "month";
961
- case "py":
962
- return "year";
963
- default:
964
- return undefined;
965
- }
966
- }
967
-
968
- async function executeTavilySearchHelper(
969
- input: Record<string, unknown>,
970
- query: string,
971
- apiKey: string,
972
- ): Promise<{ content: string; isError: boolean }> {
973
- const count =
974
- typeof input.count === "number"
975
- ? Math.min(20, Math.max(1, Math.round(input.count)))
976
- : 10;
977
- const timeRange = tavilyTimeRangeForFreshness(
978
- typeof input.freshness === "string" ? input.freshness : undefined,
979
- );
980
- const body: Record<string, unknown> = {
981
- query,
982
- search_depth: "advanced",
983
- max_results: count,
984
- };
985
- if (timeRange) {
986
- body.time_range = timeRange;
987
- }
988
-
989
- const MAX_RETRIES = 3;
990
- const BASE_DELAY_MS = 1;
991
-
992
- try {
993
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
994
- const response = await fetch("https://api.tavily.com/search", {
995
- method: "POST",
996
- headers: {
997
- "Content-Type": "application/json",
998
- Authorization: `Bearer ${apiKey}`,
999
- "X-Client-Name": "mycadet",
1000
- },
1001
- body: JSON.stringify(body),
1002
- });
1003
-
1004
- if (response.ok) {
1005
- const data = (await response.json()) as TavilySearchResponse;
1006
- const results = data.results ?? [];
1007
-
1008
- if (results.length === 0) {
1009
- return {
1010
- content: `No results found for "${query}".`,
1011
- isError: false,
1012
- };
1013
- }
1014
-
1015
- const lines: string[] = [`Web search results for "${query}":\n`];
1016
-
1017
- for (let i = 0; i < results.length; i++) {
1018
- const r = results[i];
1019
- const title = r.title?.trim() || r.url?.trim() || "Untitled result";
1020
- lines.push(`${i + 1}. ${title}`);
1021
- if (r.url) {
1022
- lines.push(` URL: ${r.url}`);
1023
- }
1024
- if (r.content) {
1025
- lines.push(` ${r.content}`);
1026
- }
1027
- if (typeof r.score === "number") {
1028
- lines.push(` Score: ${r.score.toFixed(3)}`);
1029
- }
1030
- lines.push("");
1031
- }
1032
-
1033
- return { content: lines.join("\n"), isError: false };
1034
- }
1035
-
1036
- await response.text();
1037
-
1038
- if (response.status === 401 || response.status === 403) {
1039
- return {
1040
- content: "Error: Invalid or expired Tavily API key",
1041
- isError: true,
1042
- };
1043
- }
1044
-
1045
- if (response.status === 429 && attempt < MAX_RETRIES) {
1046
- const retryAfter = response.headers.get("retry-after");
1047
- const delayMs =
1048
- retryAfter && !isNaN(Number(retryAfter))
1049
- ? Number(retryAfter) * 1000
1050
- : BASE_DELAY_MS * Math.pow(2, attempt);
1051
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1052
- continue;
1053
- }
1054
-
1055
- if (response.status === 429) {
1056
- return {
1057
- content:
1058
- "Error: Tavily Search rate limit exceeded after retries. Try again shortly.",
1059
- isError: true,
1060
- };
1061
- }
1062
- return {
1063
- content: `Error: Tavily Search API returned status ${response.status}`,
1064
- isError: true,
1065
- };
1066
- }
1067
-
1068
- return {
1069
- content:
1070
- "Error: Tavily Search rate limit exceeded after retries. Try again shortly.",
1071
- isError: true,
1072
- };
1073
- } catch (err) {
1074
- const msg = err instanceof Error ? err.message : String(err);
1075
- return { content: `Error: Web search failed: ${msg}`, isError: true };
1076
- }
1077
- }