@vellumai/assistant 0.4.46 → 0.4.49

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 (382) hide show
  1. package/ARCHITECTURE.md +7 -7
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/security.md +5 -5
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +1 -1
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/anthropic-provider.test.ts +156 -0
  12. package/src/__tests__/approval-cascade.test.ts +810 -0
  13. package/src/__tests__/approval-primitive.test.ts +0 -1
  14. package/src/__tests__/approval-routes-http.test.ts +2 -0
  15. package/src/__tests__/assistant-attachments.test.ts +12 -34
  16. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  17. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  18. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  19. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  20. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  21. package/src/__tests__/channel-guardian.test.ts +0 -2
  22. package/src/__tests__/channel-readiness-routes.test.ts +35 -25
  23. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  24. package/src/__tests__/checker.test.ts +9 -29
  25. package/src/__tests__/cli.test.ts +23 -0
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-watcher.test.ts +0 -1
  29. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  30. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  31. package/src/__tests__/context-token-estimator.test.ts +196 -13
  32. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  33. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  34. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  36. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  37. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  38. package/src/__tests__/credential-broker.test.ts +2 -1
  39. package/src/__tests__/credential-metadata-store.test.ts +239 -26
  40. package/src/__tests__/credential-resolve.test.ts +5 -4
  41. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  42. package/src/__tests__/credential-security-invariants.test.ts +111 -7
  43. package/src/__tests__/credential-vault-unit.test.ts +287 -54
  44. package/src/__tests__/credential-vault.test.ts +406 -12
  45. package/src/__tests__/credentials-cli.test.ts +82 -6
  46. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  47. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  48. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/gemini-image-service.test.ts +75 -45
  51. package/src/__tests__/gemini-provider.test.ts +9 -6
  52. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  53. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  54. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  55. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  56. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  57. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  58. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  59. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  60. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  61. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  62. package/src/__tests__/heartbeat-service.test.ts +0 -1
  63. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  64. package/src/__tests__/host-shell-tool.test.ts +27 -15
  65. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  66. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  67. package/src/__tests__/integration-status.test.ts +38 -25
  68. package/src/__tests__/intent-routing.test.ts +0 -1
  69. package/src/__tests__/invite-routes-http.test.ts +10 -9
  70. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  71. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  72. package/src/__tests__/media-generate-image.test.ts +63 -2
  73. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  74. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +373 -14
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +756 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  83. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  84. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  85. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  86. package/src/__tests__/recording-handler.test.ts +3 -4
  87. package/src/__tests__/registry.test.ts +2 -2
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  91. package/src/__tests__/schema-transforms.test.ts +226 -0
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  95. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  96. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  97. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  98. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  99. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  100. package/src/__tests__/sequence-store.test.ts +0 -1
  101. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  102. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  103. package/src/__tests__/skill-include-graph.test.ts +66 -0
  104. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  105. package/src/__tests__/skill-load-tool.test.ts +149 -1
  106. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  107. package/src/__tests__/skills-uninstall.test.ts +3 -3
  108. package/src/__tests__/skills.test.ts +3 -12
  109. package/src/__tests__/slack-channel-config.test.ts +76 -11
  110. package/src/__tests__/slack-share-routes.test.ts +17 -14
  111. package/src/__tests__/system-prompt.test.ts +0 -1
  112. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  113. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  114. package/src/__tests__/terminal-tools.test.ts +4 -3
  115. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  116. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  117. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  118. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  119. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  120. package/src/__tests__/tool-executor.test.ts +0 -1
  121. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  122. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  123. package/src/__tests__/trust-store.test.ts +1 -22
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  126. package/src/__tests__/twilio-config.test.ts +2 -1
  127. package/src/__tests__/twilio-provider.test.ts +4 -2
  128. package/src/__tests__/twilio-routes.test.ts +5 -20
  129. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  130. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  131. package/src/agent/ax-tree-compaction.test.ts +235 -0
  132. package/src/agent/loop.ts +76 -130
  133. package/src/calls/call-domain.ts +8 -10
  134. package/src/calls/relay-server.ts +9 -13
  135. package/src/calls/twilio-config.ts +4 -8
  136. package/src/calls/twilio-provider.ts +2 -1
  137. package/src/calls/twilio-rest.ts +2 -1
  138. package/src/calls/twilio-routes.ts +1 -2
  139. package/src/calls/voice-ingress-preflight.ts +1 -1
  140. package/src/cli/commands/browser-relay.ts +46 -15
  141. package/src/cli/commands/completions.ts +0 -3
  142. package/src/cli/commands/credentials.ts +110 -23
  143. package/src/cli/commands/oauth/apps.ts +255 -0
  144. package/src/cli/commands/oauth/connections.ts +299 -0
  145. package/src/cli/commands/oauth/index.ts +52 -0
  146. package/src/cli/commands/oauth/providers.ts +242 -0
  147. package/src/cli/commands/skills.ts +4 -338
  148. package/src/cli/program.ts +1 -5
  149. package/src/cli/reference.ts +1 -3
  150. package/src/cli.ts +3 -2
  151. package/src/config/assistant-feature-flags.ts +0 -3
  152. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  153. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  154. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  155. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  156. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  157. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  158. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  159. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  160. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  161. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  162. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  163. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  164. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  165. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  166. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  167. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  168. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  169. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  170. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  171. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  172. package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
  173. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  174. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  175. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  176. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  177. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  178. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  179. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  180. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  181. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  182. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  183. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  184. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  185. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  186. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  187. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  188. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  189. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  190. package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
  191. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  192. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  193. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  194. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  195. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  196. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  197. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  198. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  199. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  200. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  201. package/src/config/env-registry.ts +14 -83
  202. package/src/config/env.ts +11 -50
  203. package/src/config/feature-flag-registry.json +16 -16
  204. package/src/config/schema.ts +3 -1
  205. package/src/config/skills.ts +21 -2
  206. package/src/context/image-dimensions.ts +229 -0
  207. package/src/context/token-estimator.ts +75 -12
  208. package/src/context/window-manager.ts +49 -10
  209. package/src/daemon/assistant-attachments.ts +1 -13
  210. package/src/daemon/guardian-action-generators.ts +4 -5
  211. package/src/daemon/handlers/config-ingress.ts +8 -33
  212. package/src/daemon/handlers/config-slack-channel.ts +76 -56
  213. package/src/daemon/handlers/config-telegram.ts +53 -24
  214. package/src/daemon/handlers/sessions.ts +10 -24
  215. package/src/daemon/handlers/shared.ts +0 -130
  216. package/src/daemon/host-cu-proxy.ts +401 -0
  217. package/src/daemon/lifecycle.ts +39 -63
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/computer-use.ts +2 -119
  220. package/src/daemon/message-types/host-cu.ts +19 -0
  221. package/src/daemon/message-types/integrations.ts +1 -0
  222. package/src/daemon/message-types/messages.ts +3 -0
  223. package/src/daemon/server.ts +14 -21
  224. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  225. package/src/daemon/session-attachments.ts +1 -2
  226. package/src/daemon/session-messaging.ts +3 -1
  227. package/src/daemon/session-slash.ts +1 -1
  228. package/src/daemon/session-surfaces.ts +40 -28
  229. package/src/daemon/session-tool-setup.ts +20 -11
  230. package/src/daemon/session.ts +139 -16
  231. package/src/daemon/tool-side-effects.ts +2 -8
  232. package/src/daemon/watch-handler.ts +2 -2
  233. package/src/email/providers/index.ts +2 -1
  234. package/src/events/tool-metrics-listener.ts +2 -2
  235. package/src/hooks/manager.ts +1 -4
  236. package/src/inbound/public-ingress-urls.ts +7 -7
  237. package/src/instrument.ts +15 -1
  238. package/src/logfire.ts +16 -5
  239. package/src/media/app-icon-generator.ts +30 -4
  240. package/src/media/avatar-router.ts +26 -3
  241. package/src/media/gemini-image-service.ts +28 -2
  242. package/src/memory/conversation-key-store.ts +21 -0
  243. package/src/memory/db-init.ts +4 -0
  244. package/src/memory/guardian-action-store.ts +1 -1
  245. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  246. package/src/memory/migrations/index.ts +1 -0
  247. package/src/memory/schema/guardian.ts +1 -1
  248. package/src/memory/schema/index.ts +1 -0
  249. package/src/memory/schema/oauth.ts +65 -0
  250. package/src/messaging/provider.ts +19 -13
  251. package/src/messaging/providers/gmail/adapter.ts +40 -23
  252. package/src/messaging/providers/gmail/client.ts +283 -122
  253. package/src/messaging/providers/gmail/people-client.ts +32 -24
  254. package/src/messaging/providers/slack/adapter.ts +29 -19
  255. package/src/messaging/providers/slack/client.ts +265 -78
  256. package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
  257. package/src/messaging/providers/whatsapp/adapter.ts +17 -11
  258. package/src/messaging/registry.ts +2 -31
  259. package/src/notifications/copy-composer.ts +0 -5
  260. package/src/notifications/signal.ts +4 -5
  261. package/src/oauth/byo-connection.test.ts +537 -0
  262. package/src/oauth/byo-connection.ts +128 -0
  263. package/src/oauth/connect-orchestrator.ts +139 -56
  264. package/src/oauth/connect-types.ts +17 -23
  265. package/src/oauth/connection-resolver.ts +58 -0
  266. package/src/oauth/connection.ts +38 -0
  267. package/src/oauth/manual-token-connection.ts +104 -0
  268. package/src/oauth/oauth-store.ts +496 -0
  269. package/src/oauth/platform-connection.test.ts +192 -0
  270. package/src/oauth/platform-connection.ts +111 -0
  271. package/src/oauth/provider-behaviors.ts +124 -0
  272. package/src/oauth/scope-policy.ts +9 -2
  273. package/src/oauth/seed-providers.ts +161 -0
  274. package/src/oauth/token-persistence.ts +74 -78
  275. package/src/permissions/checker.ts +8 -4
  276. package/src/permissions/defaults.ts +0 -1
  277. package/src/permissions/prompter.ts +10 -1
  278. package/src/permissions/trust-store.ts +13 -0
  279. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  280. package/src/prompts/system-prompt.ts +70 -45
  281. package/src/providers/anthropic/client.ts +133 -24
  282. package/src/providers/gemini/client.ts +15 -6
  283. package/src/providers/managed-proxy/constants.ts +2 -2
  284. package/src/providers/managed-proxy/context.ts +5 -1
  285. package/src/providers/ratelimit.ts +17 -0
  286. package/src/providers/registry.ts +2 -2
  287. package/src/providers/retry.ts +1 -27
  288. package/src/runtime/AGENTS.md +17 -0
  289. package/src/runtime/auth/route-policy.ts +0 -3
  290. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  291. package/src/runtime/channel-readiness-service.ts +168 -195
  292. package/src/runtime/channel-readiness-types.ts +4 -0
  293. package/src/runtime/channel-reply-delivery.ts +0 -40
  294. package/src/runtime/gateway-client.ts +0 -7
  295. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  296. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  297. package/src/runtime/guardian-action-message-composer.ts +3 -23
  298. package/src/runtime/http-server.ts +17 -10
  299. package/src/runtime/http-types.ts +2 -3
  300. package/src/runtime/middleware/rate-limiter.ts +74 -20
  301. package/src/runtime/middleware/twilio-validation.ts +1 -11
  302. package/src/runtime/pending-interactions.ts +14 -12
  303. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  304. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  305. package/src/runtime/routes/conversation-routes.ts +73 -19
  306. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  307. package/src/runtime/routes/events-routes.ts +21 -11
  308. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  309. package/src/runtime/routes/host-cu-routes.ts +97 -0
  310. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  311. package/src/runtime/routes/integrations/slack/share.ts +6 -6
  312. package/src/runtime/routes/integrations/twilio.ts +6 -5
  313. package/src/runtime/routes/log-export-routes.ts +126 -8
  314. package/src/runtime/routes/secret-routes.ts +3 -2
  315. package/src/runtime/routes/settings-routes.ts +113 -48
  316. package/src/runtime/routes/surface-action-routes.ts +1 -1
  317. package/src/runtime/routes/watch-routes.ts +128 -0
  318. package/src/schedule/integration-status.ts +10 -8
  319. package/src/security/credential-key.ts +14 -0
  320. package/src/security/keychain-broker-client.ts +5 -6
  321. package/src/security/oauth2.ts +1 -1
  322. package/src/security/token-manager.ts +145 -43
  323. package/src/skills/catalog-install.ts +358 -0
  324. package/src/skills/include-graph.ts +32 -0
  325. package/src/telegram/bot-username.ts +2 -3
  326. package/src/tools/apps/definitions.ts +0 -5
  327. package/src/tools/assets/materialize.ts +0 -5
  328. package/src/tools/assets/search.ts +0 -5
  329. package/src/tools/browser/headless-browser.ts +1 -67
  330. package/src/tools/browser/network-recorder.ts +1 -1
  331. package/src/tools/browser/network-recording-types.ts +1 -1
  332. package/src/tools/claude-code/claude-code.ts +0 -5
  333. package/src/tools/computer-use/definitions.ts +46 -11
  334. package/src/tools/computer-use/registry.ts +4 -5
  335. package/src/tools/credentials/broker.ts +5 -4
  336. package/src/tools/credentials/metadata-store.ts +22 -74
  337. package/src/tools/credentials/resolve.ts +2 -1
  338. package/src/tools/credentials/vault.ts +139 -151
  339. package/src/tools/filesystem/edit.ts +1 -6
  340. package/src/tools/filesystem/read.ts +0 -5
  341. package/src/tools/filesystem/write.ts +1 -6
  342. package/src/tools/host-filesystem/edit.ts +1 -6
  343. package/src/tools/host-filesystem/read.ts +1 -6
  344. package/src/tools/host-filesystem/write.ts +1 -6
  345. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  346. package/src/tools/memory/definitions.ts +0 -5
  347. package/src/tools/network/web-fetch.ts +0 -5
  348. package/src/tools/network/web-search.ts +0 -5
  349. package/src/tools/registry.ts +2 -7
  350. package/src/tools/schema-transforms.ts +99 -0
  351. package/src/tools/skills/load.ts +62 -8
  352. package/src/tools/swarm/delegate.ts +0 -5
  353. package/src/tools/system/avatar-generator.ts +0 -5
  354. package/src/tools/ui-surface/definitions.ts +0 -15
  355. package/src/tools/watch/screen-watch.ts +0 -5
  356. package/src/tools/watch/watch-state.ts +0 -12
  357. package/src/util/logger.ts +7 -41
  358. package/src/util/platform.ts +9 -28
  359. package/src/version.ts +10 -0
  360. package/src/watcher/providers/github.ts +51 -52
  361. package/src/watcher/providers/gmail.ts +88 -80
  362. package/src/watcher/providers/google-calendar.ts +94 -86
  363. package/src/watcher/providers/linear.ts +87 -93
  364. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  365. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  366. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  367. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  368. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  369. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  370. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  371. package/src/cli/commands/dev.ts +0 -129
  372. package/src/cli/commands/map.ts +0 -391
  373. package/src/cli/commands/oauth.ts +0 -77
  374. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  375. package/src/daemon/computer-use-session.ts +0 -1020
  376. package/src/daemon/ride-shotgun-handler.ts +0 -567
  377. package/src/oauth/provider-profiles.ts +0 -192
  378. package/src/prompts/computer-use-prompt.ts +0 -98
  379. package/src/runtime/routes/computer-use-routes.ts +0 -641
  380. package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
  381. package/src/runtime/telegram-streaming-delivery.ts +0 -383
  382. package/src/tools/computer-use/request-computer-control.ts +0 -61
