@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
@@ -2,20 +2,9 @@
2
2
  * Messaging provider registry — register/lookup providers by platform ID.
3
3
  */
4
4
 
5
- import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
6
- import { getConfig } from "../config/loader.js";
7
- import { getSecureKey } from "../security/secure-keys.js";
5
+ import { isProviderConnected } from "../oauth/oauth-store.js";
8
6
  import type { MessagingProvider } from "./provider.js";
9
7
 
10
- /**
11
- * Per-platform feature flag keys. Platforms not listed here are allowed
12
- * by default (undeclared keys resolve to `true`).
13
- */
14
- const PLATFORM_FLAG_KEYS: Record<string, string> = {
15
- gmail: "feature_flags.messaging.gmail.enabled",
16
- telegram: "feature_flags.messaging.telegram.enabled",
17
- };
18
-
19
8
  const providers = new Map<string, MessagingProvider>();
20
9
 
21
10
  export function registerMessagingProvider(provider: MessagingProvider): void {
@@ -30,32 +19,14 @@ export function getMessagingProvider(id: string): MessagingProvider {
30
19
  `Messaging provider "${id}" not found. Available: ${available}`,
31
20
  );
32
21
  }
33
- assertPlatformEnabled(id);
34
22
  return provider;
35
23
  }
36
24
 
37
- export function isPlatformEnabled(platformId: string): boolean {
38
- const flagKey = PLATFORM_FLAG_KEYS[platformId];
39
- if (!flagKey) return true;
40
- return isAssistantFeatureFlagEnabled(flagKey, getConfig());
41
- }
42
-
43
- function assertPlatformEnabled(platformId: string): void {
44
- if (!isPlatformEnabled(platformId)) {
45
- throw new Error(
46
- `The ${platformId} platform is not enabled. Enable it in Settings > Features.`,
47
- );
48
- }
49
- }
50
-
51
25
  /** Return all registered providers that have stored credentials. */
52
26
  export function getConnectedProviders(): MessagingProvider[] {
53
27
  return Array.from(providers.values()).filter((p) => {
54
28
  if (p.isConnected) return p.isConnected();
55
- const token = getSecureKey(
56
- `credential:${p.credentialService}:access_token`,
57
- );
58
- return token !== undefined;
29
+ return isProviderConnected(p.credentialService);
59
30
  });
60
31
  }
61
32
 
@@ -351,11 +351,6 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
351
351
  title: "Voice Response",
352
352
  body: str(payload.preview, "A voice response is ready"),
353
353
  }),
354
-
355
- "ride_shotgun.invitation": (payload) => ({
356
- title: "Ride Shotgun",
357
- body: str(payload.message, "You have been invited to ride shotgun"),
358
- }),
359
354
  };
360
355
 
