@vellumai/assistant 0.8.0 → 0.8.2

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 (991) hide show
  1. package/AGENTS.md +11 -0
  2. package/ARCHITECTURE.md +2 -7
  3. package/Dockerfile +80 -5
  4. package/README.md +2 -2
  5. package/bun.lock +11 -1
  6. package/docker-entrypoint.sh +21 -0
  7. package/docker-init-apt-root.sh +94 -0
  8. package/docker-kata-apt-env.sh +39 -0
  9. package/docs/plugins.md +88 -47
  10. package/docs/skills.md +9 -7
  11. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  12. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  13. package/eslint.config.mjs +12 -0
  14. package/examples/plugins/echo/README.md +27 -27
  15. package/examples/plugins/echo/package.json +3 -0
  16. package/examples/plugins/echo/register.ts +31 -31
  17. package/knip.json +2 -1
  18. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  19. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  20. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  21. package/openapi.yaml +4462 -991
  22. package/package.json +5 -1
  23. package/scripts/generate-openapi.ts +135 -14
  24. package/scripts/sync-llm-catalog.ts +165 -0
  25. package/scripts/sync-web-search-catalog.ts +129 -0
  26. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  27. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  28. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  29. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  30. package/src/__tests__/anthropic-provider.test.ts +137 -2
  31. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  32. package/src/__tests__/app-control-flow.test.ts +7 -0
  33. package/src/__tests__/app-executors.test.ts +220 -4
  34. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  35. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  36. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  37. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  38. package/src/__tests__/btw-routes.test.ts +1 -0
  39. package/src/__tests__/bundled-asset.test.ts +6 -6
  40. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  41. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  42. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  43. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  44. package/src/__tests__/channel-policy.test.ts +12 -0
  45. package/src/__tests__/checker.test.ts +89 -0
  46. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  47. package/src/__tests__/clawhub.test.ts +75 -16
  48. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  49. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  50. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  51. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  52. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  53. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  54. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  55. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  56. package/src/__tests__/config-schema.test.ts +35 -3
  57. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  58. package/src/__tests__/config-set-route.test.ts +278 -0
  59. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  60. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  61. package/src/__tests__/config-watcher.test.ts +6 -0
  62. package/src/__tests__/contacts-tools.test.ts +51 -199
  63. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  64. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  65. package/src/__tests__/context-search-conversations-source.test.ts +159 -18
  66. package/src/__tests__/context-search-fanout.test.ts +20 -157
  67. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -4
  68. package/src/__tests__/context-search-types.test.ts +7 -2
  69. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  70. package/src/__tests__/context-token-estimator.test.ts +1 -0
  71. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  72. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  73. package/src/__tests__/conversation-agent-loop-overflow.test.ts +93 -92
  74. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  75. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  76. package/src/__tests__/conversation-error.test.ts +80 -3
  77. package/src/__tests__/conversation-fork-crud.test.ts +323 -1
  78. package/src/__tests__/conversation-inference-profile-route.test.ts +54 -18
  79. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  80. package/src/__tests__/conversation-lifecycle.test.ts +297 -0
  81. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  82. package/src/__tests__/conversation-pairing.test.ts +54 -0
  83. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  84. package/src/__tests__/conversation-process-callsite.test.ts +25 -2
  85. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  86. package/src/__tests__/conversation-queue.test.ts +4 -1
  87. package/src/__tests__/conversation-runtime-assembly.test.ts +80 -13
  88. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  89. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  90. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  91. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  92. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  93. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  94. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  95. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  96. package/src/__tests__/credential-security-invariants.test.ts +8 -8
  97. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  98. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  99. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  100. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  101. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  102. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  103. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  104. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  105. package/src/__tests__/dm-backfill.test.ts +121 -10
  106. package/src/__tests__/document-tool-security.test.ts +258 -0
  107. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  108. package/src/__tests__/edit-propagation.test.ts +33 -0
  109. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  110. package/src/__tests__/external-plugin-loader.test.ts +482 -0
  111. package/src/__tests__/filing-service.test.ts +163 -3
  112. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  113. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  114. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  115. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  116. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +42 -69
  117. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  118. package/src/__tests__/heartbeat-service.test.ts +50 -233
  119. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  120. package/src/__tests__/helpers/wait-for.ts +21 -0
  121. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  122. package/src/__tests__/history-repair.test.ts +162 -0
  123. package/src/__tests__/host-app-control-proxy.test.ts +365 -1
  124. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  125. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  126. package/src/__tests__/host-browser-routes.test.ts +325 -33
  127. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  128. package/src/__tests__/image-credentials.test.ts +1 -1
  129. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  130. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  131. package/src/__tests__/inference-profile-reaper.test.ts +156 -0
  132. package/src/__tests__/inference-profile-session-handler.test.ts +410 -0
  133. package/src/__tests__/inference-profile-session-ipc.test.ts +248 -0
  134. package/src/__tests__/injector-chain.test.ts +10 -8
  135. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  136. package/src/__tests__/install-skill-routing.test.ts +157 -39
  137. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +107 -3
  138. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  139. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  140. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  141. package/src/__tests__/llm-catalog-parity.test.ts +190 -2
  142. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +222 -0
  143. package/src/__tests__/llm-request-log-source-factory.test.ts +100 -0
  144. package/src/__tests__/llm-resolver.test.ts +46 -0
  145. package/src/__tests__/llm-usage-store.test.ts +114 -0
  146. package/src/__tests__/managed-profile-guard.test.ts +145 -14
  147. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  148. package/src/__tests__/managed-store.test.ts +84 -192
  149. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  150. package/src/__tests__/mcp-cli.test.ts +182 -220
  151. package/src/__tests__/mcp-health-check.test.ts +56 -27
  152. package/src/__tests__/media-generate-image.test.ts +1 -1
  153. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  154. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  155. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  156. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  157. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  158. package/src/__tests__/oauth-cli.test.ts +38 -2009
  159. package/src/__tests__/oauth-commands-routes.test.ts +863 -0
  160. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  161. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  162. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  163. package/src/__tests__/openai-provider.test.ts +24 -0
  164. package/src/__tests__/openai-responses-cutover-guard.test.ts +48 -19
  165. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  166. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  167. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  168. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  169. package/src/__tests__/platform.test.ts +2 -0
  170. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  171. package/src/__tests__/plugin-bootstrap.test.ts +41 -38
  172. package/src/__tests__/plugin-external-api.test.ts +68 -0
  173. package/src/__tests__/plugin-registry.test.ts +0 -77
  174. package/src/__tests__/plugin-route-contribution.test.ts +31 -4
  175. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  176. package/src/__tests__/plugin-tool-contribution.test.ts +47 -18
  177. package/src/__tests__/plugin-types.test.ts +15 -23
  178. package/src/__tests__/process-message-background-slack.test.ts +53 -0
  179. package/src/__tests__/process-message-display-content.test.ts +421 -0
  180. package/src/__tests__/profile-entry-status.test.ts +43 -0
  181. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  182. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  183. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +20 -12
  184. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  185. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  186. package/src/__tests__/relay-server.test.ts +118 -0
  187. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  188. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  189. package/src/__tests__/schedule-retry.test.ts +56 -4
  190. package/src/__tests__/schedule-routes.test.ts +151 -0
  191. package/src/__tests__/schedule-store.test.ts +94 -0
  192. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  193. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  194. package/src/__tests__/scheduler-reuse-conversation.test.ts +208 -5
  195. package/src/__tests__/scheduler-wake.test.ts +0 -63
  196. package/src/__tests__/schema-transforms.test.ts +20 -0
  197. package/src/__tests__/search-skills-unified.test.ts +0 -5
  198. package/src/__tests__/secret-allowlist.test.ts +1 -0
  199. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +12 -4
  200. package/src/__tests__/server-history-render.test.ts +43 -0
  201. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  202. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  203. package/src/__tests__/skill-load-feature-flag.test.ts +1 -12
  204. package/src/__tests__/skill-load-tool.test.ts +29 -93
  205. package/src/__tests__/skill-memory.test.ts +23 -3
  206. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  207. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  208. package/src/__tests__/skills-install-extract.test.ts +49 -38
  209. package/src/__tests__/skills-install-staging.test.ts +159 -0
  210. package/src/__tests__/skills-uninstall.test.ts +9 -41
  211. package/src/__tests__/skills.test.ts +51 -58
  212. package/src/__tests__/slack-channel-config.test.ts +9 -0
  213. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  214. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  215. package/src/__tests__/suggestion-routes.test.ts +3 -3
  216. package/src/__tests__/sync-message-contract.test.ts +63 -0
  217. package/src/__tests__/system-prompt.test.ts +737 -63
  218. package/src/__tests__/task-scheduler.test.ts +88 -23
  219. package/src/__tests__/terminal-tools.test.ts +28 -1
  220. package/src/__tests__/thread-backfill.test.ts +557 -27
  221. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  222. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  223. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  224. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  225. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  226. package/src/__tests__/tool-executor.test.ts +16 -4
  227. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  228. package/src/__tests__/turn-events-store.test.ts +256 -0
  229. package/src/__tests__/twilio-routes.test.ts +4 -0
  230. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  231. package/src/__tests__/usage-cli.test.ts +11 -73
  232. package/src/__tests__/user-plugin-loader.test.ts +143 -5
  233. package/src/__tests__/vercel-config.test.ts +168 -0
  234. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  235. package/src/__tests__/web-search-catalog-parity.test.ts +108 -0
  236. package/src/__tests__/web-search.test.ts +303 -2
  237. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  238. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +170 -0
  239. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  240. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +241 -0
  241. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  242. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  243. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  244. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  245. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  246. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  247. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  248. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  249. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  250. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  251. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  252. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  253. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  254. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  255. package/src/acp/resolve-agent.test.ts +25 -0
  256. package/src/acp/resolve-agent.ts +13 -2
  257. package/src/acp/session-manager.ts +14 -0
  258. package/src/agent/image-optimize.ts +13 -5
  259. package/src/approvals/guardian-request-resolvers.ts +32 -87
  260. package/src/calls/relay-server.ts +35 -0
  261. package/src/calls/relay-setup-router.ts +36 -0
  262. package/src/calls/types.ts +1 -0
  263. package/src/calls/voice-session-bridge.ts +74 -36
  264. package/src/channels/config.ts +14 -1
  265. package/src/channels/types.ts +109 -0
  266. package/src/cli/AGENTS.md +164 -4
  267. package/src/cli/__tests__/notifications.test.ts +54 -0
  268. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  269. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  270. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  271. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  272. package/src/cli/commands/__tests__/changelog.test.ts +578 -0
  273. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  274. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  275. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  276. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  277. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  278. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  279. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  280. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  281. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  282. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  283. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  284. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  285. package/src/cli/commands/__tests__/status.test.ts +249 -0
  286. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  287. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  288. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  289. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  290. package/src/cli/commands/attachment.ts +8 -3
  291. package/src/cli/commands/audit.ts +95 -64
  292. package/src/cli/commands/auth.ts +61 -58
  293. package/src/cli/commands/avatar.ts +276 -390
  294. package/src/cli/commands/backup.ts +409 -505
  295. package/src/cli/commands/bash.ts +9 -5
  296. package/src/cli/commands/browser.ts +28 -9
  297. package/src/cli/commands/cache.ts +9 -4
  298. package/src/cli/commands/changelog.ts +478 -0
  299. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  300. package/src/cli/commands/clients.ts +8 -3
  301. package/src/cli/commands/completions.ts +9 -9
  302. package/src/cli/commands/config.ts +102 -72
  303. package/src/cli/commands/contacts.ts +575 -696
  304. package/src/cli/commands/conversations-defer.ts +17 -69
  305. package/src/cli/commands/conversations-import.ts +90 -253
  306. package/src/cli/commands/conversations.ts +429 -434
  307. package/src/cli/commands/credential-execution.ts +9 -6
  308. package/src/cli/commands/credentials.ts +456 -736
  309. package/src/cli/commands/default-action.ts +10 -53
  310. package/src/cli/commands/domain.ts +128 -206
  311. package/src/cli/commands/email.ts +606 -794
  312. package/src/cli/commands/gateway.ts +8 -1
  313. package/src/cli/commands/image-generation.ts +157 -205
  314. package/src/cli/commands/inference-providers.ts +352 -0
  315. package/src/cli/commands/inference-session.ts +415 -0
  316. package/src/cli/commands/inference.ts +87 -65
  317. package/src/cli/commands/keys.ts +8 -3
  318. package/src/cli/commands/mcp.ts +103 -287
  319. package/src/cli/commands/memory-v2.ts +162 -516
  320. package/src/cli/commands/notifications.ts +342 -304
  321. package/src/cli/commands/oauth/apps.ts +292 -261
  322. package/src/cli/commands/oauth/connect.ts +176 -297
  323. package/src/cli/commands/oauth/disconnect.ts +16 -215
  324. package/src/cli/commands/oauth/index.ts +49 -45
  325. package/src/cli/commands/oauth/mode.ts +43 -199
  326. package/src/cli/commands/oauth/ping.ts +17 -125
  327. package/src/cli/commands/oauth/providers.ts +732 -921
  328. package/src/cli/commands/oauth/request.ts +60 -350
  329. package/src/cli/commands/oauth/shared.ts +11 -121
  330. package/src/cli/commands/oauth/status.ts +31 -121
  331. package/src/cli/commands/oauth/token.ts +13 -55
  332. package/src/cli/commands/pending.ts +19 -10
  333. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  334. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  335. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  336. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  337. package/src/cli/commands/platform/connect.ts +16 -80
  338. package/src/cli/commands/platform/disconnect.ts +14 -112
  339. package/src/cli/commands/platform/index.ts +177 -246
  340. package/src/cli/commands/plugins.ts +185 -0
  341. package/src/cli/commands/routes.ts +153 -336
  342. package/src/cli/commands/schedules.ts +391 -0
  343. package/src/cli/commands/sequence.ts +316 -360
  344. package/src/cli/commands/skills.ts +449 -671
  345. package/src/cli/commands/status.ts +58 -37
  346. package/src/cli/commands/stt.ts +94 -262
  347. package/src/cli/commands/task.ts +14 -40
  348. package/src/cli/commands/telemetry.ts +40 -0
  349. package/src/cli/commands/trust.ts +8 -3
  350. package/src/cli/commands/tts.ts +162 -167
  351. package/src/cli/commands/ui.ts +35 -42
  352. package/src/cli/commands/usage.ts +188 -126
  353. package/src/cli/commands/watchers.ts +8 -3
  354. package/src/cli/commands/webhooks.ts +99 -193
  355. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  356. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  357. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  358. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  359. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  360. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  361. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  362. package/src/cli/lib/cli-colors.ts +12 -0
  363. package/src/cli/lib/confirm-prompt.ts +79 -0
  364. package/src/cli/lib/daemon-credential-client.ts +4 -5
  365. package/src/cli/lib/install-from-github.ts +304 -0
  366. package/src/cli/lib/list-installed-plugins.ts +137 -0
  367. package/src/cli/lib/nested-value.ts +44 -0
  368. package/src/cli/lib/open-browser.ts +36 -0
  369. package/src/cli/lib/register-command.ts +19 -0
  370. package/src/cli/lib/time-ago.ts +34 -0
  371. package/src/cli/lib/uninstall-plugin.ts +82 -0
  372. package/src/cli/lib/unknown-command.ts +111 -0
  373. package/src/cli/program.ts +40 -6
  374. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  375. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  376. package/src/cli/utils/conversation-id.ts +30 -0
  377. package/src/cli/utils/parse-duration.ts +41 -0
  378. package/src/config/acp-defaults.test.ts +5 -1
  379. package/src/config/acp-defaults.ts +11 -4
  380. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  381. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  382. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  383. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  384. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  385. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  386. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  387. package/src/config/bundled-skills/document/SKILL.md +23 -3
  388. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  389. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  390. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  391. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  392. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  393. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  394. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  395. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  396. package/src/config/bundled-tool-registry.ts +6 -2
  397. package/src/config/feature-flag-registry.json +57 -1
  398. package/src/config/llm-resolver.ts +16 -1
  399. package/src/config/loader.ts +140 -52
  400. package/src/config/raw-config-utils.ts +2 -30
  401. package/src/config/schema.ts +8 -7
  402. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  403. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  404. package/src/config/schemas/call-site-catalog.ts +29 -7
  405. package/src/config/schemas/channels.ts +8 -0
  406. package/src/config/schemas/compaction.ts +28 -0
  407. package/src/config/schemas/heartbeat.ts +9 -0
  408. package/src/config/schemas/llm-request-logs.ts +81 -0
  409. package/src/config/schemas/llm.ts +55 -2
  410. package/src/config/schemas/memory-retrieval.ts +18 -0
  411. package/src/config/schemas/memory-retrospective.ts +48 -0
  412. package/src/config/schemas/memory-v2.ts +32 -1
  413. package/src/config/schemas/memory.ts +4 -0
  414. package/src/config/schemas/services.ts +15 -12
  415. package/src/config/schemas/tools.ts +14 -0
  416. package/src/config/seed-inference-profiles.ts +195 -134
  417. package/src/config/skills.ts +3 -96
  418. package/src/contacts/contact-store.ts +0 -61
  419. package/src/context/compactor.ts +1047 -0
  420. package/src/context/token-estimator.ts +2 -2
  421. package/src/context/window-manager.ts +197 -1334
  422. package/src/credential-execution/managed-catalog.ts +37 -0
  423. package/src/credential-health/credential-health-service.ts +280 -19
  424. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +113 -0
  425. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  426. package/src/daemon/__tests__/conversation-tool-setup.test.ts +183 -4
  427. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  428. package/src/daemon/approval-generators.ts +26 -30
  429. package/src/daemon/config-watcher.ts +94 -29
  430. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  431. package/src/daemon/conversation-agent-loop.ts +293 -103
  432. package/src/daemon/conversation-error.ts +188 -33
  433. package/src/daemon/conversation-lifecycle.ts +80 -26
  434. package/src/daemon/conversation-messaging.ts +25 -6
  435. package/src/daemon/conversation-process.ts +85 -31
  436. package/src/daemon/conversation-runtime-assembly.ts +30 -6
  437. package/src/daemon/conversation-slash.ts +184 -25
  438. package/src/daemon/conversation-store.ts +24 -10
  439. package/src/daemon/conversation-surfaces.ts +76 -12
  440. package/src/daemon/conversation-tool-setup.ts +63 -21
  441. package/src/daemon/conversation.ts +81 -10
  442. package/src/daemon/external-plugins-bootstrap.ts +231 -185
  443. package/src/daemon/first-greeting.ts +22 -2
  444. package/src/daemon/guardian-action-generators.ts +7 -22
  445. package/src/daemon/handlers/config-model.ts +13 -130
  446. package/src/daemon/handlers/config-slack-channel.ts +25 -10
  447. package/src/daemon/handlers/config-vercel.ts +3 -1
  448. package/src/daemon/handlers/shared.ts +14 -5
  449. package/src/daemon/handlers/skills.ts +166 -84
  450. package/src/daemon/history-repair.ts +61 -7
  451. package/src/daemon/host-app-control-proxy.ts +129 -29
  452. package/src/daemon/host-bash-proxy.ts +85 -158
  453. package/src/daemon/host-browser-proxy.ts +96 -35
  454. package/src/daemon/host-proxy-base.ts +13 -1
  455. package/src/daemon/host-proxy-preactivation.ts +25 -1
  456. package/src/daemon/identity-helpers.ts +19 -0
  457. package/src/daemon/lifecycle.ts +79 -70
  458. package/src/daemon/meet-host-supervisor.ts +20 -19
  459. package/src/daemon/memory-v2-startup.ts +58 -2
  460. package/src/daemon/message-protocol.ts +7 -0
  461. package/src/daemon/message-types/bookmarks.ts +18 -0
  462. package/src/daemon/message-types/conversations.ts +37 -9
  463. package/src/daemon/message-types/messages.ts +70 -1
  464. package/src/daemon/message-types/subagents.ts +1 -0
  465. package/src/daemon/message-types/sync.ts +61 -0
  466. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  467. package/src/daemon/pkb-reminder-builder.ts +21 -7
  468. package/src/daemon/plugin-source-watcher.ts +146 -0
  469. package/src/daemon/process-message.ts +77 -26
  470. package/src/daemon/server.ts +34 -20
  471. package/src/daemon/shutdown-handlers.ts +0 -2
  472. package/src/daemon/skill-memory-refresh.ts +29 -0
  473. package/src/daemon/tool-setup-types.ts +9 -0
  474. package/src/daemon/tool-side-effects.ts +6 -4
  475. package/src/daemon/wake-target-adapter.ts +11 -0
  476. package/src/documents/document-store.ts +221 -3
  477. package/src/embedded/plugin-api.ts +40 -0
  478. package/src/export/transcript-formatter.ts +61 -2
  479. package/src/filing/filing-service.ts +79 -53
  480. package/src/heartbeat/__tests__/heartbeat-service.test.ts +444 -0
  481. package/src/heartbeat/heartbeat-run-store.ts +3 -1
  482. package/src/heartbeat/heartbeat-service.ts +189 -127
  483. package/src/home/__tests__/feed-types.test.ts +99 -127
  484. package/src/home/__tests__/feed-writer.test.ts +77 -278
  485. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  486. package/src/home/feed-types.ts +41 -73
  487. package/src/home/feed-writer.ts +25 -156
  488. package/src/home/post-connect-feed.ts +2 -3
  489. package/src/index.ts +18 -1
  490. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  491. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  492. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  493. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  494. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  495. package/src/ipc/assistant-server.ts +55 -6
  496. package/src/ipc/cli-client.ts +370 -50
  497. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  498. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  499. package/src/ipc/skill-routes/events.ts +30 -3
  500. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  501. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  502. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  503. package/src/live-voice/live-voice-session-manager.ts +11 -4
  504. package/src/live-voice/live-voice-session.ts +14 -6
  505. package/src/mcp/client.ts +20 -4
  506. package/src/media/image-credentials.ts +3 -3
  507. package/src/memory/__tests__/bookmark-crud.test.ts +264 -0
  508. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  509. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  510. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  511. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  512. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  513. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  514. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  515. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +318 -0
  516. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  517. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  518. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  519. package/src/memory/__tests__/message-content.test.ts +35 -0
  520. package/src/memory/bookmark-crud.ts +211 -0
  521. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  522. package/src/memory/context-search/agent-protocol.ts +5 -1
  523. package/src/memory/context-search/agent-runner.ts +60 -85
  524. package/src/memory/context-search/limits.ts +1 -4
  525. package/src/memory/context-search/search.ts +23 -113
  526. package/src/memory/context-search/sources/conversations.ts +80 -8
  527. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  528. package/src/memory/context-search/sources/memory.ts +7 -0
  529. package/src/memory/context-search/sources/workspace.ts +17 -10
  530. package/src/memory/context-search/types.ts +1 -1
  531. package/src/memory/conversation-bootstrap.ts +11 -0
  532. package/src/memory/conversation-crud.ts +368 -22
  533. package/src/memory/conversation-queries.ts +116 -12
  534. package/src/memory/conversation-title-service.ts +1 -0
  535. package/src/memory/conversation-types.ts +16 -0
  536. package/src/memory/db-init.ts +20 -0
  537. package/src/memory/delivery-crud.ts +152 -5
  538. package/src/memory/embedding-backend.ts +6 -5
  539. package/src/memory/embedding-runtime-manager.ts +1 -2
  540. package/src/memory/external-conversation-store.ts +66 -5
  541. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  542. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  543. package/src/memory/graph/conversation-graph-memory.ts +92 -5
  544. package/src/memory/graph/extraction.ts +4 -0
  545. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  546. package/src/memory/graph/tool-handlers.ts +17 -7
  547. package/src/memory/graph/tools.ts +45 -6
  548. package/src/memory/indexer.ts +51 -29
  549. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +86 -15
  550. package/src/memory/jobs/embed-concept-page.ts +65 -20
  551. package/src/memory/jobs-store.ts +51 -1
  552. package/src/memory/jobs-worker.ts +57 -3
  553. package/src/memory/llm-request-log-source-clickhouse.ts +324 -0
  554. package/src/memory/llm-request-log-source-local.ts +26 -0
  555. package/src/memory/llm-request-log-source.ts +64 -0
  556. package/src/memory/llm-request-log-store.ts +1 -1
  557. package/src/memory/llm-usage-store.ts +125 -5
  558. package/src/memory/memory-retrospective-constants.ts +13 -0
  559. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  560. package/src/memory/memory-retrospective-job.ts +351 -0
  561. package/src/memory/memory-retrospective-startup-cleanup.ts +175 -0
  562. package/src/memory/memory-retrospective-state.ts +162 -0
  563. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  564. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  565. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  566. package/src/memory/message-content.ts +38 -1
  567. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  568. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  569. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  570. package/src/memory/migrations/229-delete-private-conversations.test.ts +107 -1
  571. package/src/memory/migrations/229-delete-private-conversations.ts +19 -0
  572. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  573. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  574. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  575. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  576. package/src/memory/migrations/243-provider-connections.ts +68 -0
  577. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  578. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  579. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  580. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  581. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  582. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  583. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  584. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  585. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  586. package/src/memory/migrations/index.ts +13 -0
  587. package/src/memory/migrations/registry.ts +8 -0
  588. package/src/memory/onboarding-events-store.ts +106 -0
  589. package/src/memory/published-pages-store.ts +16 -0
  590. package/src/memory/schema/bookmarks.ts +36 -0
  591. package/src/memory/schema/calls.ts +1 -0
  592. package/src/memory/schema/conversations.ts +2 -0
  593. package/src/memory/schema/index.ts +2 -0
  594. package/src/memory/schema/inference.ts +27 -0
  595. package/src/memory/schema/infrastructure.ts +12 -0
  596. package/src/memory/schema/memory-core.ts +9 -0
  597. package/src/memory/search/semantic.ts +1 -4
  598. package/src/memory/turn-events-store.ts +127 -2
  599. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  600. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  601. package/src/memory/v2/__tests__/activation.test.ts +11 -12
  602. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  603. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  604. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  605. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  606. package/src/memory/v2/__tests__/injection.test.ts +726 -18
  607. package/src/memory/v2/__tests__/migration.test.ts +94 -3
  608. package/src/memory/v2/__tests__/page-index.test.ts +360 -0
  609. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  610. package/src/memory/v2/__tests__/prompts-router.test.ts +309 -0
  611. package/src/memory/v2/__tests__/qdrant.test.ts +138 -3
  612. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  613. package/src/memory/v2/__tests__/router.test.ts +531 -0
  614. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  615. package/src/memory/v2/__tests__/skill-store.test.ts +445 -11
  616. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  617. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  618. package/src/memory/v2/activation-store.ts +34 -5
  619. package/src/memory/v2/activation.ts +40 -27
  620. package/src/memory/v2/backfill-jobs.ts +17 -84
  621. package/src/memory/v2/consolidation-job.ts +85 -78
  622. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  623. package/src/memory/v2/injection.ts +466 -109
  624. package/src/memory/v2/migration.ts +147 -20
  625. package/src/memory/v2/page-index.ts +221 -0
  626. package/src/memory/v2/page-store.ts +3 -0
  627. package/src/memory/v2/prompts/consolidation.ts +9 -7
  628. package/src/memory/v2/prompts/router.ts +195 -0
  629. package/src/memory/v2/prompts/sweep.ts +2 -2
  630. package/src/memory/v2/qdrant.ts +234 -93
  631. package/src/memory/v2/reranker.ts +14 -7
  632. package/src/memory/v2/router.ts +323 -0
  633. package/src/memory/v2/sim.ts +25 -12
  634. package/src/memory/v2/skill-store.ts +204 -30
  635. package/src/memory/v2/static-context.ts +16 -9
  636. package/src/memory/v2/sweep-job.ts +122 -96
  637. package/src/memory/v2/types.ts +10 -6
  638. package/src/memory/validation.ts +13 -0
  639. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  640. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  641. package/src/messaging/providers/slack/adapter.ts +43 -5
  642. package/src/messaging/providers/slack/client.ts +27 -0
  643. package/src/messaging/providers/slack/deep-link.ts +65 -0
  644. package/src/messaging/providers/slack/download.ts +104 -0
  645. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  646. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  647. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  648. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  649. package/src/messaging/providers/slack/types.ts +20 -1
  650. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  651. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  652. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  653. package/src/notifications/adapters/platform.ts +171 -0
  654. package/src/notifications/conversation-pairing.ts +4 -3
  655. package/src/notifications/copy-composer.ts +15 -0
  656. package/src/notifications/decision-engine.ts +2 -1
  657. package/src/notifications/destination-resolver.ts +21 -0
  658. package/src/notifications/emit-signal.ts +48 -2
  659. package/src/notifications/home-feed-side-effect.ts +165 -0
  660. package/src/notifications/signal.ts +8 -1
  661. package/src/oauth/connection-resolver.ts +8 -4
  662. package/src/oauth/platform-connection.ts +6 -2
  663. package/src/oauth/seed-providers.ts +10 -1
  664. package/src/permissions/checker.ts +14 -0
  665. package/src/permissions/ipc-risk-types.ts +3 -0
  666. package/src/permissions/question-prompter.test.ts +416 -0
  667. package/src/permissions/question-prompter.ts +294 -0
  668. package/src/platform/client.test.ts +1 -1
  669. package/src/platform/client.ts +1 -1
  670. package/src/plugin-api/constants.ts +26 -0
  671. package/src/plugin-api/index.ts +46 -0
  672. package/src/plugin-api/package.json +12 -0
  673. package/src/plugin-api/types.ts +144 -0
  674. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  675. package/src/plugins/defaults/compaction.ts +0 -4
  676. package/src/plugins/defaults/empty-response.ts +0 -2
  677. package/src/plugins/defaults/history-repair.ts +0 -2
  678. package/src/plugins/defaults/injectors.ts +55 -6
  679. package/src/plugins/defaults/llm-call.ts +0 -2
  680. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  681. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  682. package/src/plugins/defaults/persistence.ts +0 -2
  683. package/src/plugins/defaults/title-generate.ts +0 -5
  684. package/src/plugins/defaults/token-estimate.ts +0 -2
  685. package/src/plugins/defaults/tool-error.ts +0 -7
  686. package/src/plugins/defaults/tool-execute.ts +0 -2
  687. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  688. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  689. package/src/plugins/external-api.ts +104 -0
  690. package/src/plugins/external-plugin-loader.ts +367 -0
  691. package/src/plugins/feature-gate.ts +22 -0
  692. package/src/plugins/pipeline.ts +37 -0
  693. package/src/plugins/registry.ts +48 -80
  694. package/src/plugins/types.ts +74 -53
  695. package/src/plugins/user-loader.ts +85 -43
  696. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  697. package/src/proactive-artifact/job.test.ts +49 -9
  698. package/src/proactive-artifact/job.ts +4 -0
  699. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  700. package/src/proactive-artifact/trigger-state.ts +4 -0
  701. package/src/prompts/__tests__/system-prompt.test.ts +117 -0
  702. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  703. package/src/prompts/normalize-onboarding.ts +27 -0
  704. package/src/prompts/sections.ts +302 -0
  705. package/src/prompts/system-prompt.ts +72 -154
  706. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  707. package/src/prompts/templates/system-sections.ts +173 -0
  708. package/src/prompts/update-bulletin-job.ts +61 -73
  709. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  710. package/src/providers/__tests__/inference.test.ts +303 -0
  711. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  712. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  713. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  714. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  715. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  716. package/src/providers/anthropic/client.ts +123 -54
  717. package/src/providers/call-site-routing.ts +94 -16
  718. package/src/providers/connection-resolution.ts +170 -0
  719. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  720. package/src/providers/inference/adapter-factory.ts +210 -0
  721. package/src/providers/inference/auth.ts +112 -0
  722. package/src/providers/inference/backfill.ts +196 -0
  723. package/src/providers/inference/connections.ts +401 -0
  724. package/src/providers/inference/resolve-auth.ts +73 -0
  725. package/src/providers/model-catalog.ts +386 -6
  726. package/src/providers/openai/chat-completions-provider.ts +10 -2
  727. package/src/providers/openai/responses-provider.ts +4 -2
  728. package/src/providers/openrouter/client.ts +7 -0
  729. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  730. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  731. package/src/providers/provider-availability.ts +17 -2
  732. package/src/providers/provider-catalog-visibility.ts +36 -0
  733. package/src/providers/provider-env-vars.ts +17 -7
  734. package/src/providers/provider-secret-catalog.ts +49 -30
  735. package/src/providers/provider-send-message.ts +41 -20
  736. package/src/providers/registry.ts +151 -159
  737. package/src/providers/retry.ts +65 -11
  738. package/src/providers/search-provider-catalog.ts +121 -0
  739. package/src/runtime/AGENTS.md +18 -5
  740. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  741. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  742. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  743. package/src/runtime/actor-trust-resolver.ts +32 -10
  744. package/src/runtime/agent-wake.ts +64 -7
  745. package/src/runtime/assistant-event-hub.ts +3 -85
  746. package/src/runtime/auth/route-policy.ts +311 -9
  747. package/src/runtime/auth/same-actor.ts +2 -0
  748. package/src/runtime/background-job-runner.ts +339 -0
  749. package/src/runtime/btw-sidechain.ts +3 -0
  750. package/src/runtime/http-router.ts +36 -1
  751. package/src/runtime/http-server.ts +31 -5
  752. package/src/runtime/http-types.ts +21 -0
  753. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  754. package/src/runtime/middleware/request-logger.ts +62 -1
  755. package/src/runtime/migrations/origin-mode.ts +1 -1
  756. package/src/runtime/pending-interactions.ts +1 -0
  757. package/src/runtime/pre-first-message-gate.ts +83 -0
  758. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  759. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +268 -0
  760. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  761. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +319 -0
  762. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +280 -4
  763. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  764. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  765. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  766. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  767. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  768. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  769. package/src/runtime/routes/__tests__/tts-routes.test.ts +70 -3
  770. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  771. package/src/runtime/routes/acp-routes.ts +12 -8
  772. package/src/runtime/routes/app-management-routes.ts +228 -3
  773. package/src/runtime/routes/approval-routes.ts +0 -18
  774. package/src/runtime/routes/audit-routes.ts +43 -0
  775. package/src/runtime/routes/auth-routes.ts +72 -0
  776. package/src/runtime/routes/avatar-routes.ts +273 -20
  777. package/src/runtime/routes/backup-routes.ts +406 -2
  778. package/src/runtime/routes/bookmark-routes.ts +156 -0
  779. package/src/runtime/routes/btw-routes.ts +5 -1
  780. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  781. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  782. package/src/runtime/routes/contact-routes.ts +0 -160
  783. package/src/runtime/routes/conversation-cli-routes.ts +233 -0
  784. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  785. package/src/runtime/routes/conversation-management-routes.ts +47 -85
  786. package/src/runtime/routes/conversation-query-routes.ts +350 -97
  787. package/src/runtime/routes/conversation-routes.ts +121 -21
  788. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  789. package/src/runtime/routes/credential-routes.ts +540 -0
  790. package/src/runtime/routes/debug-routes.ts +2 -2
  791. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  792. package/src/runtime/routes/documents-routes.ts +25 -86
  793. package/src/runtime/routes/domain-routes.ts +167 -0
  794. package/src/runtime/routes/email-routes.ts +603 -0
  795. package/src/runtime/routes/errors.ts +2 -2
  796. package/src/runtime/routes/events-routes.ts +192 -0
  797. package/src/runtime/routes/group-routes.ts +5 -0
  798. package/src/runtime/routes/home-feed-routes.ts +6 -78
  799. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  800. package/src/runtime/routes/host-browser-routes.ts +103 -22
  801. package/src/runtime/routes/http-adapter.ts +2 -0
  802. package/src/runtime/routes/identity-routes.ts +5 -0
  803. package/src/runtime/routes/image-generation-routes.ts +99 -0
  804. package/src/runtime/routes/inbound-conversation.ts +28 -8
  805. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  806. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +248 -1
  807. package/src/runtime/routes/inbound-stages/background-dispatch.ts +118 -7
  808. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  809. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  810. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  811. package/src/runtime/routes/index.ts +42 -0
  812. package/src/runtime/routes/inference-profile-session-handler.ts +285 -0
  813. package/src/runtime/routes/inference-profile-session-reaper.ts +84 -0
  814. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  815. package/src/runtime/routes/inference-provider-connection-routes.ts +361 -0
  816. package/src/runtime/routes/inference-send-routes.ts +115 -0
  817. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  818. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  819. package/src/runtime/routes/integrations/twilio.ts +7 -13
  820. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  821. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  822. package/src/runtime/routes/notification-routes.ts +3 -1
  823. package/src/runtime/routes/oauth-apps.ts +112 -7
  824. package/src/runtime/routes/oauth-commands-routes.ts +1097 -0
  825. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  826. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  827. package/src/runtime/routes/oauth-providers.ts +298 -8
  828. package/src/runtime/routes/platform-routes.ts +336 -0
  829. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  830. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  831. package/src/runtime/routes/playground/state.ts +2 -1
  832. package/src/runtime/routes/publish-routes.ts +221 -0
  833. package/src/runtime/routes/question-routes.ts +259 -0
  834. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  835. package/src/runtime/routes/schedule-routes.ts +79 -0
  836. package/src/runtime/routes/sequence-routes.ts +291 -0
  837. package/src/runtime/routes/settings-routes.ts +2 -10
  838. package/src/runtime/routes/skills-routes.ts +31 -1
  839. package/src/runtime/routes/stt-routes.ts +240 -3
  840. package/src/runtime/routes/subagents-routes.ts +57 -18
  841. package/src/runtime/routes/surface-action-routes.ts +43 -7
  842. package/src/runtime/routes/telemetry-routes.ts +27 -0
  843. package/src/runtime/routes/tts-routes.ts +93 -1
  844. package/src/runtime/routes/types.ts +32 -0
  845. package/src/runtime/routes/user-routes-cli.ts +243 -0
  846. package/src/runtime/routes/webhook-routes.ts +165 -0
  847. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  848. package/src/runtime/routes/workspace-routes.ts +28 -0
  849. package/src/runtime/services/conversation-serializer.ts +39 -7
  850. package/src/runtime/sync/resource-sync-events.ts +117 -0
  851. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  852. package/src/runtime/sync/sync-publisher.ts +21 -0
  853. package/src/schedule/schedule-store.ts +27 -2
  854. package/src/schedule/scheduler.ts +208 -123
  855. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  856. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  857. package/src/security/secret-patterns.ts +3 -0
  858. package/src/security/untrusted-content.ts +93 -8
  859. package/src/sequence/engine.ts +38 -40
  860. package/src/skills/catalog-files.ts +1 -1
  861. package/src/skills/catalog-install.ts +233 -116
  862. package/src/skills/clawhub.ts +70 -13
  863. package/src/skills/managed-store.ts +4 -119
  864. package/src/skills/skillssh-registry.ts +27 -48
  865. package/src/subagent/manager.ts +28 -15
  866. package/src/telemetry/types.ts +113 -1
  867. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  868. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  869. package/src/tools/apps/executors.ts +58 -7
  870. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  871. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  872. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  873. package/src/tools/browser/browser-execution.ts +29 -14
  874. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  875. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  876. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  877. package/src/tools/browser/cdp-client/factory.ts +66 -5
  878. package/src/tools/browser/runtime-check.ts +77 -0
  879. package/src/tools/computer-use/definitions.ts +3 -3
  880. package/src/tools/credentials/vault.ts +1 -1
  881. package/src/tools/document/document-tool.ts +124 -1
  882. package/src/tools/filesystem/edit.ts +1 -1
  883. package/src/tools/filesystem/list.ts +1 -1
  884. package/src/tools/filesystem/read.ts +1 -1
  885. package/src/tools/filesystem/write.ts +5 -2
  886. package/src/tools/host-filesystem/transfer.ts +1 -1
  887. package/src/tools/host-terminal/host-shell.ts +1 -1
  888. package/src/tools/memory/register.test.ts +3 -3
  889. package/src/tools/memory/register.ts +9 -1
  890. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  891. package/src/tools/network/web-search.ts +280 -37
  892. package/src/tools/permission-checker.ts +14 -6
  893. package/src/tools/registry.ts +17 -7
  894. package/src/tools/schedule/create.ts +2 -2
  895. package/src/tools/schema-transforms.ts +7 -2
  896. package/src/tools/side-effects.ts +1 -0
  897. package/src/tools/skills/delete-managed.ts +4 -4
  898. package/src/tools/skills/execute.ts +1 -1
  899. package/src/tools/skills/scaffold-managed.ts +3 -2
  900. package/src/tools/subagent/notify-parent.ts +1 -1
  901. package/src/tools/subagent/spawn.ts +3 -3
  902. package/src/tools/system/request-permission.ts +2 -2
  903. package/src/tools/terminal/safe-env.ts +60 -1
  904. package/src/tools/terminal/shell.ts +44 -0
  905. package/src/tools/tool-manifest.ts +2 -0
  906. package/src/tools/types.ts +72 -21
  907. package/src/tools/ui-surface/definitions.ts +6 -5
  908. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  909. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  910. package/src/types/onboarding-context.ts +2 -0
  911. package/src/usage/attribution.ts +3 -2
  912. package/src/util/errors.ts +17 -0
  913. package/src/util/platform.ts +10 -0
  914. package/src/util/pricing.ts +86 -160
  915. package/src/watcher/__tests__/engine.test.ts +323 -0
  916. package/src/watcher/constants.ts +7 -0
  917. package/src/watcher/engine.ts +94 -90
  918. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  919. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  920. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +94 -5
  921. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  922. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +117 -0
  923. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +95 -0
  924. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  925. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  926. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  927. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  928. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  929. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  930. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  931. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  932. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  933. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  934. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  935. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  936. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  937. package/src/workspace/migrations/registry.ts +30 -0
  938. package/src/workspace/migrations/runner.ts +46 -5
  939. package/src/workspace/migrations/types.ts +17 -3
  940. package/src/workspace/provider-commit-message-generator.ts +3 -2
  941. package/examples/plugins/echo/bun.lock +0 -25
  942. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  943. package/src/__tests__/context-window-manager.test.ts +0 -2093
  944. package/src/__tests__/credentials-cli.test.ts +0 -1225
  945. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  946. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  947. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  948. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  949. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  950. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  951. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  952. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  953. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  954. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  955. package/src/cli/commands/autonomy.ts +0 -365
  956. package/src/cli/commands/memory.ts +0 -424
  957. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  958. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  959. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  960. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  961. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  962. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  963. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  964. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  965. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  966. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  967. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  968. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  969. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  970. package/src/context/prompts/compact.md +0 -26
  971. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  972. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  973. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  974. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  975. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  976. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  977. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  978. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  979. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  980. package/src/home/assistant-feed-authoring.ts +0 -135
  981. package/src/home/emit-feed-event.ts +0 -169
  982. package/src/home/feed-scheduler.ts +0 -281
  983. package/src/home/platform-gmail-digest.ts +0 -163
  984. package/src/home/rewrite-command-preview.ts +0 -66
  985. package/src/home/rewrite-feed-title.ts +0 -58
  986. package/src/home/rollup-producer.ts +0 -426
  987. package/src/memory/admin.ts +0 -326
  988. package/src/memory/context-search/sources/pkb.ts +0 -476
  989. package/src/memory/graph/compaction.ts +0 -299
  990. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  991. /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