@@ -17,6 +17,7 @@ let mockGenerateResult = {
17
17
  resolvedModel: "gemini-2.5-flash-image",
18
18
  };
19
19
  let mockGenerateError: Error | null = null;
20
+ let lastGenerateCredentials: unknown = null;
20
21
 
21
22
  mock.module("../config/loader.js", () => ({
22
23
  getConfig: () => ({
@@ -27,7 +28,11 @@ mock.module("../config/loader.js", () => ({
27
28
  }));
28
29
 
29
30
  mock.module("../media/gemini-image-service.js", () => ({
30
- generateImage: async (_apiKey: string, _request: Record<string, unknown>) => {
31
+ generateImage: async (
32
+ credentials: unknown,
33
+ _request: Record<string, unknown>,
34
+ ) => {
35
+ lastGenerateCredentials = credentials;
31
36
  if (mockGenerateError) throw mockGenerateError;
32
37
  return mockGenerateResult;
33
38
  },
@@ -37,6 +42,18 @@ mock.module("../media/gemini-image-service.js", () => ({
37
42
  },
38
43
  }));
39
44
 
45
+ let mockManagedBaseUrl: string | undefined;
46
+ let mockManagedProxyContext = {
47
+ enabled: false,
48
+ platformBaseUrl: "",
49
+ assistantApiKey: "",
50
+ };
51
+
52
+ mock.module("../providers/managed-proxy/context.js", () => ({
53
+ buildManagedBaseUrl: () => mockManagedBaseUrl,
54
+ resolveManagedProxyContext: () => mockManagedProxyContext,
55
+ }));
56
+
40
57
  let mockAttachments: Array<{
41
58
  id: string;
42
59
  assistantId: string;
@@ -138,6 +155,13 @@ beforeEach(() => {
138
155
  };
139
156
  mockGenerateError = null;
140
157
  mockAttachments = [];
158
+ lastGenerateCredentials = null;
159
+ mockManagedBaseUrl = undefined;
160
+ mockManagedProxyContext = {
161
+ enabled: false,
162
+ platformBaseUrl: "",
163
+ assistantApiKey: "",
164
+ };
141
165
  });
142
166
 
143
167
  const fakeContext = {
@@ -153,7 +177,7 @@ describe("image-studio skill script wrapper", () => {
153
177
  expect(getTool("media_generate_image")).toBeUndefined();
154
178
  });
155
179
 
156
- test("returns error when no API key is configured", async () => {
180
+ test("returns error when no API key and no managed proxy", async () => {
157
181
  mockApiKey = undefined;
158
182
 
159
183
  const result = await run({ prompt: "a cat" }, fakeContext);
@@ -162,6 +186,43 @@ describe("image-studio skill script wrapper", () => {
162
186
  expect(result.content).toContain("No Gemini API key");
163
187
  });
164
188
 
189
+ test("falls back to managed proxy when no API key is configured", async () => {
190
+ mockApiKey = undefined;
191
+ mockManagedBaseUrl = "https://platform.example.com/v1/runtime-proxy/vertex";
192
+ mockManagedProxyContext = {
193
+ enabled: true,
194
+ platformBaseUrl: "https://platform.example.com",
195
+ assistantApiKey: "managed-key-123",
196
+ };
197
+
198
+ const result = await run({ prompt: "a hippo" }, fakeContext);
199
+
200
+ expect(result.isError).toBe(false);
201
+ expect(result.content).toContain("Generated 1 image");
202
+ expect(lastGenerateCredentials).toEqual({
203
+ type: "managed-proxy",
204
+ assistantApiKey: "managed-key-123",
205
+ baseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
206
+ });
207
+ });
208
+
209
+ test("prefers direct API key over managed proxy", async () => {
210
+ mockApiKey = "direct-key";
211
+ mockManagedBaseUrl = "https://platform.example.com/v1/runtime-proxy/vertex";
212
+ mockManagedProxyContext = {
213
+ enabled: true,
214
+ platformBaseUrl: "https://platform.example.com",
215
+ assistantApiKey: "managed-key-123",
216
+ };
217
+
218
+ await run({ prompt: "a cat" }, fakeContext);
219
+
220
+ expect(lastGenerateCredentials).toEqual({
221
+ type: "direct",
222
+ apiKey: "direct-key",
223
+ });
224
+ });
225
+
165
226
  test("returns generated image with contentBlocks", async () => {
166
227
  const result = await run({ prompt: "a sunset" }, fakeContext);
167
228
 
@@ -62,6 +62,7 @@ mock.module("../config/loader.js", () => ({
62
62
 
63
63
  // Credential resolver and secure key mocks — must be set up before
64
64
  // session-manager is imported so the proxy uses our test data.
65
+ import { credentialKey } from "../security/credential-key.js";
65
66
  import type { ResolvedCredential } from "../tools/credentials/resolve.js";
66
67
 
67
68
  let resolveByIdResults = new Map<string, ResolvedCredential | undefined>();
@@ -154,7 +155,7 @@ function makeResolved(
154
155
  credentialId,
155
156
  service,
156
157
  field,
157
- storageKey: `credential:${service}:${field}`,
158
+ storageKey: credentialKey(service, field),
158
159
  injectionTemplates: templates,
159
160
  metadata: {
160
161
  credentialId,
@@ -357,7 +358,10 @@ describe("Story E2E: selfie yesterday -> generated image today", () => {
357
358
  };
358
359
  const resolved = makeResolved("cred-story", [tpl]);
359
360
  resolveByIdResults.set("cred-story", resolved);
360
- secureKeyValues.set("credential:test-service:api-key", "fal_test_secret");
361
+ secureKeyValues.set(
362
+ credentialKey("test-service", "api-key"),
363
+ "fal_test_secret",
364
+ );
361
365
 
362
366
  // Drive the proxy step through the bash tool — the actual integration path.
363
367
  // -x "$HTTP_PROXY" forces curl to use the proxy explicitly (macOS curl
@@ -381,7 +385,7 @@ describe("Story E2E: selfie yesterday -> generated image today", () => {
381
385
 
382
386
  await stopAllSessions();
383
387
  resolveByIdResults.delete("cred-story");
384
- secureKeyValues.delete("credential:test-service:api-key");
388
+ secureKeyValues.delete(credentialKey("test-service", "api-key"));
385
389
  } finally {
386
390
  echo.server.close();
387
391
  }
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import type { MessagingProvider } from "../messaging/provider.js";
4
4
  import type { SendOptions } from "../messaging/provider-types.js";
5
+ import type { OAuthConnection } from "../oauth/connection.js";
5
6
 
6
7
  const sendMessageMock = mock(async (..._args: unknown[]) => ({
7
8
  id: "msg-1",
@@ -23,19 +24,16 @@ const provider: MessagingProvider = {
23
24
  getHistory: async () => [],
24
25
  search: async () => ({ total: 0, messages: [], hasMore: false }),
25
26
  sendMessage: (
26
- token: string,
27
+ connectionOrToken: OAuthConnection | string,
27
28
  conversationId: string,
28
29
  text: string,
29
30
  options?: SendOptions,
30
- ) => sendMessageMock(token, conversationId, text, options),
31
+ ) => sendMessageMock(connectionOrToken, conversationId, text, options),
31
32
  };
32
33
 
33
34
  mock.module("../config/bundled-skills/messaging/tools/shared.js", () => ({
34
35
  resolveProvider: () => provider,
35
- withProviderToken: async (
36
- _provider: MessagingProvider,
37
- fn: (token: string) => Promise<unknown>,
38
- ) => fn("provider-token"),
36
+ getProviderConnection: () => "provider-token",
39
37
  ok: (content: string) => ({ content, isError: false }),
40
38
  err: (content: string) => ({ content, isError: true }),
41
39
  extractHeader: () => "",
@@ -13,7 +13,6 @@ mock.module("../util/logger.js", () => ({
13
13
  new Proxy({} as Record<string, unknown>, {
14
14
  get: () => () => {},
15
15
  }),
16
- isDebug: () => false,
17
16
  truncateForLog: (v: string) => v,
18
17
  }));
19
18
 
@@ -2,6 +2,8 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import { Command } from "commander";
4
4
 
5
+ import { credentialKey } from "../security/credential-key.js";
6
+
5
7
  // ---------------------------------------------------------------------------
6
8
  // Mock state
7
9
  // ---------------------------------------------------------------------------
@@ -11,6 +13,28 @@ let mockWithValidToken: <T>(
11
13
  cb: (token: string) => Promise<T>,
12
14
  ) => Promise<T>;
13
15
 
16
+ // Disconnect mock state
17
+ let mockListProviders: () => Array<Record<string, unknown>> = () => [];
18
+ let secureKeyStore = new Map<string, string>();
19
+ let metadataStore: Array<{
20
+ credentialId: string;
21
+ service: string;
22
+ field: string;
23
+ allowedTools: string[];
24
+ allowedDomains: string[];
25
+ createdAt: number;
26
+ updatedAt: number;
27
+ }> = [];
28
+ let disconnectOAuthProviderCalls: string[] = [];
29
+ let disconnectOAuthProviderResult: "disconnected" | "not-found" | "error" =
30
+ "not-found";
31
+ let idCounter = 0;
32
+
33
+ function nextUUID(): string {
34
+ idCounter += 1;
35
+ return `00000000-0000-0000-0000-${String(idCounter).padStart(12, "0")}`;
36
+ }
37
+
14
38
  // ---------------------------------------------------------------------------
15
39
  // Mock token-manager
16
40
  // ---------------------------------------------------------------------------
@@ -32,19 +56,77 @@ mock.module("../security/token-manager.js", () => ({
32
56
  },
33
57
  }));
34
58
 
59
+ // ---------------------------------------------------------------------------
60
+ // Mock oauth-store (stateful for disconnect tests)
61
+ // ---------------------------------------------------------------------------
62
+
63
+ mock.module("../oauth/oauth-store.js", () => ({
64
+ disconnectOAuthProvider: async (
65
+ providerKey: string,
66
+ ): Promise<"disconnected" | "not-found" | "error"> => {
67
+ disconnectOAuthProviderCalls.push(providerKey);
68
+ return disconnectOAuthProviderResult;
69
+ },
70
+ getConnection: () => undefined,
71
+ getConnectionByProvider: () => undefined,
72
+ listConnections: () => [],
73
+ deleteConnection: () => false,
74
+ // Stubs required by apps.ts and providers.ts (transitively loaded via oauth/index.ts)
75
+ upsertApp: async () => ({}),
76
+ getApp: () => undefined,
77
+ getAppByProviderAndClientId: () => undefined,
78
+ getMostRecentAppByProvider: () => undefined,
79
+ listApps: () => [],
80
+ deleteApp: async () => false,
81
+ getProvider: () => undefined,
82
+ listProviders: () => mockListProviders(),
83
+ registerProvider: () => ({}),
84
+ seedProviders: () => {},
85
+ createConnection: () => ({}),
86
+ isProviderConnected: () => false,
87
+ updateConnection: () => ({}),
88
+ }));
89
+
35
90
  // Stub out transitive dependencies that token-manager would normally pull in
36
91
  mock.module("../security/secure-keys.js", () => ({
37
92
  getSecureKey: () => undefined,
38
93
  setSecureKey: () => true,
39
94
  getSecureKeyAsync: async () => undefined,
40
95
  setSecureKeyAsync: async () => true,
41
- deleteSecureKey: () => "not-found",
96
+ deleteSecureKey: (account: string) => {
97
+ if (secureKeyStore.has(account)) {
98
+ secureKeyStore.delete(account);
99
+ return "deleted" as const;
100
+ }
101
+ return "not-found" as const;
102
+ },
103
+ deleteSecureKeyAsync: async (account: string) => {
104
+ if (secureKeyStore.has(account)) {
105
+ secureKeyStore.delete(account);
106
+ return "deleted" as const;
107
+ }
108
+ return "not-found" as const;
109
+ },
110
+ listSecureKeys: () => [...secureKeyStore.keys()],
111
+ getBackendType: () => "encrypted",
112
+ isDowngradedFromKeychain: () => false,
113
+ _resetBackend: () => {},
114
+ _setBackend: () => {},
42
115
  }));
43
116
 
44
117
  mock.module("../tools/credentials/metadata-store.js", () => ({
118
+ assertMetadataWritable: () => {},
45
119
  getCredentialMetadata: () => undefined,
46
120
  upsertCredentialMetadata: () => ({}),
47
121
  listCredentialMetadata: () => [],
122
+ deleteCredentialMetadata: (service: string, field: string): boolean => {
123
+ const idx = metadataStore.findIndex(
124
+ (c) => c.service === service && c.field === field,
125
+ );
126
+ if (idx === -1) return false;
127
+ metadataStore.splice(idx, 1);
128
+ return true;
129
+ },
48
130
  }));
49
131
 
50
132
  mock.module("../util/logger.js", () => ({
@@ -66,7 +148,7 @@ mock.module("../util/logger.js", () => ({
66
148
  // Import the module under test (after mocks are registered)
67
149
  // ---------------------------------------------------------------------------
68
150
 
69
- const { registerOAuthCommand } = await import("../cli/commands/oauth.js");
151
+ const { registerOAuthCommand } = await import("../cli/commands/oauth/index.js");
70
152
 
71
153
  // ---------------------------------------------------------------------------
72
154
  // Test helper
@@ -117,43 +199,61 @@ async function runCli(
117
199
  // Tests
118
200
  // ---------------------------------------------------------------------------
119
201
 
120
- describe("assistant oauth token", () => {
202
+ describe("assistant oauth connections token <provider-key>", () => {
121
203
  beforeEach(() => {
122
204
  mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
205
+ secureKeyStore = new Map();
206
+ metadataStore = [];
207
+ disconnectOAuthProviderCalls = [];
208
+ disconnectOAuthProviderResult = "not-found";
209
+ idCounter = 0;
123
210
  });
124
211
 
125
212
  test("prints bare token in human mode", async () => {
126
- const { exitCode, stdout } = await runCli(["token", "twitter"]);
213
+ const { exitCode, stdout } = await runCli([
214
+ "connections",
215
+ "token",
216
+ "integration:twitter",
217
+ ]);
127
218
  expect(exitCode).toBe(0);
128
219
  expect(stdout).toBe("mock-access-token-xyz\n");
129
220
  });
130
221
 
131
222
  test("prints JSON in --json mode", async () => {
132
- const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
223
+ const { exitCode, stdout } = await runCli([
224
+ "connections",
225
+ "token",
226
+ "integration:twitter",
227
+ "--json",
228
+ ]);
133
229
  expect(exitCode).toBe(0);
134
230
  const parsed = JSON.parse(stdout);
135
231
  expect(parsed).toEqual({ ok: true, token: "mock-access-token-xyz" });
136
232
  });
137
233
 
138
- test("qualifies service name with integration: prefix", async () => {
234
+ test("passes provider key directly to withValidToken", async () => {
139
235
  let capturedService: string | undefined;
140
236
  mockWithValidToken = async (service, cb) => {
141
237
  capturedService = service;
142
238
  return cb("tok");
143
239
  };
144
240
 
145
- await runCli(["token", "twitter"]);
241
+ await runCli(["connections", "token", "integration:twitter"]);
146
242
  expect(capturedService).toBe("integration:twitter");
147
243
  });
148
244
 
149
- test("works with other service names", async () => {
245
+ test("works with other provider keys", async () => {
150
246
  let capturedService: string | undefined;
151
247
  mockWithValidToken = async (service, cb) => {
152
248
  capturedService = service;
153
249
  return cb("gmail-token");
154
250
  };
155
251
 
156
- const { exitCode, stdout } = await runCli(["token", "gmail"]);
252
+ const { exitCode, stdout } = await runCli([
253
+ "connections",
254
+ "token",
255
+ "integration:gmail",
256
+ ]);
157
257
  expect(exitCode).toBe(0);
158
258
  expect(stdout).toBe("gmail-token\n");
159
259
  expect(capturedService).toBe("integration:gmail");
@@ -166,7 +266,12 @@ describe("assistant oauth token", () => {
166
266
  );
167
267
  };
168
268
 
169
- const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
269
+ const { exitCode, stdout } = await runCli([
270
+ "connections",
271
+ "token",
272
+ "integration:twitter",
273
+ "--json",
274
+ ]);
170
275
  expect(exitCode).toBe(1);
171
276
  const parsed = JSON.parse(stdout);
172
277
  expect(parsed.ok).toBe(false);
@@ -180,7 +285,12 @@ describe("assistant oauth token", () => {
180
285
  );
181
286
  };
182
287
 
183
- const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
288
+ const { exitCode, stdout } = await runCli([
289
+ "connections",
290
+ "token",
291
+ "integration:twitter",
292
+ "--json",
293
+ ]);
184
294
  expect(exitCode).toBe(1);
185
295
  const parsed = JSON.parse(stdout);
186
296
  expect(parsed.ok).toBe(false);
@@ -191,13 +301,262 @@ describe("assistant oauth token", () => {
191
301
  // Simulate withValidToken refreshing and returning a new token
192
302
  mockWithValidToken = async (_service, cb) => cb("refreshed-new-token");
193
303
 
194
- const { exitCode, stdout } = await runCli(["token", "twitter"]);
304
+ const { exitCode, stdout } = await runCli([
305
+ "connections",
306
+ "token",
307
+ "integration:twitter",
308
+ ]);
195
309
  expect(exitCode).toBe(0);
196
310
  expect(stdout).toBe("refreshed-new-token\n");
197
311
  });
198
312
 
199
- test("missing service argument exits non-zero", async () => {
200
- const { exitCode } = await runCli(["token"]);
313
+ test("missing provider-key argument exits non-zero", async () => {
314
+ const { exitCode } = await runCli(["connections", "token"]);
201
315
  expect(exitCode).not.toBe(0);
202
316
  });
203
317
  });
318
+
319
+ // ---------------------------------------------------------------------------
320
+ // disconnect
321
+ // ---------------------------------------------------------------------------
322
+
323
+ describe("assistant oauth connections disconnect <provider-key>", () => {
324
+ beforeEach(() => {
325
+ mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
326
+ secureKeyStore = new Map();
327
+ metadataStore = [];
328
+ disconnectOAuthProviderCalls = [];
329
+ disconnectOAuthProviderResult = "not-found";
330
+ idCounter = 0;
331
+ });
332
+
333
+ test("succeeds when an OAuth connection exists", async () => {
334
+ disconnectOAuthProviderResult = "disconnected";
335
+
336
+ const result = await runCli([
337
+ "connections",
338
+ "disconnect",
339
+ "integration:gmail",
340
+ "--json",
341
+ ]);
342
+ expect(result.exitCode).toBe(0);
343
+ const parsed = JSON.parse(result.stdout);
344
+ expect(parsed.ok).toBe(true);
345
+ expect(parsed.service).toBe("integration:gmail");
346
+
347
+ // disconnectOAuthProvider should have been called with the full provider key
348
+ expect(disconnectOAuthProviderCalls).toEqual(["integration:gmail"]);
349
+ });
350
+
351
+ test("reports not-found when nothing exists", async () => {
352
+ const result = await runCli([
353
+ "connections",
354
+ "disconnect",
355
+ "integration:gmail",
356
+ "--json",
357
+ ]);
358
+ expect(result.exitCode).toBe(1);
359
+ const parsed = JSON.parse(result.stdout);
360
+ expect(parsed.ok).toBe(false);
361
+ expect(parsed.error).toContain("No OAuth connection or credentials");
362
+ expect(parsed.error).toContain("integration:gmail");
363
+ });
364
+
365
+ test("cleans up legacy credential keys if present", async () => {
366
+ // Seed legacy credential keys (no OAuth connection)
367
+ const legacyFields = [
368
+ "access_token",
369
+ "refresh_token",
370
+ "client_id",
371
+ "client_secret",
372
+ ];
373
+ for (const field of legacyFields) {
374
+ secureKeyStore.set(
375
+ credentialKey("integration:gmail", field),
376
+ `legacy_${field}_value`,
377
+ );
378
+ metadataStore.push({
379
+ credentialId: nextUUID(),
380
+ service: "integration:gmail",
381
+ field,
382
+ allowedTools: [],
383
+ allowedDomains: [],
384
+ createdAt: Date.now(),
385
+ updatedAt: Date.now(),
386
+ });
387
+ }
388
+
389
+ const result = await runCli([
390
+ "connections",
391
+ "disconnect",
392
+ "integration:gmail",
393
+ "--json",
394
+ ]);
395
+ expect(result.exitCode).toBe(0);
396
+ const parsed = JSON.parse(result.stdout);
397
+ expect(parsed.ok).toBe(true);
398
+ expect(parsed.service).toBe("integration:gmail");
399
+
400
+ // All legacy keys should be removed
401
+ for (const field of legacyFields) {
402
+ expect(
403
+ secureKeyStore.has(credentialKey("integration:gmail", field)),
404
+ ).toBe(false);
405
+ expect(
406
+ metadataStore.find(
407
+ (m) => m.service === "integration:gmail" && m.field === field,
408
+ ),
409
+ ).toBeUndefined();
410
+ }
411
+ });
412
+
413
+ test("cleans up both OAuth connection and legacy keys when both exist", async () => {
414
+ // Seed OAuth connection
415
+ disconnectOAuthProviderResult = "disconnected";
416
+
417
+ // Seed a legacy credential key
418
+ secureKeyStore.set(
419
+ credentialKey("integration:gmail", "access_token"),
420
+ "legacy_token",
421
+ );
422
+ metadataStore.push({
423
+ credentialId: nextUUID(),
424
+ service: "integration:gmail",
425
+ field: "access_token",
426
+ allowedTools: [],
427
+ allowedDomains: [],
428
+ createdAt: Date.now(),
429
+ updatedAt: Date.now(),
430
+ });
431
+
432
+ const result = await runCli([
433
+ "connections",
434
+ "disconnect",
435
+ "integration:gmail",
436
+ "--json",
437
+ ]);
438
+ expect(result.exitCode).toBe(0);
439
+ const parsed = JSON.parse(result.stdout);
440
+ expect(parsed.ok).toBe(true);
441
+
442
+ // Both should be cleaned up
443
+ expect(disconnectOAuthProviderCalls).toEqual(["integration:gmail"]);
444
+ expect(
445
+ secureKeyStore.has(credentialKey("integration:gmail", "access_token")),
446
+ ).toBe(false);
447
+ });
448
+ });
449
+
450
+ // ---------------------------------------------------------------------------
451
+ // providers list
452
+ // ---------------------------------------------------------------------------
453
+
454
+ describe("assistant oauth providers list", () => {
455
+ const fakeProviders = [
456
+ {
457
+ providerKey: "integration:gmail",
458
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
459
+ tokenUrl: "https://oauth2.googleapis.com/token",
460
+ defaultScopes: "[]",
461
+ scopePolicy: "{}",
462
+ extraParams: null,
463
+ createdAt: "2025-01-01T00:00:00.000Z",
464
+ updatedAt: "2025-01-01T00:00:00.000Z",
465
+ },
466
+ {
467
+ providerKey: "integration:google-calendar",
468
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
469
+ tokenUrl: "https://oauth2.googleapis.com/token",
470
+ defaultScopes: "[]",
471
+ scopePolicy: "{}",
472
+ extraParams: null,
473
+ createdAt: "2025-01-01T00:00:00.000Z",
474
+ updatedAt: "2025-01-01T00:00:00.000Z",
475
+ },
476
+ {
477
+ providerKey: "integration:slack",
478
+ authUrl: "https://slack.com/oauth/v2/authorize",
479
+ tokenUrl: "https://slack.com/api/oauth.v2.access",
480
+ defaultScopes: "[]",
481
+ scopePolicy: "{}",
482
+ extraParams: null,
483
+ createdAt: "2025-01-01T00:00:00.000Z",
484
+ updatedAt: "2025-01-01T00:00:00.000Z",
485
+ },
486
+ {
487
+ providerKey: "integration:twitter",
488
+ authUrl: "https://twitter.com/i/oauth2/authorize",
489
+ tokenUrl: "https://api.twitter.com/2/oauth2/token",
490
+ defaultScopes: "[]",
491
+ scopePolicy: "{}",
492
+ extraParams: null,
493
+ createdAt: "2025-01-01T00:00:00.000Z",
494
+ updatedAt: "2025-01-01T00:00:00.000Z",
495
+ },
496
+ ];
497
+
498
+ beforeEach(() => {
499
+ mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
500
+ mockListProviders = () => fakeProviders;
501
+ secureKeyStore = new Map();
502
+ metadataStore = [];
503
+ disconnectOAuthProviderCalls = [];
504
+ disconnectOAuthProviderResult = "not-found";
505
+ idCounter = 0;
506
+ });
507
+
508
+ test("returns all providers when no --provider-key is given", async () => {
509
+ const { exitCode, stdout } = await runCli(["providers", "list", "--json"]);
510
+ expect(exitCode).toBe(0);
511
+ const parsed = JSON.parse(stdout);
512
+ expect(parsed).toHaveLength(4);
513
+ const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
514
+ expect(keys).toContain("integration:gmail");
515
+ expect(keys).toContain("integration:google-calendar");
516
+ expect(keys).toContain("integration:slack");
517
+ expect(keys).toContain("integration:twitter");
518
+ });
519
+
520
+ test("filters by single --provider-key value", async () => {
521
+ const { exitCode, stdout } = await runCli([
522
+ "providers",
523
+ "list",
524
+ "--provider-key",
525
+ "gmail",
526
+ "--json",
527
+ ]);
528
+ expect(exitCode).toBe(0);
529
+ const parsed = JSON.parse(stdout);
530
+ expect(parsed).toHaveLength(1);
531
+ expect(parsed[0].providerKey).toBe("integration:gmail");
532
+ });
533
+
534
+ test("filters by comma-separated OR values", async () => {
535
+ const { exitCode, stdout } = await runCli([
536
+ "providers",
537
+ "list",
538
+ "--provider-key",
539
+ "gmail,google",
540
+ "--json",
541
+ ]);
542
+ expect(exitCode).toBe(0);
543
+ const parsed = JSON.parse(stdout);
544
+ expect(parsed).toHaveLength(2);
545
+ const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
546
+ expect(keys).toContain("integration:gmail");
547
+ expect(keys).toContain("integration:google-calendar");
548
+ });
549
+
550
+ test("returns empty array when comma-separated filter has no matches", async () => {
551
+ const { exitCode, stdout } = await runCli([
552
+ "providers",
553
+ "list",
554
+ "--provider-key",
555
+ "notion,linear",
556
+ "--json",
557
+ ]);
558
+ expect(exitCode).toBe(0);
559
+ const parsed = JSON.parse(stdout);
560
+ expect(parsed).toHaveLength(0);
561
+ });
562
+ });