361
356
  /**
@@ -37,7 +37,10 @@ export const NOTIFICATION_SOURCE_EVENT_NAMES = [
37
37
  id: "user.send_notification",
38
38
  description: "User-initiated notification via assistant tool",
39
39
  },
40
- { id: "schedule.notify", description: "Scheduled notification triggered (one-shot or recurring)" },
40
+ {
41
+ id: "schedule.notify",
42
+ description: "Scheduled notification triggered (one-shot or recurring)",
43
+ },
41
44
  { id: "schedule.complete", description: "Scheduled task finished running" },
42
45
  {
43
46
  id: "guardian.question",
@@ -89,10 +92,6 @@ export const NOTIFICATION_SOURCE_EVENT_NAMES = [
89
92
  id: "voice.response_ready",
90
93
  description: "Voice response ready for playback",
91
94
  },
92
- {
93
- id: "ride_shotgun.invitation",
94
- description: "Invitation to ride shotgun on a session",
95
- },
96
95
  ] as const;
97
96
 
98
97
  export type NotificationSourceEventName =
@@ -0,0 +1,537 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { mkdirSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ afterAll,
7
+ afterEach,
8
+ beforeAll,
9
+ beforeEach,
10
+ describe,
11
+ expect,
12
+ mock,
13
+ test,
14
+ } from "bun:test";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Mock logger
18
+ // ---------------------------------------------------------------------------
19
+
20
+ mock.module("../util/logger.js", () => ({
21
+ getLogger: () =>
22
+ new Proxy({} as Record<string, unknown>, {
23
+ get: () => () => {},
24
+ }),
25
+ }));
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Use encrypted backend with a temp store path
29
+ // ---------------------------------------------------------------------------
30
+
31
+ import { _setStorePath } from "../security/encrypted-store.js";
32
+ import { _resetBackend } from "../security/secure-keys.js";
33
+
34
+ const TEST_DIR = join(
35
+ tmpdir(),
36
+ `vellum-byo-conn-test-${randomBytes(4).toString("hex")}`,
37
+ );
38
+ const STORE_PATH = join(TEST_DIR, "keys.enc");
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Mock OAuth2 token refresh
42
+ // ---------------------------------------------------------------------------
43
+
44
+ let mockRefreshOAuth2Token: ReturnType<
45
+ typeof mock<
46
+ () => Promise<{
47
+ accessToken: string;
48
+ expiresIn: number;
49
+ refreshToken?: string;
50
+ }>
51
+ >
52
+ >;
53
+
54
+ mock.module("../security/oauth2.js", () => {
55
+ mockRefreshOAuth2Token = mock(() =>
56
+ Promise.resolve({
57
+ accessToken: "refreshed-access-token",
58
+ expiresIn: 3600,
59
+ }),
60
+ );
61
+ return {
62
+ refreshOAuth2Token: mockRefreshOAuth2Token,
63
+ };
64
+ });
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Mock oauth-store — token-manager reads refresh config from SQLite
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /** Mutable per-test map of provider connections for getConnectionByProvider */
71
+ const mockConnections = new Map<
72
+ string,
73
+ {
74
+ id: string;
75
+ providerKey: string;
76
+ oauthAppId: string;
77
+ expiresAt: number | null;
78
+ grantedScopes?: string;
79
+ accountInfo?: string | null;
80
+ }
81
+ >();
82
+ const mockApps = new Map<
83
+ string,
84
+ { id: string; providerKey: string; clientId: string }
85
+ >();
86
+ const mockProviders = new Map<
87
+ string,
88
+ {
89
+ key: string;
90
+ tokenUrl: string;
91
+ tokenEndpointAuthMethod?: string;
92
+ baseUrl?: string;
93
+ }
94
+ >();
95
+
96
+ mock.module("./oauth-store.js", () => ({
97
+ getConnectionByProvider: (service: string) => mockConnections.get(service),
98
+ getApp: (id: string) => mockApps.get(id),
99
+ getProvider: (key: string) => mockProviders.get(key),
100
+ updateConnection: () => {},
101
+ getMostRecentAppByProvider: () => undefined,
102
+ listConnections: () => [],
103
+ }));
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Imports (after mocks)
107
+ // ---------------------------------------------------------------------------
108
+
109
+ import { setSecureKey } from "../security/secure-keys.js";
110
+ import {
111
+ _resetInflightRefreshes,
112
+ _resetRefreshBreakers,
113
+ } from "../security/token-manager.js";
114
+ import {
115
+ _setMetadataPath,
116
+ upsertCredentialMetadata,
117
+ } from "../tools/credentials/metadata-store.js";
118
+ import { BYOOAuthConnection } from "./byo-connection.js";
119
+ import { resolveOAuthConnection } from "./connection-resolver.js";
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Mock fetch
123
+ // ---------------------------------------------------------------------------
124
+
125
+ const originalFetch = globalThis.fetch;
126
+ let mockFetch: ReturnType<typeof mock<any>>;
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Setup / teardown
130
+ // ---------------------------------------------------------------------------
131
+
132
+ beforeAll(() => {
133
+ mkdirSync(TEST_DIR, { recursive: true });
134
+ });
135
+
136
+ afterAll(() => {
137
+ rmSync(TEST_DIR, { recursive: true, force: true });
138
+ globalThis.fetch = originalFetch;
139
+ });
140
+
141
+ beforeEach(() => {
142
+ _setStorePath(STORE_PATH);
143
+ _setMetadataPath(join(TEST_DIR, "metadata.json"));
144
+ _resetBackend();
145
+ _resetRefreshBreakers();
146
+ _resetInflightRefreshes();
147
+ // Clear mock oauth-store maps
148
+ mockConnections.clear();
149
+ mockApps.clear();
150
+ mockProviders.clear();
151
+
152
+ // Default mock fetch returning 200 JSON
153
+ mockFetch = mock(() =>
154
+ Promise.resolve(
155
+ new Response(JSON.stringify({ ok: true }), {
156
+ status: 200,
157
+ headers: { "content-type": "application/json" },
158
+ }),
159
+ ),
160
+ );
161
+ globalThis.fetch = mockFetch as unknown as typeof fetch;
162
+ });
163
+
164
+ afterEach(() => {
165
+ globalThis.fetch = originalFetch;
166
+ // Clean up store for next test
167
+ try {
168
+ rmSync(STORE_PATH, { force: true });
169
+ rmSync(join(TEST_DIR, "metadata.json"), { force: true });
170
+ } catch {
171
+ // ignore
172
+ }
173
+ });
174
+
175
+ function setupCredential(
176
+ service: string,
177
+ opts?: { expiresAt?: number; grantedScopes?: string[] },
178
+ ) {
179
+ // Seed mock oauth-store maps so token-manager can resolve refresh config
180
+ const appId = `app-${service}`;
181
+ const connId = `conn-${service}`;
182
+ mockProviders.set(service, {
183
+ key: service,
184
+ tokenUrl: "https://oauth2.googleapis.com/token",
185
+ // Only well-known providers (gmail) have a baseUrl; custom services don't
186
+ baseUrl:
187
+ service === "integration:gmail"
188
+ ? "https://gmail.googleapis.com/gmail/v1/users/me"
189
+ : undefined,
190
+ });
191
+ mockApps.set(appId, {
192
+ id: appId,
193
+ providerKey: service,
194
+ clientId: "test-client-id",
195
+ });
196
+ mockConnections.set(service, {
197
+ id: connId,
198
+ providerKey: service,
199
+ oauthAppId: appId,
200
+ expiresAt: opts?.expiresAt ?? Date.now() + 3600 * 1000,
201
+ grantedScopes: JSON.stringify(opts?.grantedScopes ?? ["read", "write"]),
202
+ accountInfo: null,
203
+ });
204
+ // Store access token in oauth-store key format
205
+ setSecureKey(`oauth_connection/${connId}/access_token`, "test-access-token");
206
+ // Store refresh token and client_secret in secure keys (token-manager reads them)
207
+ setSecureKey(
208
+ `oauth_connection/${connId}/refresh_token`,
209
+ "test-refresh-token",
210
+ );
211
+ setSecureKey(`oauth_app/${appId}/client_secret`, "test-client-secret");
212
+ upsertCredentialMetadata(service, "access_token", {});
213
+ }
214
+
215
+ function createConnection(service = "integration:gmail"): BYOOAuthConnection {
216
+ return new BYOOAuthConnection({
217
+ id: "test-cred-id",
218
+ providerKey: service,
219
+ baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
220
+ accountInfo: null,
221
+ grantedScopes: ["read", "write"],
222
+ credentialService: service,
223
+ });
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // Tests
228
+ // ---------------------------------------------------------------------------
229
+
230
+ describe("BYOOAuthConnection", () => {
231
+ describe("request()", () => {
232
+ test("makes authenticated request with Bearer token", async () => {
233
+ setupCredential("integration:gmail");
234
+ const conn = createConnection();
235
+
236
+ const result = await conn.request({
237
+ method: "GET",
238
+ path: "/messages",
239
+ });
240
+
241
+ expect(result.status).toBe(200);
242
+ expect(result.body).toEqual({ ok: true });
243
+ expect(mockFetch).toHaveBeenCalledTimes(1);
244
+
245
+ const [url, init] = mockFetch.mock.calls[0];
246
+ expect(url).toBe(
247
+ "https://gmail.googleapis.com/gmail/v1/users/me/messages",
248
+ );
249
+ const headers = (init as RequestInit).headers as Headers;
250
+ expect(headers.get("Authorization")).toBe("Bearer test-access-token");
251
+ // GET requests have no body, so Content-Type should not be set
252
+ expect(headers.has("Content-Type")).toBe(false);
253
+ expect((init as RequestInit).method).toBe("GET");
254
+ });
255
+
256
+ test("appends query parameters", async () => {
257
+ setupCredential("integration:gmail");
258
+ const conn = createConnection();
259
+
260
+ await conn.request({
261
+ method: "GET",
262
+ path: "/messages",
263
+ query: { maxResults: "10", labelIds: "INBOX" },
264
+ });
265
+
266
+ const [url] = mockFetch.mock.calls[0];
267
+ const parsed = new URL(url as string);
268
+ expect(parsed.searchParams.get("maxResults")).toBe("10");
269
+ expect(parsed.searchParams.get("labelIds")).toBe("INBOX");
270
+ });
271
+
272
+ test("uses per-request baseUrl override", async () => {
273
+ setupCredential("integration:gmail");
274
+ const conn = createConnection();
275
+
276
+ await conn.request({
277
+ method: "GET",
278
+ path: "/calendars",
279
+ baseUrl: "https://www.googleapis.com/calendar/v3",
280
+ });
281
+
282
+ const [url] = mockFetch.mock.calls[0];
283
+ expect(url).toBe("https://www.googleapis.com/calendar/v3/calendars");
284
+ });
285
+
286
+ test("sends JSON body for POST requests", async () => {
287
+ setupCredential("integration:gmail");
288
+ const conn = createConnection();
289
+
290
+ await conn.request({
291
+ method: "POST",
292
+ path: "/messages/send",
293
+ body: { raw: "base64-encoded-email" },
294
+ });
295
+
296
+ const [, init] = mockFetch.mock.calls[0];
297
+ expect((init as RequestInit).body).toBe(
298
+ JSON.stringify({ raw: "base64-encoded-email" }),
299
+ );
300
+ expect((init as RequestInit).method).toBe("POST");
301
+ // POST requests with a body should include Content-Type
302
+ const headers = (init as RequestInit).headers as Headers;
303
+ expect(headers.get("Content-Type")).toBe("application/json");
304
+ });
305
+
306
+ test("retries once on 401 response", async () => {
307
+ setupCredential("integration:gmail");
308
+ const conn = createConnection();
309
+
310
+ // First call returns 401, second returns 200
311
+ let callCount = 0;
312
+ globalThis.fetch = mock(() => {
313
+ callCount++;
314
+ if (callCount === 1) {
315
+ return Promise.resolve(new Response("Unauthorized", { status: 401 }));
316
+ }
317
+ return Promise.resolve(
318
+ new Response(JSON.stringify({ ok: true }), { status: 200 }),
319
+ );
320
+ }) as unknown as typeof fetch;
321
+
322
+ const result = await conn.request({
323
+ method: "GET",
324
+ path: "/messages",
325
+ });
326
+
327
+ expect(result.status).toBe(200);
328
+ expect(result.body).toEqual({ ok: true });
329
+ expect(callCount).toBe(2);
330
+ // Verify refresh was called
331
+ expect(mockRefreshOAuth2Token).toHaveBeenCalled();
332
+ });
333
+
334
+ test("handles empty response body", async () => {
335
+ setupCredential("integration:gmail");
336
+ const conn = createConnection();
337
+
338
+ globalThis.fetch = mock(() =>
339
+ Promise.resolve(new Response("", { status: 204 })),
340
+ ) as unknown as typeof fetch;
341
+
342
+ const result = await conn.request({
343
+ method: "DELETE",
344
+ path: "/messages/123",
345
+ });
346
+
347
+ expect(result.status).toBe(204);
348
+ expect(result.body).toBeNull();
349
+ });
350
+
351
+ test("handles non-JSON response body", async () => {
352
+ setupCredential("integration:gmail");
353
+ const conn = createConnection();
354
+
355
+ globalThis.fetch = mock(() =>
356
+ Promise.resolve(new Response("plain text response", { status: 200 })),
357
+ ) as unknown as typeof fetch;
358
+
359
+ const result = await conn.request({
360
+ method: "GET",
361
+ path: "/raw",
362
+ });
363
+
364
+ expect(result.status).toBe(200);
365
+ expect(result.body).toBe("plain text response");
366
+ });
367
+
368
+ test("returns response headers", async () => {
369
+ setupCredential("integration:gmail");
370
+ const conn = createConnection();
371
+
372
+ globalThis.fetch = mock(() =>
373
+ Promise.resolve(
374
+ new Response(JSON.stringify({}), {
375
+ status: 200,
376
+ headers: {
377
+ "x-ratelimit-remaining": "99",
378
+ "content-type": "application/json",
379
+ },
380
+ }),
381
+ ),
382
+ ) as unknown as typeof fetch;
383
+
384
+ const result = await conn.request({
385
+ method: "GET",
386
+ path: "/messages",
387
+ });
388
+
389
+ expect(result.headers["x-ratelimit-remaining"]).toBe("99");
390
+ });
391
+
392
+ test("includes custom request headers", async () => {
393
+ setupCredential("integration:gmail");
394
+ const conn = createConnection();
395
+
396
+ await conn.request({
397
+ method: "GET",
398
+ path: "/messages",
399
+ headers: { "X-Custom-Header": "custom-value" },
400
+ });
401
+
402
+ const [, init] = mockFetch.mock.calls[0];
403
+ const headers = (init as RequestInit).headers as Headers;
404
+ expect(headers.get("X-Custom-Header")).toBe("custom-value");
405
+ expect(headers.get("Authorization")).toBe("Bearer test-access-token");
406
+ });
407
+ });
408
+
409
+ describe("proactive token refresh", () => {
410
+ test("refreshes token when near expiry (within 5-minute buffer)", async () => {
411
+ // Set token to expire in 2 minutes (within 5-min buffer)
412
+ setupCredential("integration:gmail", {
413
+ expiresAt: Date.now() + 2 * 60 * 1000,
414
+ });
415
+ const conn = createConnection();
416
+
417
+ await conn.request({
418
+ method: "GET",
419
+ path: "/messages",
420
+ });
421
+
422
+ // Token should have been refreshed proactively
423
+ expect(mockRefreshOAuth2Token).toHaveBeenCalled();
424
+
425
+ // The request should use the refreshed token
426
+ const [, init] = mockFetch.mock.calls[0];
427
+ const headers = (init as RequestInit).headers as Headers;
428
+ expect(headers.get("Authorization")).toBe(
429
+ "Bearer refreshed-access-token",
430
+ );
431
+ });
432
+ });
433
+
434
+ describe("withToken()", () => {
435
+ test("provides valid token to callback", async () => {
436
+ setupCredential("integration:gmail");
437
+ const conn = createConnection();
438
+
439
+ const result = await conn.withToken(async (token) => {
440
+ return `got-${token}`;
441
+ });
442
+
443
+ expect(result).toBe("got-test-access-token");
444
+ });
445
+
446
+ test("retries callback on 401 error", async () => {
447
+ setupCredential("integration:gmail");
448
+ const conn = createConnection();
449
+
450
+ let callCount = 0;
451
+ const result = await conn.withToken(async (token) => {
452
+ callCount++;
453
+ if (callCount === 1) {
454
+ const err = new Error("Unauthorized");
455
+ (err as Error & { status: number }).status = 401;
456
+ throw err;
457
+ }
458
+ return `got-${token}`;
459
+ });
460
+
461
+ expect(callCount).toBe(2);
462
+ expect(result).toBe("got-refreshed-access-token");
463
+ expect(mockRefreshOAuth2Token).toHaveBeenCalled();
464
+ });
465
+ });
466
+
467
+ describe("missing credential", () => {
468
+ test("throws when no access token exists", async () => {
469
+ const conn = createConnection();
470
+
471
+ await expect(
472
+ conn.request({ method: "GET", path: "/messages" }),
473
+ ).rejects.toThrow(/No access token found/);
474
+ });
475
+ });
476
+ });
477
+
478
+ describe("resolveOAuthConnection", () => {
479
+ test("returns a BYOOAuthConnection for valid credential", () => {
480
+ setupCredential("integration:gmail");
481
+ const conn = resolveOAuthConnection("integration:gmail");
482
+
483
+ expect(conn).toBeInstanceOf(BYOOAuthConnection);
484
+ expect(conn.providerKey).toBe("integration:gmail");
485
+ expect(conn.grantedScopes).toEqual(["read", "write"]);
486
+ });
487
+
488
+ test("throws when no credential metadata exists", () => {
489
+ expect(() => resolveOAuthConnection("integration:unknown")).toThrow(
490
+ /No credential found for "integration:unknown"/,
491
+ );
492
+ });
493
+
494
+ test("throws when no base URL configured", () => {
495
+ setupCredential("integration:custom-service");
496
+ expect(() => resolveOAuthConnection("integration:custom-service")).toThrow(
497
+ /No base URL configured for "integration:custom-service"/,
498
+ );
499
+ });
500
+
501
+ test("resolves base URL via app's canonical providerKey for custom credential_service", () => {
502
+ // Set up a well-known provider with a baseUrl
503
+ mockProviders.set("github", {
504
+ key: "github",
505
+ tokenUrl: "https://github.com/login/oauth/access_token",
506
+ baseUrl: "https://api.github.com",
507
+ });
508
+ // The custom credential service has no provider entry of its own
509
+ // (getProvider("integration:github-work") returns undefined)
510
+
511
+ // App points to the canonical "github" provider
512
+ const appId = "app-github-work";
513
+ mockApps.set(appId, {
514
+ id: appId,
515
+ providerKey: "github",
516
+ clientId: "test-client-id",
517
+ });
518
+
519
+ // Connection uses the custom credential service as its providerKey
520
+ const connId = "conn-github-work";
521
+ mockConnections.set("integration:github-work", {
522
+ id: connId,
523
+ providerKey: "integration:github-work",
524
+ oauthAppId: appId,
525
+ expiresAt: Date.now() + 3600 * 1000,
526
+ grantedScopes: JSON.stringify(["repo"]),
527
+ accountInfo: null,
528
+ });
529
+ setSecureKey(`oauth_connection/${connId}/access_token`, "ghp-test-token");
530
+
531
+ const conn = resolveOAuthConnection("integration:github-work");
532
+
533
+ expect(conn).toBeInstanceOf(BYOOAuthConnection);
534
+ expect(conn.providerKey).toBe("integration:github-work");
535
+ expect(conn.grantedScopes).toEqual(["repo"]);
536
+ });
537
+ });