@@ -1,382 +1,32 @@
1
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
1
+ /**
2
+ * CLI plumbing tests for `assistant/src/cli/commands/oauth/`.
3
+ *
4
+ * The oauth CLI commands are thin wrappers around `cliIpcCall(...)`; daemon
5
+ * route handlers in `runtime/routes/oauth-commands-routes.ts` execute the
6
+ * actual work. Tests here focus on argument handling and helpers that live
7
+ * entirely in the CLI process.
8
+ *
9
+ * What lives here today:
10
+ * - `requirePlatformConnection` helper — gates managed-mode operations on
11
+ * a connected platform; used by multiple oauth subcommands.
12
+ *
13
+ * Daemon-side coverage of the IPC endpoints lives in
14
+ * `oauth-commands-routes.test.ts`. Underlying store and token-refresh logic
15
+ * is covered by `oauth-store.test.ts` and `credential-vault.test.ts`.
16
+ *
17
+ * Follow-up opportunities for CLI-layer coverage:
18
+ * - `exitFromIpcResult` exit-code mapping
19
+ * - `shouldOutputJson` / `writeOutput` output formatting
20
+ * - `oauth token` shell-lockdown guard (`VELLUM_UNTRUSTED_SHELL=1`)
21
+ * - per-subcommand argument parsing & help text
22
+ */
23
+
24
+ import { describe, expect, mock, test } from "bun:test";
2
25
 
3
26
  import { Command } from "commander";
4
27
 
5
- // ---------------------------------------------------------------------------
6
- // Mock state
7
- // ---------------------------------------------------------------------------
8
-
9
- let mockWithValidToken: <T>(
10
- service: string,
11
- cb: (token: string) => Promise<T>,
12
- ) => Promise<T>;
13
-
14
- let mockListProviders: () => Array<Record<string, unknown>> = () => [];
15
- const secureKeyStore = new Map<string, string>();
16
- const metadataStore: Array<{
17
- credentialId: string;
18
- service: string;
19
- field: string;
20
- allowedTools: string[];
21
- allowedDomains: string[];
22
- createdAt: number;
23
- updatedAt: number;
24
- }> = [];
25
- const disconnectOAuthProviderCalls: string[] = [];
26
- const disconnectOAuthProviderResult: "disconnected" | "not-found" | "error" =
27
- "not-found";
28
-
29
- // In-memory provider store used by registerProvider/updateProvider/getProvider
30
- // mocks below. Tests that exercise the providers register/update/get commands
31
- // can read and write through this map directly.
32
- const mockProviderStore = new Map<string, Record<string, unknown>>();
33
-
34
- // App upsert mock state
35
- let mockUpsertAppCalls: Array<{
36
- provider: string;
37
- clientId: string;
38
- clientSecretOpts?: {
39
- clientSecretValue?: string;
40
- clientSecretCredentialPath?: string;
41
- };
42
- }> = [];
43
- let mockUpsertAppResult: Record<string, unknown> = {
44
- id: "app-upsert-1",
45
- provider: "test",
46
- clientId: "test-client-id",
47
- createdAt: 1700000000000,
48
- updatedAt: 1700000000000,
49
- };
50
- let mockUpsertAppImpl:
51
- | ((
52
- provider: string,
53
- clientId: string,
54
- clientSecretOpts?: {
55
- clientSecretValue?: string;
56
- clientSecretCredentialPath?: string;
57
- },
58
- ) => Promise<Record<string, unknown>>)
59
- | undefined;
60
-
61
- // Transitive mock state (connect-orchestrator, etc.)
62
- let mockOrchestrateOAuthConnect: (
63
- opts: Record<string, unknown>,
64
- ) => Promise<Record<string, unknown>>;
65
- let mockCliIpcCall: (
66
- operationId: string,
67
- params?: Record<string, unknown>,
68
- ) => Promise<Record<string, unknown>> = async () => ({
69
- ok: false,
70
- error: "Could not connect to assistant daemon (test default)",
71
- });
72
- let mockGetAppByProviderAndClientId: (
73
- provider: string,
74
- clientId: string,
75
- ) => Record<string, unknown> | undefined = () => undefined;
76
- let mockGetMostRecentAppByProvider: (
77
- provider: string,
78
- ) => Record<string, unknown> | undefined = () => undefined;
79
- let mockGetProvider: (
80
- provider: string,
81
- ) => Record<string, unknown> | undefined = () => undefined;
82
- let mockGetSecureKey: (account: string) => string | undefined = () => undefined;
83
- let mockResolveOAuthConnection: (
84
- provider: string,
85
- options?: Record<string, unknown>,
86
- ) => Promise<{
87
- request: (req: Record<string, unknown>) => Promise<{
88
- status: number;
89
- headers: Record<string, string>;
90
- body: unknown;
91
- }>;
92
- withToken: <T>(fn: (token: string) => Promise<T>) => Promise<T>;
93
- id: string;
94
- provider: string;
95
- accountInfo: string | null;
96
- }> = async () => {
97
- throw new Error("resolveOAuthConnection not configured in test");
98
- };
99
- let mockGetCredentialMetadata: (
100
- service: string,
101
- field: string,
102
- ) => Record<string, unknown> | undefined = () => undefined;
103
- let mockPlatformClientCreate: () => Promise<Record<
104
- string,
105
- unknown
106
- > | null> = async () => null;
107
-
108
- // ---------------------------------------------------------------------------
109
- // Mock token-manager
110
- // ---------------------------------------------------------------------------
111
-
112
- mock.module("../security/token-manager.js", () => ({
113
- withValidToken: <T>(
114
- service: string,
115
- cb: (token: string) => Promise<T>,
116
- ): Promise<T> => mockWithValidToken(service, cb),
117
- // Stubs for any transitive imports that reference other exports:
118
- TokenExpiredError: class TokenExpiredError extends Error {
119
- constructor(
120
- public readonly service: string,
121
- message?: string,
122
- ) {
123
- super(message ?? `Token expired for "${service}".`);
124
- this.name = "TokenExpiredError";
125
- }
126
- },
127
- }));
128
-
129
- // ---------------------------------------------------------------------------
130
- // Mock oauth-store
131
- // ---------------------------------------------------------------------------
132
-
133
- mock.module("../oauth/oauth-store.js", () => ({
134
- disconnectOAuthProvider: async (
135
- provider: string,
136
- ): Promise<"disconnected" | "not-found" | "error"> => {
137
- disconnectOAuthProviderCalls.push(provider);
138
- return disconnectOAuthProviderResult;
139
- },
140
- getConnection: () => undefined,
141
- getConnectionByProvider: () => undefined,
142
- listConnections: () => [],
143
- deleteConnection: () => false,
144
- // Stubs required by apps.ts and providers.ts (transitively loaded via oauth/index.ts)
145
- upsertApp: async (
146
- provider: string,
147
- clientId: string,
148
- clientSecretOpts?: {
149
- clientSecretValue?: string;
150
- clientSecretCredentialPath?: string;
151
- },
152
- ) => {
153
- if (mockUpsertAppImpl) {
154
- return mockUpsertAppImpl(provider, clientId, clientSecretOpts);
155
- }
156
- mockUpsertAppCalls.push({ provider, clientId, clientSecretOpts });
157
- return mockUpsertAppResult;
158
- },
159
- getApp: () => undefined,
160
- getAppByProviderAndClientId: (provider: string, clientId: string) =>
161
- mockGetAppByProviderAndClientId(provider, clientId),
162
- getMostRecentAppByProvider: (provider: string) =>
163
- mockGetMostRecentAppByProvider(provider),
164
- listApps: () => [],
165
- deleteApp: async () => false,
166
- getProvider: (provider: string) => {
167
- // If the test has plugged in a custom mockGetProvider, prefer that.
168
- const custom = mockGetProvider(provider);
169
- if (custom !== undefined) return custom;
170
- return mockProviderStore.get(provider);
171
- },
172
- listProviders: () => mockListProviders(),
173
- registerProvider: (params: Record<string, unknown>) => {
174
- const now = Date.now();
175
- const row: Record<string, unknown> = {
176
- provider: params.provider,
177
- authorizeUrl: params.authorizeUrl,
178
- tokenExchangeUrl: params.tokenExchangeUrl,
179
- refreshUrl: (params.refreshUrl as string | undefined) ?? null,
180
- tokenEndpointAuthMethod:
181
- (params.tokenEndpointAuthMethod as string | undefined) ||
182
- "client_secret_post",
183
- tokenExchangeBodyFormat:
184
- (params.tokenExchangeBodyFormat as string | undefined) ?? "form",
185
- userinfoUrl: params.userinfoUrl ?? null,
186
- baseUrl: params.baseUrl ?? null,
187
- defaultScopes: JSON.stringify(params.defaultScopes ?? []),
188
- availableScopes: params.availableScopes
189
- ? JSON.stringify(params.availableScopes)
190
- : null,
191
- scopeSeparator: (params.scopeSeparator as string | undefined) ?? " ",
192
- authorizeParams: params.authorizeParams
193
- ? JSON.stringify(params.authorizeParams)
194
- : null,
195
- pingUrl: params.pingUrl ?? null,
196
- pingMethod: params.pingMethod ?? null,
197
- pingHeaders: params.pingHeaders
198
- ? JSON.stringify(params.pingHeaders)
199
- : null,
200
- pingBody:
201
- params.pingBody !== undefined ? JSON.stringify(params.pingBody) : null,
202
- revokeUrl: (params.revokeUrl as string | undefined) ?? null,
203
- revokeBodyTemplate: params.revokeBodyTemplate
204
- ? JSON.stringify(params.revokeBodyTemplate)
205
- : null,
206
- managedServiceConfigKey: params.managedServiceConfigKey ?? null,
207
- displayLabel: params.displayLabel ?? null,
208
- description: params.description ?? null,
209
- dashboardUrl: params.dashboardUrl ?? null,
210
- logoUrl: params.logoUrl ?? null,
211
- clientIdPlaceholder: params.clientIdPlaceholder ?? null,
212
- requiresClientSecret: params.requiresClientSecret ?? 1,
213
- loopbackPort: params.loopbackPort ?? null,
214
- injectionTemplates: params.injectionTemplates
215
- ? JSON.stringify(params.injectionTemplates)
216
- : null,
217
- appType: params.appType ?? null,
218
- setupNotes: params.setupNotes ? JSON.stringify(params.setupNotes) : null,
219
- identityUrl: params.identityUrl ?? null,
220
- identityMethod: params.identityMethod ?? null,
221
- identityHeaders: params.identityHeaders
222
- ? JSON.stringify(params.identityHeaders)
223
- : null,
224
- identityBody:
225
- params.identityBody !== undefined
226
- ? JSON.stringify(params.identityBody)
227
- : null,
228
- identityResponsePaths: params.identityResponsePaths
229
- ? JSON.stringify(params.identityResponsePaths)
230
- : null,
231
- identityFormat: params.identityFormat ?? null,
232
- identityOkField: params.identityOkField ?? null,
233
- featureFlag: params.featureFlag ?? null,
234
- createdAt: now,
235
- updatedAt: now,
236
- };
237
- mockProviderStore.set(params.provider as string, row);
238
- return row;
239
- },
240
- updateProvider: (provider: string, params: Record<string, unknown>) => {
241
- const existing = mockProviderStore.get(provider);
242
- if (!existing) return undefined;
243
- const updated: Record<string, unknown> = { ...existing };
244
- if (params.scopeSeparator !== undefined) {
245
- updated.scopeSeparator = params.scopeSeparator;
246
- }
247
- if (params.authorizeUrl !== undefined) {
248
- updated.authorizeUrl = params.authorizeUrl;
249
- }
250
- if (params.tokenExchangeUrl !== undefined) {
251
- updated.tokenExchangeUrl = params.tokenExchangeUrl;
252
- }
253
- if (params.refreshUrl !== undefined) {
254
- updated.refreshUrl = params.refreshUrl;
255
- }
256
- if (params.revokeUrl !== undefined) {
257
- updated.revokeUrl = params.revokeUrl;
258
- }
259
- if (params.revokeBodyTemplate !== undefined) {
260
- updated.revokeBodyTemplate =
261
- params.revokeBodyTemplate === null
262
- ? null
263
- : JSON.stringify(params.revokeBodyTemplate);
264
- }
265
- if (params.defaultScopes !== undefined) {
266
- updated.defaultScopes = JSON.stringify(params.defaultScopes);
267
- }
268
- if (params.displayLabel !== undefined) {
269
- updated.displayLabel = params.displayLabel;
270
- }
271
- if (params.logoUrl !== undefined) {
272
- updated.logoUrl = params.logoUrl;
273
- }
274
- updated.updatedAt = Date.now();
275
- mockProviderStore.set(provider, updated);
276
- return updated;
277
- },
278
- deleteProvider: () => false,
279
- seedProviders: () => {},
280
- getActiveConnection: () => undefined,
281
- listActiveConnectionsByProvider: () => [],
282
- createConnection: () => ({}),
283
- isProviderConnected: () => false,
284
- updateConnection: () => ({}),
285
- }));
286
-
287
- // Stub out transitive dependencies that token-manager would normally pull in.
288
- // All named exports must be present to avoid SyntaxError when bun's module
289
- // mock is shared across test files in the same run.
290
- mock.module("../security/secure-keys.js", () => ({
291
- getSecureKeyAsync: async (account: string) => mockGetSecureKey(account),
292
- getSecureKeyResultAsync: async (account: string) => ({
293
- value: mockGetSecureKey(account),
294
- unreachable: false,
295
- }),
296
- setSecureKeyAsync: async () => true,
297
- deleteSecureKeyAsync: async (account: string) => {
298
- if (secureKeyStore.has(account)) {
299
- secureKeyStore.delete(account);
300
- return "deleted" as const;
301
- }
302
- return "not-found" as const;
303
- },
304
- bulkSetSecureKeysAsync: async () => true,
305
- getProviderKeyAsync: async () => undefined,
306
- getMaskedProviderKey: async () => undefined,
307
- listSecureKeysAsync: async () => ({
308
- accounts: [...secureKeyStore.keys()],
309
- unreachable: false,
310
- }),
311
- getActiveBackendName: () => "mock",
312
- setCesClient: () => {},
313
- onCesClientChanged: () => () => {},
314
- setCesReconnect: () => {},
315
- _resetBackend: () => {},
316
- }));
317
-
318
- mock.module("../tools/credentials/metadata-store.js", () => ({
319
- assertMetadataWritable: () => {},
320
- getCredentialMetadata: (service: string, field: string) =>
321
- mockGetCredentialMetadata(service, field),
322
- getCredentialMetadataById: () => undefined,
323
- upsertCredentialMetadata: () => ({}),
324
- listCredentialMetadata: () => [],
325
- deleteCredentialMetadata: (service: string, field: string): boolean => {
326
- const idx = metadataStore.findIndex(
327
- (c) => c.service === service && c.field === field,
328
- );
329
- if (idx === -1) return false;
330
- metadataStore.splice(idx, 1);
331
- return true;
332
- },
333
- _setMetadataPath: () => {},
334
- }));
335
-
336
- // ---------------------------------------------------------------------------
337
- // Mock connect-orchestrator
338
- // ---------------------------------------------------------------------------
339
-
340
- mock.module("../oauth/connect-orchestrator.js", () => ({
341
- orchestrateOAuthConnect: (opts: Record<string, unknown>) =>
342
- mockOrchestrateOAuthConnect(opts),
343
- }));
344
-
345
- // ---------------------------------------------------------------------------
346
- // Mock cli-client (IPC) — used by `oauth connect` for daemon-orchestrated flow
347
- // ---------------------------------------------------------------------------
348
-
349
- mock.module("../ipc/cli-client.js", () => ({
350
- cliIpcCall: (operationId: string, params?: Record<string, unknown>) =>
351
- mockCliIpcCall(operationId, params),
352
- }));
353
-
354
- mock.module("../oauth/seed-providers.js", () => ({
355
- SEEDED_PROVIDER_KEYS: new Set([
356
- "google",
357
- "slack",
358
- "github",
359
- "notion",
360
- "twitter",
361
- "linear",
362
- ]),
363
- seedOAuthProviders: () => {},
364
- }));
365
-
366
- // ---------------------------------------------------------------------------
367
- // Mock connection-resolver (needed by request.ts)
368
- // ---------------------------------------------------------------------------
369
-
370
- mock.module("../oauth/connection-resolver.js", () => ({
371
- resolveOAuthConnection: (
372
- provider: string,
373
- options?: Record<string, unknown>,
374
- ) => mockResolveOAuthConnection(provider, options),
375
- }));
376
-
377
- // ---------------------------------------------------------------------------
378
- // Mock platform/client (needed by request.ts)
379
- // ---------------------------------------------------------------------------
28
+ let mockPlatformClientCreate: () => Promise<Record<string, unknown> | null> =
29
+ async () => null;
380
30
 
381
31
  mock.module("../platform/client.js", () => ({
382
32
  VellumPlatformClient: {
@@ -384,18 +34,13 @@ mock.module("../platform/client.js", () => ({
384
34
  },
385
35
  }));
386
36
 
387
- // ---------------------------------------------------------------------------
388
- // Mock config/loader (needed by isManagedMode in shared.ts)
389
- // ---------------------------------------------------------------------------
390
-
391
- let mockGetConfig: () => Record<string, unknown> = () => ({
392
- services: {},
393
- });
394
-
37
+ // Some shared helpers in oauth/shared.ts touch getConfig() — stub it so the
38
+ // import resolves cleanly even though the requirePlatformConnection path
39
+ // never reads service configuration.
395
40
  mock.module("../config/loader.js", () => ({
396
- getConfig: () => mockGetConfig(),
397
- getConfigReadOnly: () => mockGetConfig(),
398
- loadConfig: () => mockGetConfig(),
41
+ getConfig: () => ({ services: {} }),
42
+ getConfigReadOnly: () => ({ services: {} }),
43
+ loadConfig: () => ({ services: {} }),
399
44
  invalidateConfigCache: () => {},
400
45
  loadRawConfig: () => ({}),
401
46
  saveRawConfig: () => {},
@@ -405,1022 +50,17 @@ mock.module("../config/loader.js", () => ({
405
50
  getNestedValue: () => undefined,
406
51
  setNestedValue: () => {},
407
52
  _appendQuarantineBulletin: () => {},
408
- API_KEY_PROVIDERS: [
409
- "anthropic",
410
- "openai",
411
- "gemini",
412
- "ollama",
413
- "fireworks",
414
- "openrouter",
415
- "brave",
416
- "perplexity",
417
- ],
53
+ API_KEY_PROVIDERS: ["anthropic", "openai", "gemini"],
418
54
  }));
419
55
 
420
56
  mock.module("../util/logger.js", () => ({
421
- getLogger: () => ({
422
- info: () => {},
423
- warn: () => {},
424
- error: () => {},
425
- debug: () => {},
426
- }),
427
- getCliLogger: () => ({
428
- info: () => {},
429
- warn: () => {},
430
- error: () => {},
431
- debug: () => {},
432
- }),
57
+ getLogger: () => ({ info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }),
58
+ getCliLogger: () => ({ info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }),
433
59
  }));
434
60
 
435
- // ---------------------------------------------------------------------------
436
- // Import the module under test (after mocks are registered)
437
- // ---------------------------------------------------------------------------
438
-
439
- const { registerOAuthCommand } = await import("../cli/commands/oauth/index.js");
440
- const { requirePlatformClient, requirePlatformConnection } =
441
- await import("../cli/commands/oauth/shared.js");
442
-
443
- // ---------------------------------------------------------------------------
444
- // Test helper
445
- // ---------------------------------------------------------------------------
446
-
447
- async function runCli(
448
- args: string[],
449
- ): Promise<{ exitCode: number; stdout: string }> {
450
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
451
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
452
- const stdoutChunks: string[] = [];
453
-
454
- process.stdout.write = ((chunk: unknown) => {
455
- stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
456
- return true;
457
- }) as typeof process.stdout.write;
458
-
459
- process.stderr.write = (() => true) as typeof process.stderr.write;
460
-
461
- process.exitCode = 0;
462
-
463
- try {
464
- const program = new Command();
465
- program.exitOverride();
466
- program.configureOutput({
467
- writeErr: () => {},
468
- writeOut: (str: string) => stdoutChunks.push(str),
469
- });
470
- registerOAuthCommand(program);
471
- await program.parseAsync(["node", "assistant", "oauth", ...args]);
472
- } catch {
473
- if (process.exitCode === 0) process.exitCode = 1;
474
- } finally {
475
- process.stdout.write = originalStdoutWrite;
476
- process.stderr.write = originalStderrWrite;
477
- }
478
-
479
- const exitCode = process.exitCode ?? 0;
480
- process.exitCode = 0;
481
-
482
- return {
483
- exitCode,
484
- stdout: stdoutChunks.join(""),
485
- };
486
- }
487
-
488
- // ---------------------------------------------------------------------------
489
- // Tests
490
- // ---------------------------------------------------------------------------
491
-
492
- describe("assistant oauth token <provider-key>", () => {
493
- beforeEach(() => {
494
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
495
- });
496
-
497
- test("prints bare token in human mode", async () => {
498
- const { exitCode, stdout } = await runCli(["token", "twitter"]);
499
- expect(exitCode).toBe(0);
500
- expect(stdout).toBe("mock-access-token-xyz\n");
501
- });
502
-
503
- test("prints JSON in --json mode", async () => {
504
- const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
505
- expect(exitCode).toBe(0);
506
- const parsed = JSON.parse(stdout);
507
- expect(parsed).toEqual({ ok: true, token: "mock-access-token-xyz" });
508
- });
509
-
510
- test("passes provider key directly to withValidToken", async () => {
511
- let capturedService: string | undefined;
512
- mockWithValidToken = async (service, cb) => {
513
- capturedService = service;
514
- return cb("tok");
515
- };
516
-
517
- await runCli(["token", "twitter"]);
518
- expect(capturedService).toBe("twitter");
519
- });
520
-
521
- test("works with other provider keys", async () => {
522
- let capturedService: string | undefined;
523
- mockWithValidToken = async (service, cb) => {
524
- capturedService = service;
525
- return cb("gmail-token");
526
- };
527
-
528
- const { exitCode, stdout } = await runCli(["token", "google"]);
529
- expect(exitCode).toBe(0);
530
- expect(stdout).toBe("gmail-token\n");
531
- expect(capturedService).toBe("google");
532
- });
533
-
534
- test("exits 1 when no token exists", async () => {
535
- mockWithValidToken = async () => {
536
- throw new Error(
537
- 'No access token found for "twitter". Authorization required.',
538
- );
539
- };
540
-
541
- const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
542
- expect(exitCode).toBe(1);
543
- const parsed = JSON.parse(stdout);
544
- expect(parsed.ok).toBe(false);
545
- expect(parsed.error).toContain("No access token found");
546
- });
547
-
548
- test("exits 1 when refresh fails", async () => {
549
- mockWithValidToken = async () => {
550
- throw new Error('Token refresh failed for "twitter": invalid_grant.');
551
- };
552
-
553
- const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
554
- expect(exitCode).toBe(1);
555
- const parsed = JSON.parse(stdout);
556
- expect(parsed.ok).toBe(false);
557
- expect(parsed.error).toContain("Token refresh failed");
558
- });
559
-
560
- test("returns refreshed token transparently", async () => {
561
- // Simulate withValidToken refreshing and returning a new token
562
- mockWithValidToken = async (_service, cb) => cb("refreshed-new-token");
563
-
564
- const { exitCode, stdout } = await runCli(["token", "twitter"]);
565
- expect(exitCode).toBe(0);
566
- expect(stdout).toBe("refreshed-new-token\n");
567
- });
568
-
569
- test("missing provider-key argument exits non-zero", async () => {
570
- const { exitCode } = await runCli(["token"]);
571
- expect(exitCode).not.toBe(0);
572
- });
573
- });
574
-
575
- // ---------------------------------------------------------------------------
576
- // providers list
577
- // ---------------------------------------------------------------------------
578
-
579
- describe("assistant oauth providers list", () => {
580
- const fakeProviders = [
581
- {
582
- provider: "google",
583
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
584
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
585
- defaultScopes: "[]",
586
- availableScopes: null,
587
- authorizeParams: null,
588
- managedServiceConfigKey: "google-oauth",
589
- createdAt: "2025-01-01T00:00:00.000Z",
590
- updatedAt: "2025-01-01T00:00:00.000Z",
591
- },
592
- {
593
- provider: "google-calendar",
594
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
595
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
596
- defaultScopes: "[]",
597
- availableScopes: null,
598
- authorizeParams: null,
599
- managedServiceConfigKey: "google-calendar-oauth",
600
- createdAt: "2025-01-01T00:00:00.000Z",
601
- updatedAt: "2025-01-01T00:00:00.000Z",
602
- },
603
- {
604
- provider: "slack",
605
- authorizeUrl: "https://slack.com/oauth/v2/authorize",
606
- tokenExchangeUrl: "https://slack.com/api/oauth.v2.access",
607
- defaultScopes: "[]",
608
- availableScopes: null,
609
- authorizeParams: null,
610
- managedServiceConfigKey: null,
611
- createdAt: "2025-01-01T00:00:00.000Z",
612
- updatedAt: "2025-01-01T00:00:00.000Z",
613
- },
614
- {
615
- provider: "twitter",
616
- authorizeUrl: "https://twitter.com/i/oauth2/authorize",
617
- tokenExchangeUrl: "https://api.twitter.com/2/oauth2/token",
618
- defaultScopes: "[]",
619
- availableScopes: null,
620
- authorizeParams: null,
621
- managedServiceConfigKey: null,
622
- createdAt: "2025-01-01T00:00:00.000Z",
623
- updatedAt: "2025-01-01T00:00:00.000Z",
624
- },
625
- ];
626
-
627
- beforeEach(() => {
628
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
629
- mockListProviders = () => fakeProviders;
630
- });
631
-
632
- test("returns all providers when no --provider-key is given", async () => {
633
- const { exitCode, stdout } = await runCli(["providers", "list", "--json"]);
634
- expect(exitCode).toBe(0);
635
- const parsed = JSON.parse(stdout);
636
- expect(parsed).toHaveLength(4);
637
- const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
638
- expect(keys).toContain("google");
639
- expect(keys).toContain("google-calendar");
640
- expect(keys).toContain("slack");
641
- expect(keys).toContain("twitter");
642
- });
643
-
644
- test("filters by single --provider-key value", async () => {
645
- const { exitCode, stdout } = await runCli([
646
- "providers",
647
- "list",
648
- "--provider-key",
649
- "slack",
650
- "--json",
651
- ]);
652
- expect(exitCode).toBe(0);
653
- const parsed = JSON.parse(stdout);
654
- expect(parsed).toHaveLength(1);
655
- expect(parsed[0].providerKey).toBe("slack");
656
- });
657
-
658
- test("filters by comma-separated OR values", async () => {
659
- const { exitCode, stdout } = await runCli([
660
- "providers",
661
- "list",
662
- "--provider-key",
663
- "slack,google",
664
- "--json",
665
- ]);
666
- expect(exitCode).toBe(0);
667
- const parsed = JSON.parse(stdout);
668
- expect(parsed).toHaveLength(3);
669
- const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
670
- expect(keys).toContain("google");
671
- expect(keys).toContain("google-calendar");
672
- expect(keys).toContain("slack");
673
- });
674
-
675
- test("returns empty array when comma-separated filter has no matches", async () => {
676
- const { exitCode, stdout } = await runCli([
677
- "providers",
678
- "list",
679
- "--provider-key",
680
- "notion,linear",
681
- "--json",
682
- ]);
683
- expect(exitCode).toBe(0);
684
- const parsed = JSON.parse(stdout);
685
- expect(parsed).toHaveLength(0);
686
- });
687
-
688
- test("trims whitespace around commas in --provider-key", async () => {
689
- const { exitCode, stdout } = await runCli([
690
- "providers",
691
- "list",
692
- "--provider-key",
693
- "slack, google",
694
- "--json",
695
- ]);
696
- expect(exitCode).toBe(0);
697
- const parsed = JSON.parse(stdout);
698
- expect(parsed).toHaveLength(3);
699
- const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
700
- expect(keys).toContain("google");
701
- expect(keys).toContain("google-calendar");
702
- expect(keys).toContain("slack");
703
- });
704
-
705
- test("ignores empty segments from extra commas in --provider-key", async () => {
706
- const { exitCode, stdout } = await runCli([
707
- "providers",
708
- "list",
709
- "--provider-key",
710
- "slack,,google",
711
- "--json",
712
- ]);
713
- expect(exitCode).toBe(0);
714
- const parsed = JSON.parse(stdout);
715
- expect(parsed).toHaveLength(3);
716
- const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
717
- expect(keys).toContain("google");
718
- expect(keys).toContain("google-calendar");
719
- expect(keys).toContain("slack");
720
- });
721
-
722
- test("--supports-managed returns only providers with managedServiceConfigKey set", async () => {
723
- const { exitCode, stdout } = await runCli([
724
- "providers",
725
- "list",
726
- "--supports-managed",
727
- "--json",
728
- ]);
729
- expect(exitCode).toBe(0);
730
- const parsed = JSON.parse(stdout);
731
- expect(parsed).toHaveLength(2);
732
- const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
733
- expect(keys).toContain("google");
734
- expect(keys).toContain("google-calendar");
735
- expect(keys).not.toContain("slack");
736
- expect(keys).not.toContain("twitter");
737
- });
738
-
739
- test("--supports-managed combined with --provider-key applies both filters (AND)", async () => {
740
- const { exitCode, stdout } = await runCli([
741
- "providers",
742
- "list",
743
- "--supports-managed",
744
- "--provider-key",
745
- "google",
746
- "--json",
747
- ]);
748
- expect(exitCode).toBe(0);
749
- const parsed = JSON.parse(stdout);
750
- // Both google and google-calendar match --provider-key "google" AND have
751
- // managedServiceConfigKey set, so both are returned.
752
- expect(parsed).toHaveLength(2);
753
- const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
754
- expect(keys).toContain("google");
755
- expect(keys).toContain("google-calendar");
756
- });
757
-
758
- test("without --supports-managed all providers are returned (existing behavior)", async () => {
759
- const { exitCode, stdout } = await runCli(["providers", "list", "--json"]);
760
- expect(exitCode).toBe(0);
761
- const parsed = JSON.parse(stdout);
762
- expect(parsed).toHaveLength(4);
763
- const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
764
- expect(keys).toContain("google");
765
- expect(keys).toContain("google-calendar");
766
- expect(keys).toContain("slack");
767
- expect(keys).toContain("twitter");
768
- });
769
- });
770
-
771
- // ---------------------------------------------------------------------------
772
- // apps upsert --client-secret-credential-path
773
- // ---------------------------------------------------------------------------
774
-
775
- describe("assistant oauth apps upsert --client-secret-credential-path", () => {
776
- beforeEach(() => {
777
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
778
- mockUpsertAppCalls = [];
779
- mockUpsertAppResult = {
780
- id: "app-upsert-1",
781
- provider: "google",
782
- clientId: "abc123",
783
- createdAt: 1700000000000,
784
- updatedAt: 1700000000000,
785
- };
786
- mockOrchestrateOAuthConnect = async () => ({
787
- success: true,
788
- deferred: false,
789
- grantedScopes: [],
790
- });
791
- mockGetAppByProviderAndClientId = () => undefined;
792
- mockGetMostRecentAppByProvider = () => undefined;
793
- mockGetProvider = () => undefined;
794
- mockGetSecureKey = () => undefined;
795
- mockGetCredentialMetadata = () => undefined;
796
- mockUpsertAppImpl = undefined;
797
- });
798
-
799
- test("upsert with --client-secret-credential-path passes path to upsertApp", async () => {
800
- // "custom/path" has no colon and no credential/ or oauth_app/ prefix.
801
- // resolveCredentialPath passes it through unchanged since it doesn't
802
- // match the service:field shorthand pattern.
803
- const { exitCode, stdout } = await runCli([
804
- "apps",
805
- "upsert",
806
- "--provider",
807
- "google",
808
- "--client-id",
809
- "abc123",
810
- "--client-secret-credential-path",
811
- "custom/path",
812
- "--json",
813
- ]);
814
- expect(exitCode).toBe(0);
815
- expect(mockUpsertAppCalls).toHaveLength(1);
816
- expect(mockUpsertAppCalls[0]).toEqual({
817
- provider: "google",
818
- clientId: "abc123",
819
- clientSecretOpts: { clientSecretCredentialPath: "custom/path" },
820
- });
821
- const parsed = JSON.parse(stdout);
822
- expect(parsed.id).toBe("app-upsert-1");
823
- });
824
-
825
- test("upsert with both --client-secret and --client-secret-credential-path returns error", async () => {
826
- const { exitCode, stdout } = await runCli([
827
- "apps",
828
- "upsert",
829
- "--provider",
830
- "google",
831
- "--client-id",
832
- "abc123",
833
- "--client-secret",
834
- "s3cret",
835
- "--client-secret-credential-path",
836
- "custom/path",
837
- "--json",
838
- ]);
839
- expect(exitCode).toBe(1);
840
- const parsed = JSON.parse(stdout);
841
- expect(parsed.ok).toBe(false);
842
- expect(parsed.error).toContain(
843
- "Cannot provide both --client-secret and --client-secret-credential-path",
844
- );
845
- // upsertApp should NOT have been called
846
- expect(mockUpsertAppCalls).toHaveLength(0);
847
- });
848
-
849
- test("upsert with --client-secret passes clientSecretValue to upsertApp", async () => {
850
- const { exitCode } = await runCli([
851
- "apps",
852
- "upsert",
853
- "--provider",
854
- "google",
855
- "--client-id",
856
- "abc123",
857
- "--client-secret",
858
- "s3cret",
859
- "--json",
860
- ]);
861
- expect(exitCode).toBe(0);
862
- expect(mockUpsertAppCalls).toHaveLength(1);
863
- expect(mockUpsertAppCalls[0]).toEqual({
864
- provider: "google",
865
- clientId: "abc123",
866
- clientSecretOpts: { clientSecretValue: "s3cret" },
867
- });
868
- });
869
-
870
- test("upsert without any secret option passes undefined", async () => {
871
- const { exitCode } = await runCli([
872
- "apps",
873
- "upsert",
874
- "--provider",
875
- "google",
876
- "--client-id",
877
- "abc123",
878
- "--json",
879
- ]);
880
- expect(exitCode).toBe(0);
881
- expect(mockUpsertAppCalls).toHaveLength(1);
882
- expect(mockUpsertAppCalls[0]).toEqual({
883
- provider: "google",
884
- clientId: "abc123",
885
- clientSecretOpts: undefined,
886
- });
887
- });
888
-
889
- test("upsert resolves service:field shorthand to full credential path", async () => {
890
- // The service:field shorthand is resolved to credential/{service}/{field}
891
- const { exitCode, stdout } = await runCli([
892
- "apps",
893
- "upsert",
894
- "--provider",
895
- "google",
896
- "--client-id",
897
- "abc",
898
- "--client-secret-credential-path",
899
- "google:client_secret",
900
- "--json",
901
- ]);
902
- expect(exitCode).toBe(0);
903
- expect(mockUpsertAppCalls).toHaveLength(1);
904
- expect(mockUpsertAppCalls[0]).toEqual({
905
- provider: "google",
906
- clientId: "abc",
907
- clientSecretOpts: {
908
- clientSecretCredentialPath: "credential/google/client_secret",
909
- },
910
- });
911
- const parsed = JSON.parse(stdout);
912
- expect(parsed.id).toBe("app-upsert-1");
913
- });
914
-
915
- test("upsert resolves slack:client_secret shorthand to full credential path", async () => {
916
- const { exitCode, stdout } = await runCli([
917
- "apps",
918
- "upsert",
919
- "--provider",
920
- "slack",
921
- "--client-id",
922
- "slack-abc",
923
- "--client-secret-credential-path",
924
- "slack:client_secret",
925
- "--json",
926
- ]);
927
- expect(exitCode).toBe(0);
928
- expect(mockUpsertAppCalls).toHaveLength(1);
929
- expect(mockUpsertAppCalls[0]).toEqual({
930
- provider: "slack",
931
- clientId: "slack-abc",
932
- clientSecretOpts: {
933
- clientSecretCredentialPath: "credential/slack/client_secret",
934
- },
935
- });
936
- const parsed = JSON.parse(stdout);
937
- expect(parsed.id).toBe("app-upsert-1");
938
- });
939
-
940
- test("upsert passes prefixed credential path through unchanged", async () => {
941
- const { exitCode, stdout } = await runCli([
942
- "apps",
943
- "upsert",
944
- "--provider",
945
- "google",
946
- "--client-id",
947
- "abc",
948
- "--client-secret-credential-path",
949
- "credential/google/client_secret",
950
- "--json",
951
- ]);
952
- expect(exitCode).toBe(0);
953
- expect(mockUpsertAppCalls).toHaveLength(1);
954
- // Already-prefixed path should be passed through as-is
955
- expect(mockUpsertAppCalls[0]).toEqual({
956
- provider: "google",
957
- clientId: "abc",
958
- clientSecretOpts: {
959
- clientSecretCredentialPath: "credential/google/client_secret",
960
- },
961
- });
962
- const parsed = JSON.parse(stdout);
963
- expect(parsed.id).toBe("app-upsert-1");
964
- });
965
-
966
- test("upsert passes oauth_app/ prefixed credential path through unchanged", async () => {
967
- const { exitCode, stdout } = await runCli([
968
- "apps",
969
- "upsert",
970
- "--provider",
971
- "google",
972
- "--client-id",
973
- "abc",
974
- "--client-secret-credential-path",
975
- "oauth_app/some-id/client_secret",
976
- "--json",
977
- ]);
978
- expect(exitCode).toBe(0);
979
- expect(mockUpsertAppCalls).toHaveLength(1);
980
- // oauth_app/ prefixed path should be passed through as-is
981
- expect(mockUpsertAppCalls[0]).toEqual({
982
- provider: "google",
983
- clientId: "abc",
984
- clientSecretOpts: {
985
- clientSecretCredentialPath: "oauth_app/some-id/client_secret",
986
- },
987
- });
988
- const parsed = JSON.parse(stdout);
989
- expect(parsed.id).toBe("app-upsert-1");
990
- });
991
-
992
- test("upsert with invalid credential path returns error when no secret found", async () => {
993
- // Override upsertApp to throw when given an unresolvable credential path
994
- mockUpsertAppImpl = async (_provider, _clientId, clientSecretOpts) => {
995
- throw new Error(
996
- `No secret found at credential path: ${clientSecretOpts?.clientSecretCredentialPath}`,
997
- );
998
- };
999
-
1000
- const { exitCode, stdout } = await runCli([
1001
- "apps",
1002
- "upsert",
1003
- "--provider",
1004
- "google",
1005
- "--client-id",
1006
- "abc",
1007
- "--client-secret-credential-path",
1008
- "bogus:nonexistent:path",
1009
- "--json",
1010
- ]);
1011
- expect(exitCode).toBe(1);
1012
- const parsed = JSON.parse(stdout);
1013
- expect(parsed.ok).toBe(false);
1014
- expect(parsed.error).toContain("No secret found");
1015
- });
1016
- });
1017
-
1018
- // ---------------------------------------------------------------------------
1019
- // ping
1020
- // ---------------------------------------------------------------------------
1021
-
1022
- describe("assistant oauth ping <provider-key>", () => {
1023
- beforeEach(() => {
1024
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1025
- // Reset resolveOAuthConnection to default (unconfigured)
1026
- mockResolveOAuthConnection = async () => {
1027
- throw new Error("resolveOAuthConnection not configured in test");
1028
- };
1029
- });
1030
-
1031
- test("returns ok when ping endpoint returns 200", async () => {
1032
- mockGetProvider = () => ({
1033
- provider: "google",
1034
- pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
1035
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1036
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
1037
- defaultScopes: "[]",
1038
- availableScopes: null,
1039
- authorizeParams: null,
1040
- createdAt: Date.now(),
1041
- updatedAt: Date.now(),
1042
- });
1043
- mockResolveOAuthConnection = async () => ({
1044
- id: "conn-1",
1045
- provider: "google",
1046
- accountInfo: null,
1047
- request: async () => ({ status: 200, headers: {}, body: {} }),
1048
- withToken: async (fn) => fn("mock-access-token-xyz"),
1049
- });
1050
- const { exitCode, stdout } = await runCli(["ping", "google", "--json"]);
1051
- expect(exitCode).toBe(0);
1052
- const parsed = JSON.parse(stdout);
1053
- expect(parsed.ok).toBe(true);
1054
- expect(parsed.status).toBe(200);
1055
- });
1056
-
1057
- test("exits 1 when provider not found", async () => {
1058
- mockGetProvider = () => undefined;
1059
- const { exitCode, stdout } = await runCli(["ping", "unknown", "--json"]);
1060
- expect(exitCode).toBe(1);
1061
- const parsed = JSON.parse(stdout);
1062
- expect(parsed.ok).toBe(false);
1063
- expect(parsed.error).toContain("Unknown provider");
1064
- });
1065
-
1066
- test("exits 1 when no ping URL configured", async () => {
1067
- mockGetProvider = () => ({
1068
- provider: "telegram",
1069
- pingUrl: null,
1070
- authorizeUrl: "urn:manual-token",
1071
- tokenExchangeUrl: "urn:manual-token",
1072
- defaultScopes: "[]",
1073
- availableScopes: null,
1074
- authorizeParams: null,
1075
- createdAt: Date.now(),
1076
- updatedAt: Date.now(),
1077
- });
1078
- const { exitCode, stdout } = await runCli(["ping", "telegram", "--json"]);
1079
- expect(exitCode).toBe(1);
1080
- const parsed = JSON.parse(stdout);
1081
- expect(parsed.ok).toBe(false);
1082
- expect(parsed.error).toContain("No ping URL configured");
1083
- });
1084
-
1085
- test("exits 1 when ping endpoint returns non-2xx", async () => {
1086
- mockGetProvider = () => ({
1087
- provider: "google",
1088
- pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
1089
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1090
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
1091
- defaultScopes: "[]",
1092
- availableScopes: null,
1093
- authorizeParams: null,
1094
- createdAt: Date.now(),
1095
- updatedAt: Date.now(),
1096
- });
1097
- mockResolveOAuthConnection = async () => ({
1098
- id: "conn-1",
1099
- provider: "google",
1100
- accountInfo: null,
1101
- request: async () => ({ status: 403, headers: {}, body: "Forbidden" }),
1102
- withToken: async (fn) => fn("mock-access-token-xyz"),
1103
- });
1104
- const { exitCode, stdout } = await runCli(["ping", "google", "--json"]);
1105
- expect(exitCode).toBe(1);
1106
- const parsed = JSON.parse(stdout);
1107
- expect(parsed.ok).toBe(false);
1108
- expect(parsed.status).toBe(403);
1109
- });
1110
-
1111
- test("exits 1 when no connection can be resolved", async () => {
1112
- mockGetProvider = () => ({
1113
- provider: "google",
1114
- pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
1115
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1116
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
1117
- defaultScopes: "[]",
1118
- availableScopes: null,
1119
- authorizeParams: null,
1120
- createdAt: Date.now(),
1121
- updatedAt: Date.now(),
1122
- });
1123
- mockResolveOAuthConnection = async () => {
1124
- throw new Error('No access token found for "google".');
1125
- };
1126
- const { exitCode, stdout } = await runCli(["ping", "google", "--json"]);
1127
- expect(exitCode).toBe(1);
1128
- const parsed = JSON.parse(stdout);
1129
- expect(parsed.ok).toBe(false);
1130
- expect(parsed.error).toContain("No access token");
1131
- });
1132
- });
1133
-
1134
- // ---------------------------------------------------------------------------
1135
- // oauth connect — managed mode 401/403 error messages
1136
- // ---------------------------------------------------------------------------
1137
-
1138
- describe("assistant oauth connect managed mode — platform 401/403 errors", () => {
1139
- /**
1140
- * Helper: create a mock platform client whose `fetch` always returns the
1141
- * given status code and body text.
1142
- */
1143
- function makeMockPlatformClient(status: number, body = "") {
1144
- return {
1145
- platformAssistantId: "asst-test-123",
1146
- fetch: async () =>
1147
- new Response(body, { status, statusText: `HTTP ${status}` }),
1148
- };
1149
- }
1150
-
1151
- beforeEach(() => {
1152
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1153
- mockOrchestrateOAuthConnect = async () => ({
1154
- success: true,
1155
- deferred: false,
1156
- grantedScopes: [],
1157
- });
1158
- mockGetAppByProviderAndClientId = () => undefined;
1159
- mockGetMostRecentAppByProvider = () => undefined;
1160
- mockGetSecureKey = () => undefined;
1161
- mockGetCredentialMetadata = () => undefined;
1162
-
1163
- // Set up managed mode: provider has managedServiceConfigKey, config
1164
- // returns the matching service with mode "managed".
1165
- mockGetProvider = () => ({
1166
- provider: "google",
1167
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1168
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
1169
- defaultScopes: "[]",
1170
- availableScopes: null,
1171
- authorizeParams: null,
1172
- managedServiceConfigKey: "google-oauth",
1173
- createdAt: Date.now(),
1174
- updatedAt: Date.now(),
1175
- });
1176
- mockGetConfig = () => ({
1177
- services: {
1178
- "google-oauth": { mode: "managed" },
1179
- },
1180
- });
1181
- });
1182
-
1183
- afterEach(() => {
1184
- mockPlatformClientCreate = async () => null;
1185
- mockGetConfig = () => ({ services: {} });
1186
- });
1187
-
1188
- test("401 response includes 'vellum platform connect' suggestion", async () => {
1189
- mockPlatformClientCreate = async () =>
1190
- makeMockPlatformClient(401, "Unauthorized");
1191
- const { exitCode, stdout } = await runCli([
1192
- "connect",
1193
- "google",
1194
- "--no-browser",
1195
- "--json",
1196
- ]);
1197
- expect(exitCode).toBe(1);
1198
- const parsed = JSON.parse(stdout);
1199
- expect(parsed.ok).toBe(false);
1200
- expect(parsed.error).toContain("Platform returned HTTP 401");
1201
- expect(parsed.error).toContain("Unauthorized");
1202
- expect(parsed.error).toContain("vellum platform connect");
1203
- });
1204
-
1205
- test("403 response includes 'vellum platform connect' suggestion", async () => {
1206
- mockPlatformClientCreate = async () =>
1207
- makeMockPlatformClient(403, "Forbidden");
1208
- const { exitCode, stdout } = await runCli([
1209
- "connect",
1210
- "google",
1211
- "--no-browser",
1212
- "--json",
1213
- ]);
1214
- expect(exitCode).toBe(1);
1215
- const parsed = JSON.parse(stdout);
1216
- expect(parsed.ok).toBe(false);
1217
- expect(parsed.error).toContain("Platform returned HTTP 403");
1218
- expect(parsed.error).toContain("Forbidden");
1219
- expect(parsed.error).toContain("vellum platform connect");
1220
- });
1221
-
1222
- test("500 response does NOT include 'vellum platform connect' suggestion", async () => {
1223
- mockPlatformClientCreate = async () =>
1224
- makeMockPlatformClient(500, "Internal Server Error");
1225
- const { exitCode, stdout } = await runCli([
1226
- "connect",
1227
- "google",
1228
- "--no-browser",
1229
- "--json",
1230
- ]);
1231
- expect(exitCode).toBe(1);
1232
- const parsed = JSON.parse(stdout);
1233
- expect(parsed.ok).toBe(false);
1234
- expect(parsed.error).toContain("Platform returned HTTP 500");
1235
- expect(parsed.error).not.toContain("vellum platform connect");
1236
- });
1237
- });
1238
-
1239
- // ---------------------------------------------------------------------------
1240
- // `assistant oauth connect <provider>` BYO mode — daemon-unreachable behavior.
1241
- //
1242
- // We deleted the in-process `orchestrateOAuthConnect` fallback (the same
1243
- // pattern as the MCP CLI consolidation in #29484). When the daemon is
1244
- // unreachable, the CLI must surface a clear error and exit 1 — never
1245
- // silently fall back to in-process flow.
1246
- // ---------------------------------------------------------------------------
1247
-
1248
- describe("assistant oauth connect <provider> — daemon unreachable (BYO mode)", () => {
1249
- beforeEach(() => {
1250
- // BYO provider with a registered app and no managed-mode wiring.
1251
- mockGetProvider = () => ({
1252
- provider: "github",
1253
- authorizeUrl: "https://github.com/login/oauth/authorize",
1254
- tokenExchangeUrl: "https://github.com/login/oauth/access_token",
1255
- defaultScopes: "[]",
1256
- availableScopes: null,
1257
- authorizeParams: null,
1258
- managedServiceConfigKey: null,
1259
- requiresClientSecret: false,
1260
- createdAt: Date.now(),
1261
- updatedAt: Date.now(),
1262
- });
1263
- mockGetMostRecentAppByProvider = () => ({
1264
- provider: "github",
1265
- clientId: "test-client-id",
1266
- clientSecretCredentialPath: "oauth_app/github/test/client_secret",
1267
- });
1268
- mockGetSecureKey = () => "test-secret";
1269
- mockGetConfig = () => ({ services: {} });
1270
- });
1271
-
1272
- test("daemon connect-refused → exit 1 with 'Is the assistant running?'", async () => {
1273
- mockCliIpcCall = async () => ({
1274
- ok: false,
1275
- error: "Could not connect to assistant daemon: ECONNREFUSED",
1276
- });
1277
-
1278
- const { exitCode, stdout } = await runCli([
1279
- "connect",
1280
- "github",
1281
- "--no-browser",
1282
- "--json",
1283
- ]);
1284
-
1285
- expect(exitCode).toBe(1);
1286
- const parsed = JSON.parse(stdout);
1287
- expect(parsed.ok).toBe(false);
1288
- expect(parsed.error).toContain("Could not reach the assistant");
1289
- expect(parsed.error).toContain("Is the assistant running?");
1290
- });
1291
-
1292
- test("daemon route missing (Unknown method) → exit 1, never falls through to in-process", async () => {
1293
- let orchestratorCalls = 0;
1294
- mockOrchestrateOAuthConnect = async () => {
1295
- orchestratorCalls++;
1296
- return { success: true, deferred: false, grantedScopes: [] };
1297
- };
1298
- mockCliIpcCall = async () => ({
1299
- ok: false,
1300
- error: "Unknown method: internal_oauth_connect_start",
1301
- });
1302
-
1303
- const { exitCode } = await runCli([
1304
- "connect",
1305
- "github",
1306
- "--no-browser",
1307
- "--json",
1308
- ]);
1309
-
1310
- expect(exitCode).toBe(1);
1311
- // Critical regression guard: the in-process `orchestrateOAuthConnect`
1312
- // must NOT be invoked from the CLI. The daemon-orchestrated path is
1313
- // the sole code path; this is the same invariant #29484 established
1314
- // for the MCP CLI.
1315
- expect(orchestratorCalls).toBe(0);
1316
- });
1317
-
1318
- test("daemon HTTP error (statusCode set) → surfaces error verbatim, no fallback", async () => {
1319
- let orchestratorCalls = 0;
1320
- mockOrchestrateOAuthConnect = async () => {
1321
- orchestratorCalls++;
1322
- return { success: true, deferred: false, grantedScopes: [] };
1323
- };
1324
- mockCliIpcCall = async () => ({
1325
- ok: false,
1326
- statusCode: 400,
1327
- error: "service must be registered before connecting",
1328
- });
1329
-
1330
- const { exitCode, stdout } = await runCli([
1331
- "connect",
1332
- "github",
1333
- "--no-browser",
1334
- "--json",
1335
- ]);
1336
-
1337
- expect(exitCode).toBe(1);
1338
- const parsed = JSON.parse(stdout);
1339
- expect(parsed.error).toContain("service must be registered");
1340
- expect(orchestratorCalls).toBe(0);
1341
- });
1342
- });
1343
-
1344
- // ---------------------------------------------------------------------------
1345
- // requirePlatformClient — improved error messages
1346
- // ---------------------------------------------------------------------------
1347
-
1348
- describe("requirePlatformClient", () => {
1349
- test("returns error mentioning 'vellum platform connect' when not connected", async () => {
1350
- mockPlatformClientCreate = async () => null;
1351
- const stdoutChunks: string[] = [];
1352
- const originalWrite = process.stdout.write.bind(process.stdout);
1353
- process.stdout.write = ((chunk: unknown) => {
1354
- stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
1355
- return true;
1356
- }) as typeof process.stdout.write;
1357
- process.exitCode = 0;
1358
-
1359
- try {
1360
- const cmd = new Command();
1361
- cmd.option("--json");
1362
- cmd.parse(["node", "test", "--json"]);
1363
- const result = await requirePlatformClient(cmd);
1364
- expect(result).toBeNull();
1365
- expect(process.exitCode).toBe(1);
1366
- const output = stdoutChunks.join("");
1367
- const parsed = JSON.parse(output);
1368
- expect(parsed.ok).toBe(false);
1369
- expect(parsed.error).toContain("vellum platform connect");
1370
- expect(parsed.error).toContain("Not connected");
1371
- } finally {
1372
- process.stdout.write = originalWrite;
1373
- process.exitCode = 0;
1374
- }
1375
- });
1376
-
1377
- test("returns distinct error when connected but missing assistant ID", async () => {
1378
- mockPlatformClientCreate = async () => ({
1379
- platformAssistantId: "",
1380
- fetch: async () => new Response(),
1381
- });
1382
- const stdoutChunks: string[] = [];
1383
- const originalWrite = process.stdout.write.bind(process.stdout);
1384
- process.stdout.write = ((chunk: unknown) => {
1385
- stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
1386
- return true;
1387
- }) as typeof process.stdout.write;
1388
- process.exitCode = 0;
1389
-
1390
- try {
1391
- const cmd = new Command();
1392
- cmd.option("--json");
1393
- cmd.parse(["node", "test", "--json"]);
1394
- const result = await requirePlatformClient(cmd);
1395
- expect(result).toBeNull();
1396
- expect(process.exitCode).toBe(1);
1397
- const output = stdoutChunks.join("");
1398
- const parsed = JSON.parse(output);
1399
- expect(parsed.ok).toBe(false);
1400
- expect(parsed.error).toContain("no assistant ID is configured");
1401
- expect(parsed.error).toContain("registered on the platform");
1402
- } finally {
1403
- process.stdout.write = originalWrite;
1404
- process.exitCode = 0;
1405
- }
1406
- });
1407
-
1408
- test("returns client when connected with assistant ID", async () => {
1409
- mockPlatformClientCreate = async () => ({
1410
- platformAssistantId: "asst-123",
1411
- fetch: async () => new Response(),
1412
- });
1413
- process.exitCode = 0;
1414
-
1415
- const cmd = new Command();
1416
- cmd.option("--json");
1417
- cmd.parse(["node", "test", "--json"]);
1418
- const result = await requirePlatformClient(cmd);
1419
- expect(result).not.toBeNull();
1420
- expect(result!.platformAssistantId).toBe("asst-123");
1421
- expect(process.exitCode).toBe(0);
1422
- });
1423
- });
61
+ const { requirePlatformConnection } = await import(
62
+ "../cli/commands/oauth/shared.js"
63
+ );
1424
64
 
1425
65
  // ---------------------------------------------------------------------------
1426
66
  // requirePlatformConnection
@@ -1485,614 +125,3 @@ describe("requirePlatformConnection", () => {
1485
125
  expect(process.exitCode).toBe(0);
1486
126
  });
1487
127
  });
1488
-
1489
- // ---------------------------------------------------------------------------
1490
- // oauth mode — platform connection guard
1491
- // ---------------------------------------------------------------------------
1492
-
1493
- describe("assistant oauth mode", () => {
1494
- beforeEach(() => {
1495
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1496
- mockGetProvider = () => ({
1497
- provider: "google",
1498
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1499
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
1500
- defaultScopes: "[]",
1501
- availableScopes: null,
1502
- authorizeParams: null,
1503
- managedServiceConfigKey: "google-oauth",
1504
- createdAt: Date.now(),
1505
- updatedAt: Date.now(),
1506
- });
1507
- mockGetConfig = () => ({
1508
- services: {
1509
- "google-oauth": { mode: "your-own" },
1510
- },
1511
- });
1512
- });
1513
-
1514
- afterEach(() => {
1515
- mockPlatformClientCreate = async () => null;
1516
- mockGetConfig = () => ({ services: {} });
1517
- mockGetProvider = () => undefined;
1518
- });
1519
-
1520
- test("oauth mode <provider> --set managed fails when not connected to platform", async () => {
1521
- mockPlatformClientCreate = async () => null;
1522
- const { exitCode, stdout } = await runCli([
1523
- "mode",
1524
- "google",
1525
- "--set",
1526
- "managed",
1527
- "--json",
1528
- ]);
1529
- expect(exitCode).toBe(1);
1530
- const parsed = JSON.parse(stdout);
1531
- expect(parsed.ok).toBe(false);
1532
- expect(parsed.error).toContain("vellum platform connect");
1533
- });
1534
-
1535
- test("oauth mode <provider> --set your-own succeeds without platform connection", async () => {
1536
- mockPlatformClientCreate = async () => null;
1537
- const { exitCode, stdout } = await runCli([
1538
- "mode",
1539
- "google",
1540
- "--set",
1541
- "your-own",
1542
- "--json",
1543
- ]);
1544
- // Setting to "your-own" doesn't need platform — it's a local-only operation
1545
- expect(exitCode).toBe(0);
1546
- const parsed = JSON.parse(stdout);
1547
- expect(parsed.ok).toBe(true);
1548
- expect(parsed.mode).toBe("your-own");
1549
- });
1550
-
1551
- test("oauth mode <provider> (read) succeeds without platform connection", async () => {
1552
- mockPlatformClientCreate = async () => null;
1553
- const { exitCode, stdout } = await runCli(["mode", "google", "--json"]);
1554
- expect(exitCode).toBe(0);
1555
- const parsed = JSON.parse(stdout);
1556
- expect(parsed.ok).toBe(true);
1557
- expect(parsed.provider).toBe("google");
1558
- expect(parsed.mode).toBe("your-own");
1559
- });
1560
- });
1561
-
1562
- // ---------------------------------------------------------------------------
1563
- // providers register / update / get — --scope-separator wiring
1564
- // ---------------------------------------------------------------------------
1565
-
1566
- describe("assistant oauth providers --scope-separator", () => {
1567
- beforeEach(() => {
1568
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1569
- mockProviderStore.clear();
1570
- // Default getProvider falls through to mockProviderStore via the
1571
- // oauth-store mock module. Tests in this describe block don't need
1572
- // a per-test mockGetProvider override.
1573
- mockGetProvider = () => undefined;
1574
- mockGetConfig = () => ({ services: {} });
1575
- });
1576
-
1577
- afterEach(() => {
1578
- mockProviderStore.clear();
1579
- mockGetProvider = () => undefined;
1580
- });
1581
-
1582
- test("providers register --scope-separator , stores ',' on the provider row", async () => {
1583
- const { exitCode } = await runCli([
1584
- "providers",
1585
- "register",
1586
- "--provider-key",
1587
- "custom-linear",
1588
- "--auth-url",
1589
- "https://linear.app/oauth/authorize",
1590
- "--token-url",
1591
- "https://api.linear.app/oauth/token",
1592
- "--scopes",
1593
- "read,write",
1594
- "--scope-separator",
1595
- ",",
1596
- "--json",
1597
- ]);
1598
- expect(exitCode).toBe(0);
1599
- const stored = mockProviderStore.get("custom-linear");
1600
- expect(stored).toBeDefined();
1601
- expect(stored?.scopeSeparator).toBe(",");
1602
- });
1603
-
1604
- test("providers register without --scope-separator stores the default ' '", async () => {
1605
- const { exitCode } = await runCli([
1606
- "providers",
1607
- "register",
1608
- "--provider-key",
1609
- "custom-default-sep",
1610
- "--auth-url",
1611
- "https://example.com/oauth/authorize",
1612
- "--token-url",
1613
- "https://example.com/oauth/token",
1614
- "--scopes",
1615
- "read,write",
1616
- "--json",
1617
- ]);
1618
- expect(exitCode).toBe(0);
1619
- const stored = mockProviderStore.get("custom-default-sep");
1620
- expect(stored).toBeDefined();
1621
- expect(stored?.scopeSeparator).toBe(" ");
1622
- });
1623
-
1624
- test("providers update --scope-separator , updates an existing custom provider", async () => {
1625
- // Seed the store with an existing custom provider that uses the default
1626
- // " " separator.
1627
- await runCli([
1628
- "providers",
1629
- "register",
1630
- "--provider-key",
1631
- "custom-update-target",
1632
- "--auth-url",
1633
- "https://example.com/oauth/authorize",
1634
- "--token-url",
1635
- "https://example.com/oauth/token",
1636
- "--scopes",
1637
- "read",
1638
- "--json",
1639
- ]);
1640
- expect(mockProviderStore.get("custom-update-target")?.scopeSeparator).toBe(
1641
- " ",
1642
- );
1643
-
1644
- const { exitCode } = await runCli([
1645
- "providers",
1646
- "update",
1647
- "custom-update-target",
1648
- "--scope-separator",
1649
- ",",
1650
- "--json",
1651
- ]);
1652
- expect(exitCode).toBe(0);
1653
- expect(mockProviderStore.get("custom-update-target")?.scopeSeparator).toBe(
1654
- ",",
1655
- );
1656
- });
1657
-
1658
- test("providers get <key> --json includes scopeSeparator from the serialized output", async () => {
1659
- // Seed the store with a custom provider that uses ',' as the separator.
1660
- await runCli([
1661
- "providers",
1662
- "register",
1663
- "--provider-key",
1664
- "custom-get-target",
1665
- "--auth-url",
1666
- "https://example.com/oauth/authorize",
1667
- "--token-url",
1668
- "https://example.com/oauth/token",
1669
- "--scopes",
1670
- "read,write",
1671
- "--scope-separator",
1672
- ",",
1673
- "--json",
1674
- ]);
1675
-
1676
- const { exitCode, stdout } = await runCli([
1677
- "providers",
1678
- "get",
1679
- "custom-get-target",
1680
- "--json",
1681
- ]);
1682
- expect(exitCode).toBe(0);
1683
- const parsed = JSON.parse(stdout);
1684
- expect(parsed.scopeSeparator).toBe(",");
1685
- });
1686
- });
1687
-
1688
- // ---------------------------------------------------------------------------
1689
- // providers register / update / get — --refresh-url wiring
1690
- // ---------------------------------------------------------------------------
1691
-
1692
- describe("assistant oauth providers --refresh-url", () => {
1693
- beforeEach(() => {
1694
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1695
- mockProviderStore.clear();
1696
- // Default getProvider falls through to mockProviderStore via the
1697
- // oauth-store mock module.
1698
- mockGetProvider = () => undefined;
1699
- mockGetConfig = () => ({ services: {} });
1700
- });
1701
-
1702
- afterEach(() => {
1703
- mockProviderStore.clear();
1704
- mockGetProvider = () => undefined;
1705
- });
1706
-
1707
- test("providers register --refresh-url stores the URL on the provider row", async () => {
1708
- const { exitCode } = await runCli([
1709
- "providers",
1710
- "register",
1711
- "--provider-key",
1712
- "custom-refresh-url",
1713
- "--auth-url",
1714
- "https://example.com/oauth/authorize",
1715
- "--token-url",
1716
- "https://example.com/oauth/token",
1717
- "--refresh-url",
1718
- "https://refresh.example.com/token",
1719
- "--json",
1720
- ]);
1721
- expect(exitCode).toBe(0);
1722
- const stored = mockProviderStore.get("custom-refresh-url");
1723
- expect(stored).toBeDefined();
1724
- expect(stored?.refreshUrl).toBe("https://refresh.example.com/token");
1725
- });
1726
-
1727
- test("providers register without --refresh-url stores null", async () => {
1728
- const { exitCode } = await runCli([
1729
- "providers",
1730
- "register",
1731
- "--provider-key",
1732
- "custom-no-refresh-url",
1733
- "--auth-url",
1734
- "https://example.com/oauth/authorize",
1735
- "--token-url",
1736
- "https://example.com/oauth/token",
1737
- "--json",
1738
- ]);
1739
- expect(exitCode).toBe(0);
1740
- const stored = mockProviderStore.get("custom-no-refresh-url");
1741
- expect(stored).toBeDefined();
1742
- expect(stored?.refreshUrl).toBeNull();
1743
- });
1744
-
1745
- test("providers update --refresh-url updates an existing custom provider", async () => {
1746
- // Seed the store with an existing custom provider that has no refresh URL.
1747
- await runCli([
1748
- "providers",
1749
- "register",
1750
- "--provider-key",
1751
- "custom-update-refresh",
1752
- "--auth-url",
1753
- "https://example.com/oauth/authorize",
1754
- "--token-url",
1755
- "https://example.com/oauth/token",
1756
- "--json",
1757
- ]);
1758
- expect(
1759
- mockProviderStore.get("custom-update-refresh")?.refreshUrl,
1760
- ).toBeNull();
1761
-
1762
- const { exitCode } = await runCli([
1763
- "providers",
1764
- "update",
1765
- "custom-update-refresh",
1766
- "--refresh-url",
1767
- "https://new-refresh.example.com/token",
1768
- "--json",
1769
- ]);
1770
- expect(exitCode).toBe(0);
1771
- expect(mockProviderStore.get("custom-update-refresh")?.refreshUrl).toBe(
1772
- "https://new-refresh.example.com/token",
1773
- );
1774
- });
1775
-
1776
- test("providers get <key> --json includes refreshUrl from the serialized output", async () => {
1777
- // Seed the store with a custom provider that has a refresh URL set.
1778
- await runCli([
1779
- "providers",
1780
- "register",
1781
- "--provider-key",
1782
- "custom-get-refresh",
1783
- "--auth-url",
1784
- "https://example.com/oauth/authorize",
1785
- "--token-url",
1786
- "https://example.com/oauth/token",
1787
- "--refresh-url",
1788
- "https://refresh.example.com/token",
1789
- "--json",
1790
- ]);
1791
-
1792
- const { exitCode, stdout } = await runCli([
1793
- "providers",
1794
- "get",
1795
- "custom-get-refresh",
1796
- "--json",
1797
- ]);
1798
- expect(exitCode).toBe(0);
1799
- const parsed = JSON.parse(stdout);
1800
- expect(parsed.refreshUrl).toBe("https://refresh.example.com/token");
1801
- });
1802
- });
1803
-
1804
- // ---------------------------------------------------------------------------
1805
- // providers register / update / get — --revoke-url and --revoke-body-template wiring
1806
- // ---------------------------------------------------------------------------
1807
-
1808
- describe("assistant oauth providers --revoke-url and --revoke-body-template", () => {
1809
- beforeEach(() => {
1810
- mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1811
- mockProviderStore.clear();
1812
- mockGetProvider = () => undefined;
1813
- mockGetConfig = () => ({ services: {} });
1814
- });
1815
-
1816
- afterEach(() => {
1817
- mockProviderStore.clear();
1818
- mockGetProvider = () => undefined;
1819
- });
1820
-
1821
- test("providers register --revoke-url and --revoke-body-template stores both fields", async () => {
1822
- const { exitCode } = await runCli([
1823
- "providers",
1824
- "register",
1825
- "--provider-key",
1826
- "custom-revoke-roundtrip",
1827
- "--auth-url",
1828
- "https://example.com/oauth/authorize",
1829
- "--token-url",
1830
- "https://example.com/oauth/token",
1831
- "--revoke-url",
1832
- "https://revoke.example.com",
1833
- "--revoke-body-template",
1834
- '{"token":"{access_token}"}',
1835
- "--json",
1836
- ]);
1837
- expect(exitCode).toBe(0);
1838
- const stored = mockProviderStore.get("custom-revoke-roundtrip");
1839
- expect(stored).toBeDefined();
1840
- expect(stored?.revokeUrl).toBe("https://revoke.example.com");
1841
- // revokeBodyTemplate is JSON-stringified at the storage layer.
1842
- expect(JSON.parse(stored?.revokeBodyTemplate as string)).toEqual({
1843
- token: "{access_token}",
1844
- });
1845
- });
1846
-
1847
- test("providers register without --revoke-url or --revoke-body-template stores both as null", async () => {
1848
- const { exitCode } = await runCli([
1849
- "providers",
1850
- "register",
1851
- "--provider-key",
1852
- "custom-no-revoke",
1853
- "--auth-url",
1854
- "https://example.com/oauth/authorize",
1855
- "--token-url",
1856
- "https://example.com/oauth/token",
1857
- "--json",
1858
- ]);
1859
- expect(exitCode).toBe(0);
1860
- const stored = mockProviderStore.get("custom-no-revoke");
1861
- expect(stored).toBeDefined();
1862
- expect(stored?.revokeUrl).toBeNull();
1863
- expect(stored?.revokeBodyTemplate).toBeNull();
1864
- });
1865
-
1866
- test("providers register with malformed --revoke-body-template fails with a JSON parse error", async () => {
1867
- const { exitCode, stdout } = await runCli([
1868
- "providers",
1869
- "register",
1870
- "--provider-key",
1871
- "custom-bad-revoke",
1872
- "--auth-url",
1873
- "https://example.com/oauth/authorize",
1874
- "--token-url",
1875
- "https://example.com/oauth/token",
1876
- "--revoke-body-template",
1877
- "not-json{",
1878
- "--json",
1879
- ]);
1880
- expect(exitCode).toBe(1);
1881
- const parsed = JSON.parse(stdout);
1882
- expect(parsed.ok).toBe(false);
1883
- // The error message comes from JSON.parse — match permissively.
1884
- expect(parsed.error).toMatch(/JSON|Unexpected|parse/i);
1885
- // Provider should not have been written.
1886
- expect(mockProviderStore.get("custom-bad-revoke")).toBeUndefined();
1887
- });
1888
-
1889
- test("providers update --revoke-url updates only the URL", async () => {
1890
- // Seed an existing custom provider.
1891
- await runCli([
1892
- "providers",
1893
- "register",
1894
- "--provider-key",
1895
- "custom-update-revoke-url",
1896
- "--auth-url",
1897
- "https://example.com/oauth/authorize",
1898
- "--token-url",
1899
- "https://example.com/oauth/token",
1900
- "--json",
1901
- ]);
1902
- expect(
1903
- mockProviderStore.get("custom-update-revoke-url")?.revokeUrl,
1904
- ).toBeNull();
1905
-
1906
- const { exitCode } = await runCli([
1907
- "providers",
1908
- "update",
1909
- "custom-update-revoke-url",
1910
- "--revoke-url",
1911
- "https://new-revoke.example.com",
1912
- "--json",
1913
- ]);
1914
- expect(exitCode).toBe(0);
1915
- expect(mockProviderStore.get("custom-update-revoke-url")?.revokeUrl).toBe(
1916
- "https://new-revoke.example.com",
1917
- );
1918
- // revokeBodyTemplate should still be null (unchanged).
1919
- expect(
1920
- mockProviderStore.get("custom-update-revoke-url")?.revokeBodyTemplate,
1921
- ).toBeNull();
1922
- });
1923
-
1924
- test("providers update --revoke-url with empty string clears the stored URL to null", async () => {
1925
- // Seed an existing custom provider that already has a non-null revokeUrl.
1926
- await runCli([
1927
- "providers",
1928
- "register",
1929
- "--provider-key",
1930
- "custom-clear-revoke-url",
1931
- "--auth-url",
1932
- "https://example.com/oauth/authorize",
1933
- "--token-url",
1934
- "https://example.com/oauth/token",
1935
- "--revoke-url",
1936
- "https://revoke.example.com",
1937
- "--json",
1938
- ]);
1939
- expect(mockProviderStore.get("custom-clear-revoke-url")?.revokeUrl).toBe(
1940
- "https://revoke.example.com",
1941
- );
1942
-
1943
- // Pass an empty string to --revoke-url — the help text promises this
1944
- // clears the field, so the stored value should end up as null (not "").
1945
- const { exitCode, stdout } = await runCli([
1946
- "providers",
1947
- "update",
1948
- "custom-clear-revoke-url",
1949
- "--revoke-url",
1950
- "",
1951
- "--json",
1952
- ]);
1953
- expect(exitCode).toBe(0);
1954
- expect(
1955
- mockProviderStore.get("custom-clear-revoke-url")?.revokeUrl,
1956
- ).toBeNull();
1957
-
1958
- // The serialized --json output should also reflect null, not "".
1959
- const parsed = JSON.parse(stdout);
1960
- expect(parsed.revokeUrl).toBeNull();
1961
- });
1962
-
1963
- test("providers update --revoke-body-template updates only the template (JSON round-trip)", async () => {
1964
- await runCli([
1965
- "providers",
1966
- "register",
1967
- "--provider-key",
1968
- "custom-update-revoke-body",
1969
- "--auth-url",
1970
- "https://example.com/oauth/authorize",
1971
- "--token-url",
1972
- "https://example.com/oauth/token",
1973
- "--json",
1974
- ]);
1975
-
1976
- const { exitCode } = await runCli([
1977
- "providers",
1978
- "update",
1979
- "custom-update-revoke-body",
1980
- "--revoke-body-template",
1981
- '{"token":"{access_token}","client_id":"{client_id}"}',
1982
- "--json",
1983
- ]);
1984
- expect(exitCode).toBe(0);
1985
- const stored = mockProviderStore.get("custom-update-revoke-body");
1986
- expect(stored).toBeDefined();
1987
- expect(JSON.parse(stored?.revokeBodyTemplate as string)).toEqual({
1988
- token: "{access_token}",
1989
- client_id: "{client_id}",
1990
- });
1991
- // revokeUrl should still be null (unchanged).
1992
- expect(stored?.revokeUrl).toBeNull();
1993
- });
1994
-
1995
- test("providers update --revoke-body-template with empty string clears the stored template to null", async () => {
1996
- // Seed an existing custom provider that already has a non-null
1997
- // revokeBodyTemplate set via register.
1998
- await runCli([
1999
- "providers",
2000
- "register",
2001
- "--provider-key",
2002
- "custom-clear-revoke-body",
2003
- "--auth-url",
2004
- "https://example.com/oauth/authorize",
2005
- "--token-url",
2006
- "https://example.com/oauth/token",
2007
- "--revoke-url",
2008
- "https://revoke.example.com",
2009
- "--revoke-body-template",
2010
- '{"token":"{access_token}"}',
2011
- "--json",
2012
- ]);
2013
- const initial = mockProviderStore.get("custom-clear-revoke-body");
2014
- expect(initial?.revokeBodyTemplate).toBeDefined();
2015
- expect(JSON.parse(initial?.revokeBodyTemplate as string)).toEqual({
2016
- token: "{access_token}",
2017
- });
2018
-
2019
- // Pass an empty string to --revoke-body-template — the help text promises
2020
- // this clears the field, so the stored value should end up as null
2021
- // (not the string "null" or a JSON.parse crash).
2022
- const { exitCode, stdout } = await runCli([
2023
- "providers",
2024
- "update",
2025
- "custom-clear-revoke-body",
2026
- "--revoke-body-template",
2027
- "",
2028
- "--json",
2029
- ]);
2030
- expect(exitCode).toBe(0);
2031
- expect(
2032
- mockProviderStore.get("custom-clear-revoke-body")?.revokeBodyTemplate,
2033
- ).toBeNull();
2034
-
2035
- // The serialized --json output should also reflect null.
2036
- const parsed = JSON.parse(stdout);
2037
- expect(parsed.revokeBodyTemplate).toBeNull();
2038
- });
2039
-
2040
- test("providers get google --json includes both fields populated from PR 1's seed data", async () => {
2041
- // Simulate the seeded "google" provider row by overriding mockGetProvider
2042
- // to return a row matching what PR 1's seed inserts.
2043
- const now = Date.now();
2044
- mockGetProvider = (provider: string) => {
2045
- if (provider !== "google") return undefined;
2046
- return {
2047
- provider: "google",
2048
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
2049
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
2050
- refreshUrl: null,
2051
- tokenEndpointAuthMethod: "client_secret_post",
2052
- userinfoUrl: null,
2053
- baseUrl: null,
2054
- defaultScopes: "[]",
2055
- availableScopes: null,
2056
- scopeSeparator: " ",
2057
- authorizeParams: null,
2058
- pingUrl: null,
2059
- pingMethod: null,
2060
- pingHeaders: null,
2061
- pingBody: null,
2062
- revokeUrl: "https://oauth2.googleapis.com/revoke",
2063
- revokeBodyTemplate: JSON.stringify({ token: "{access_token}" }),
2064
- managedServiceConfigKey: null,
2065
- displayLabel: "Google",
2066
- description: null,
2067
- dashboardUrl: null,
2068
- clientIdPlaceholder: null,
2069
- requiresClientSecret: 1,
2070
- loopbackPort: null,
2071
- injectionTemplates: null,
2072
- appType: null,
2073
- setupNotes: null,
2074
- identityUrl: null,
2075
- identityMethod: null,
2076
- identityHeaders: null,
2077
- identityBody: null,
2078
- identityResponsePaths: null,
2079
- identityFormat: null,
2080
- identityOkField: null,
2081
- featureFlag: null,
2082
- createdAt: now,
2083
- updatedAt: now,
2084
- };
2085
- };
2086
-
2087
- const { exitCode, stdout } = await runCli([
2088
- "providers",
2089
- "get",
2090
- "google",
2091
- "--json",
2092
- ]);
2093
- expect(exitCode).toBe(0);
2094
- const parsed = JSON.parse(stdout);
2095
- expect(parsed.revokeUrl).toBe("https://oauth2.googleapis.com/revoke");
2096
- expect(parsed.revokeBodyTemplate).toEqual({ token: "{access_token}" });
2097
- });
2098
- });