@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
@@ -0,0 +1,111 @@
1
+ import { BackendError } from "../util/errors.js";
2
+ import type {
3
+ OAuthConnection,
4
+ OAuthConnectionRequest,
5
+ OAuthConnectionResponse,
6
+ } from "./connection.js";
7
+
8
+ export class CredentialRequiredError extends BackendError {
9
+ constructor(message = "Connection not set up on platform") {
10
+ super(message);
11
+ this.name = "CredentialRequiredError";
12
+ }
13
+ }
14
+
15
+ export class ProviderUnreachableError extends BackendError {
16
+ constructor(message = "Provider is unreachable") {
17
+ super(message);
18
+ this.name = "ProviderUnreachableError";
19
+ }
20
+ }
21
+
22
+ export interface PlatformOAuthConnectionOptions {
23
+ id: string;
24
+ providerKey: string;
25
+ externalId: string;
26
+ accountInfo: string | null;
27
+ grantedScopes: string[];
28
+ assistantId: string;
29
+ platformBaseUrl: string;
30
+ apiKey: string;
31
+ }
32
+
33
+ export class PlatformOAuthConnection implements OAuthConnection {
34
+ readonly id: string;
35
+ readonly providerKey: string;
36
+ readonly externalId: string;
37
+ readonly accountInfo: string | null;
38
+ readonly grantedScopes: string[];
39
+
40
+ private readonly assistantId: string;
41
+ private readonly platformBaseUrl: string;
42
+ private readonly apiKey: string;
43
+
44
+ constructor(options: PlatformOAuthConnectionOptions) {
45
+ this.id = options.id;
46
+ this.providerKey = options.providerKey;
47
+ this.externalId = options.externalId;
48
+ this.accountInfo = options.accountInfo;
49
+ this.grantedScopes = options.grantedScopes;
50
+ this.assistantId = options.assistantId;
51
+ this.platformBaseUrl = options.platformBaseUrl.replace(/\/+$/, "");
52
+ this.apiKey = options.apiKey;
53
+ }
54
+
55
+ async request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse> {
56
+ const providerSlug = this.providerKey.replace(/^integration:/, "");
57
+ const proxyUrl = `${this.platformBaseUrl}/v1/assistants/${this.assistantId}/external-provider-proxy/${providerSlug}/`;
58
+
59
+ const body: Record<string, unknown> = {
60
+ request: {
61
+ method: req.method,
62
+ path: req.path,
63
+ query: req.query ?? {},
64
+ headers: req.headers ?? {},
65
+ body: req.body ?? null,
66
+ ...(req.baseUrl ? { baseUrl: req.baseUrl } : {}),
67
+ },
68
+ };
69
+
70
+ const response = await fetch(proxyUrl, {
71
+ method: "POST",
72
+ headers: {
73
+ Authorization: `Api-Key ${this.apiKey}`,
74
+ "Content-Type": "application/json",
75
+ },
76
+ body: JSON.stringify(body),
77
+ });
78
+
79
+ if (response.status === 424) {
80
+ throw new CredentialRequiredError();
81
+ }
82
+
83
+ if (response.status === 502) {
84
+ throw new ProviderUnreachableError();
85
+ }
86
+
87
+ if (!response.ok) {
88
+ throw new BackendError(
89
+ `Platform proxy returned unexpected status ${response.status}`,
90
+ );
91
+ }
92
+
93
+ const json = (await response.json()) as {
94
+ status: number;
95
+ headers: Record<string, string>;
96
+ body: unknown;
97
+ };
98
+
99
+ return {
100
+ status: json.status,
101
+ headers: json.headers,
102
+ body: json.body,
103
+ };
104
+ }
105
+
106
+ async withToken<T>(_fn: (token: string) => Promise<T>): Promise<T> {
107
+ throw new BackendError(
108
+ "Raw token access is not supported for platform-managed connections. Use connection.request() instead.",
109
+ );
110
+ }
111
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * OAuth provider behavior registry.
3
+ *
4
+ * Contains code-side behavioral configuration for well-known OAuth
5
+ * providers. Protocol-level fields (authUrl, tokenUrl, scopes, etc.)
6
+ * are stored in the `oauth_providers` SQLite table and seeded by
7
+ * `seed-providers.ts`. This module contains only fields that require
8
+ * code references (functions, templates, skill IDs) and cannot be
9
+ * serialised to a DB row.
10
+ */
11
+
12
+ import type { OAuthProviderBehavior } from "./connect-types.js";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Provider behaviors
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
19
+ "integration:gmail": {
20
+ service: "integration:gmail",
21
+ // Google APIs for Gmail/Calendar/Contacts span multiple hosts; register
22
+ // all of them so proxied bash can inject the OAuth bearer token reliably.
23
+ injectionTemplates: [
24
+ {
25
+ hostPattern: "gmail.googleapis.com",
26
+ injectionType: "header",
27
+ headerName: "Authorization",
28
+ valuePrefix: "Bearer ",
29
+ },
30
+ {
31
+ hostPattern: "www.googleapis.com",
32
+ injectionType: "header",
33
+ headerName: "Authorization",
34
+ valuePrefix: "Bearer ",
35
+ },
36
+ {
37
+ hostPattern: "people.googleapis.com",
38
+ injectionType: "header",
39
+ headerName: "Authorization",
40
+ valuePrefix: "Bearer ",
41
+ },
42
+ ],
43
+ setupSkillId: "google-oauth-applescript",
44
+ setup: {
45
+ displayName: "Google (Gmail & Calendar)",
46
+ dashboardUrl: "https://console.cloud.google.com/apis/credentials",
47
+ appType: "Desktop app",
48
+ requiresClientSecret: true,
49
+ },
50
+ },
51
+
52
+ "integration:slack": {
53
+ service: "integration:slack",
54
+ },
55
+
56
+ "integration:notion": {
57
+ service: "integration:notion",
58
+ injectionTemplates: [
59
+ {
60
+ hostPattern: "api.notion.com",
61
+ injectionType: "header",
62
+ headerName: "Authorization",
63
+ valuePrefix: "Bearer ",
64
+ },
65
+ ],
66
+ },
67
+
68
+ "integration:twitter": {
69
+ service: "integration:twitter",
70
+ setup: {
71
+ displayName: "Twitter / X",
72
+ dashboardUrl: "https://developer.x.com/en/portal/dashboard",
73
+ appType: "App",
74
+ requiresClientSecret: false,
75
+ },
76
+ identityVerifier: async (
77
+ accessToken: string,
78
+ ): Promise<string | undefined> => {
79
+ try {
80
+ const resp = await fetch("https://api.x.com/2/users/me", {
81
+ headers: { Authorization: `Bearer ${accessToken}` },
82
+ });
83
+ if (resp.ok) {
84
+ const body = (await resp.json()) as { data?: { username?: string } };
85
+ return body.data?.username ? `@${body.data.username}` : undefined;
86
+ }
87
+ } catch {
88
+ // Non-fatal — identity verification is best-effort
89
+ }
90
+ return undefined;
91
+ },
92
+ },
93
+ };
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Aliases & resolution
97
+ // ---------------------------------------------------------------------------
98
+
99
+ /** Map shorthand aliases to canonical service names. */
100
+ export const SERVICE_ALIASES: Record<string, string> = {
101
+ gmail: "integration:gmail",
102
+ slack: "integration:slack",
103
+ notion: "integration:notion",
104
+ twitter: "integration:twitter",
105
+ };
106
+
107
+ /**
108
+ * Resolve a service name through aliases, then fall back to `integration:`
109
+ * prefix for providers registered in PROVIDER_BEHAVIORS without a
110
+ * SERVICE_ALIASES entry.
111
+ */
112
+ export function resolveService(service: string): string {
113
+ if (SERVICE_ALIASES[service]) return SERVICE_ALIASES[service];
114
+ if (!service.includes(":") && PROVIDER_BEHAVIORS[`integration:${service}`])
115
+ return `integration:${service}`;
116
+ return service;
117
+ }
118
+
119
+ /** Look up a provider behavior by canonical service name. */
120
+ export function getProviderBehavior(
121
+ service: string,
122
+ ): OAuthProviderBehavior | undefined {
123
+ return PROVIDER_BEHAVIORS[service];
124
+ }
@@ -5,7 +5,7 @@
5
5
  * scopes for an OAuth flow based on the provider profile's scope policy.
6
6
  */
7
7
 
8
- import type { OAuthProviderProfile } from "./connect-types.js";
8
+ import type { OAuthScopePolicy } from "./connect-types.js";
9
9
 
10
10
  // ---------------------------------------------------------------------------
11
11
  // Result types
@@ -28,8 +28,15 @@ export type ScopeResolutionResult =
28
28
  * requested scope against the provider's `scopePolicy`.
29
29
  * - Returns a deduplicated union of default + approved requested scopes.
30
30
  */
31
+ /** Minimal shape needed by the scope resolver. */
32
+ export interface ScopeResolverInput {
33
+ service: string;
34
+ defaultScopes: string[];
35
+ scopePolicy: OAuthScopePolicy;
36
+ }
37
+
31
38
  export function resolveScopes(
32
- profile: OAuthProviderProfile,
39
+ profile: ScopeResolverInput,
33
40
  requestedScopes?: string[],
34
41
  ): ScopeResolutionResult {
35
42
  const { defaultScopes, scopePolicy, service } = profile;
@@ -0,0 +1,161 @@
1
+ import { seedProviders } from "./oauth-store.js";
2
+
3
+ /**
4
+ * Protocol-level seed data for each well-known OAuth provider.
5
+ *
6
+ * These values are upserted into the `oauth_providers` SQLite table on
7
+ * every startup so that corrections (e.g. a fixed baseUrl) propagate to
8
+ * existing installations. Code-side behavioral fields (identityVerifier,
9
+ * injectionTemplates, setup, etc.) live in `provider-behaviors.ts` and
10
+ * are never persisted to the DB.
11
+ */
12
+ const PROVIDER_SEED_DATA: Record<
13
+ string,
14
+ {
15
+ providerKey: string;
16
+ authUrl: string;
17
+ tokenUrl: string;
18
+ tokenEndpointAuthMethod?: string;
19
+ userinfoUrl?: string;
20
+ baseUrl?: string;
21
+ defaultScopes: string[];
22
+ scopePolicy: {
23
+ allowAdditionalScopes: boolean;
24
+ allowedOptionalScopes: string[];
25
+ forbiddenScopes: string[];
26
+ };
27
+ extraParams?: Record<string, string>;
28
+ callbackTransport?: string;
29
+ loopbackPort?: number;
30
+ }
31
+ > = {
32
+ "integration:gmail": {
33
+ providerKey: "integration:gmail",
34
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
35
+ tokenUrl: "https://oauth2.googleapis.com/token",
36
+ userinfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
37
+ baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
38
+ defaultScopes: [
39
+ "https://www.googleapis.com/auth/gmail.readonly",
40
+ "https://www.googleapis.com/auth/gmail.modify",
41
+ "https://www.googleapis.com/auth/gmail.send",
42
+ "https://www.googleapis.com/auth/calendar.readonly",
43
+ "https://www.googleapis.com/auth/calendar.events",
44
+ "https://www.googleapis.com/auth/userinfo.email",
45
+ "https://www.googleapis.com/auth/contacts.readonly",
46
+ ],
47
+ scopePolicy: {
48
+ allowAdditionalScopes: false,
49
+ allowedOptionalScopes: [],
50
+ forbiddenScopes: [],
51
+ },
52
+ extraParams: { access_type: "offline", prompt: "consent" },
53
+ callbackTransport: "loopback",
54
+ },
55
+
56
+ "integration:slack": {
57
+ providerKey: "integration:slack",
58
+ authUrl: "https://slack.com/oauth/v2/authorize",
59
+ tokenUrl: "https://slack.com/api/oauth.v2.access",
60
+ baseUrl: "https://slack.com/api",
61
+ defaultScopes: [
62
+ "channels:read",
63
+ "channels:history",
64
+ "groups:read",
65
+ "groups:history",
66
+ "im:read",
67
+ "im:history",
68
+ "im:write",
69
+ "mpim:read",
70
+ "mpim:history",
71
+ "users:read",
72
+ "chat:write",
73
+ "search:read",
74
+ "reactions:write",
75
+ ],
76
+ scopePolicy: {
77
+ allowAdditionalScopes: false,
78
+ allowedOptionalScopes: [],
79
+ forbiddenScopes: [],
80
+ },
81
+ extraParams: {
82
+ user_scope:
83
+ "channels:read,channels:history,groups:read,groups:history,im:read,im:history,im:write,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write",
84
+ },
85
+ callbackTransport: "loopback",
86
+ loopbackPort: 17322,
87
+ },
88
+
89
+ "integration:notion": {
90
+ providerKey: "integration:notion",
91
+ authUrl: "https://api.notion.com/v1/oauth/authorize",
92
+ tokenUrl: "https://api.notion.com/v1/oauth/token",
93
+ baseUrl: "https://api.notion.com",
94
+ defaultScopes: [],
95
+ scopePolicy: {
96
+ allowAdditionalScopes: false,
97
+ allowedOptionalScopes: [],
98
+ forbiddenScopes: [],
99
+ },
100
+ extraParams: { owner: "user" },
101
+ tokenEndpointAuthMethod: "client_secret_basic",
102
+ },
103
+
104
+ "integration:twitter": {
105
+ providerKey: "integration:twitter",
106
+ authUrl: "https://twitter.com/i/oauth2/authorize",
107
+ tokenUrl: "https://api.x.com/2/oauth2/token",
108
+ baseUrl: "https://api.x.com",
109
+ defaultScopes: [
110
+ "tweet.read",
111
+ "tweet.write",
112
+ "users.read",
113
+ "offline.access",
114
+ ],
115
+ scopePolicy: {
116
+ allowAdditionalScopes: false,
117
+ allowedOptionalScopes: [],
118
+ forbiddenScopes: [],
119
+ },
120
+ tokenEndpointAuthMethod: "client_secret_basic",
121
+ callbackTransport: "gateway",
122
+ },
123
+
124
+ // Manual-token providers: these don't use OAuth2 flows but need provider
125
+ // rows so that oauth_app and oauth_connection FK chains can reference them.
126
+ // The authUrl/tokenUrl values are placeholders — never used at runtime.
127
+ slack_channel: {
128
+ providerKey: "slack_channel",
129
+ authUrl: "urn:manual-token",
130
+ tokenUrl: "urn:manual-token",
131
+ baseUrl: "https://slack.com/api",
132
+ defaultScopes: [],
133
+ scopePolicy: {
134
+ allowAdditionalScopes: false,
135
+ allowedOptionalScopes: [],
136
+ forbiddenScopes: [],
137
+ },
138
+ },
139
+
140
+ telegram: {
141
+ providerKey: "telegram",
142
+ authUrl: "urn:manual-token",
143
+ tokenUrl: "urn:manual-token",
144
+ baseUrl: "https://api.telegram.org",
145
+ defaultScopes: [],
146
+ scopePolicy: {
147
+ allowAdditionalScopes: false,
148
+ allowedOptionalScopes: [],
149
+ forbiddenScopes: [],
150
+ },
151
+ },
152
+ };
153
+
154
+ /**
155
+ * Seed the oauth_providers table with well-known provider configurations.
156
+ * Uses INSERT … ON CONFLICT DO UPDATE so seed-data corrections propagate
157
+ * to existing installations. Safe to call on every startup.
158
+ */
159
+ export function seedOAuthProviders(): void {
160
+ seedProviders(Object.values(PROVIDER_SEED_DATA));
161
+ }
@@ -2,8 +2,12 @@
2
2
  * OAuth2 token persistence helper.
3
3
  *
4
4
  * Extracted from vault.ts so it can be reused by both the credential
5
- * vault tool (interactive and deferred paths) and the future OAuth
5
+ * vault tool (interactive and deferred paths) and the OAuth
6
6
  * orchestrator without duplicating storage logic.
7
+ *
8
+ * Writes exclusively to the SQLite tables (oauth_app, oauth_connection)
9
+ * and new-format secure keys (`oauth_app/{id}/...`,
10
+ * `oauth_connection/{id}/...`).
7
11
  */
8
12
 
9
13
  import type {
@@ -14,12 +18,14 @@ import {
14
18
  deleteSecureKeyAsync,
15
19
  setSecureKeyAsync,
16
20
  } from "../security/secure-keys.js";
17
- import {
18
- deleteCredentialMetadata,
19
- upsertCredentialMetadata,
20
- } from "../tools/credentials/metadata-store.js";
21
21
  import type { CredentialInjectionTemplate } from "../tools/credentials/policy-types.js";
22
22
  import { runPostConnectHook } from "../tools/credentials/post-connect-hooks.js";
23
+ import {
24
+ createConnection,
25
+ getConnectionByProvider,
26
+ updateConnection,
27
+ upsertApp,
28
+ } from "./oauth-store.js";
23
29
 
24
30
  // ---------------------------------------------------------------------------
25
31
  // Types
@@ -39,6 +45,8 @@ export interface StoreOAuth2TokensParams {
39
45
  wellKnownInjectionTemplates?: CredentialInjectionTemplate[];
40
46
  /** Fallback account info from an identity verifier (e.g. @username, email). */
41
47
  identityAccountInfo?: string;
48
+ /** Pre-resolved oauth_app ID — skips the upsertApp() call if provided. */
49
+ oauthAppId?: string;
42
50
  }
43
51
 
44
52
  // ---------------------------------------------------------------------------
@@ -49,9 +57,9 @@ export interface StoreOAuth2TokensParams {
49
57
  * Store OAuth2 tokens and associated metadata after a successful flow.
50
58
  *
51
59
  * Persists the access token, optional refresh token, client credentials,
52
- * and metadata (scopes, expiry, account info) into the secure key store
53
- * and credential metadata file. Runs any registered post-connect hook
54
- * for the service.
60
+ * and metadata (scopes, expiry, account info) into the SQLite oauth_app /
61
+ * oauth_connection tables with new-format secure keys. Runs any registered
62
+ * post-connect hook for the service.
55
63
  */
56
64
  export async function storeOAuth2Tokens(
57
65
  params: StoreOAuth2TokensParams,
@@ -63,21 +71,9 @@ export async function storeOAuth2Tokens(
63
71
  rawTokenResponse,
64
72
  clientId,
65
73
  clientSecret,
66
- tokenUrl,
67
- tokenEndpointAuthMethod,
68
74
  userinfoUrl,
69
- allowedTools,
70
- wellKnownInjectionTemplates,
71
75
  } = params;
72
76
 
73
- const tokenStored = await setSecureKeyAsync(
74
- `credential:${service}:access_token`,
75
- tokens.accessToken,
76
- );
77
- if (!tokenStored) {
78
- throw new Error("Failed to store access token in secure storage");
79
- }
80
-
81
77
  const expiresAt = tokens.expiresIn
82
78
  ? Date.now() + tokens.expiresIn * 1000
83
79
  : null;
@@ -97,82 +93,82 @@ export async function storeOAuth2Tokens(
97
93
  }
98
94
  }
99
95
 
100
- // Persist client credentials in keychain for defense in depth
101
- const clientIdStored = await setSecureKeyAsync(
102
- `credential:${service}:client_id`,
103
- clientId,
104
- );
105
- if (!clientIdStored) {
106
- throw new Error("Failed to store client_id in secure storage");
107
- }
108
- if (clientSecret) {
109
- const clientSecretStored = await setSecureKeyAsync(
110
- `credential:${service}:client_secret`,
96
+ const resolvedAccountInfo = accountInfo ?? params.identityAccountInfo;
97
+
98
+ // -------------------------------------------------------------------
99
+ // SQLite oauth_app + oauth_connection + new-format secure keys
100
+ // -------------------------------------------------------------------
101
+
102
+ // 1. Upsert the oauth_app row (or use the pre-resolved ID).
103
+ const app = params.oauthAppId
104
+ ? { id: params.oauthAppId }
105
+ : await upsertApp(service, clientId, clientSecret);
106
+
107
+ // When oauthAppId is pre-resolved, still persist clientSecret if provided.
108
+ if (params.oauthAppId && clientSecret) {
109
+ const stored = await setSecureKeyAsync(
110
+ `oauth_app/${params.oauthAppId}/client_secret`,
111
111
  clientSecret,
112
112
  );
113
- if (!clientSecretStored) {
113
+ if (!stored) {
114
114
  throw new Error("Failed to store client_secret in secure storage");
115
115
  }
116
116
  }
117
117
 
118
- upsertCredentialMetadata(service, "access_token", {
119
- allowedTools: allowedTools ?? [],
120
- expiresAt,
121
- grantedScopes,
122
- oauth2TokenUrl: tokenUrl,
123
- oauth2ClientId: clientId,
124
- oauth2ClientSecret: clientSecret ?? null,
125
- ...(tokenEndpointAuthMethod
126
- ? { oauth2TokenEndpointAuthMethod: tokenEndpointAuthMethod }
127
- : {}),
128
- ...(wellKnownInjectionTemplates
129
- ? { injectionTemplates: wellKnownInjectionTemplates }
130
- : {}),
131
- });
132
-
133
- // Write accountInfo to config using a namespaced key (dynamic import to
134
- // avoid circular dependencies — the config loader may transitively depend
135
- // on credential modules).
136
- const resolvedAccountInfo = accountInfo ?? params.identityAccountInfo;
137
- if (resolvedAccountInfo) {
138
- try {
139
- const {
140
- invalidateConfigCache,
141
- loadRawConfig,
142
- saveRawConfig,
143
- setNestedValue,
144
- } = await import("../config/loader.js");
145
- const raw = loadRawConfig();
146
- setNestedValue(
147
- raw,
148
- `integrations.accountInfo.${service}`,
149
- resolvedAccountInfo,
150
- );
151
- saveRawConfig(raw);
152
- invalidateConfigCache();
153
- } catch {
154
- // Non-fatal — tokens stored even if config write fails
155
- }
118
+ // 2. Upsert oauth_connection — reuse existing active connection for this
119
+ // provider, or create a new one.
120
+ const existingConn = getConnectionByProvider(service);
121
+ let connId: string;
122
+
123
+ const hasRefreshToken = !!tokens.refreshToken;
124
+
125
+ if (existingConn) {
126
+ connId = existingConn.id;
127
+ updateConnection(connId, {
128
+ oauthAppId: app.id,
129
+ accountInfo: resolvedAccountInfo,
130
+ grantedScopes,
131
+ expiresAt,
132
+ hasRefreshToken,
133
+ metadata: rawTokenResponse,
134
+ });
135
+ } else {
136
+ const conn = createConnection({
137
+ oauthAppId: app.id,
138
+ providerKey: service,
139
+ accountInfo: resolvedAccountInfo,
140
+ grantedScopes,
141
+ expiresAt: expiresAt ?? undefined,
142
+ hasRefreshToken,
143
+ metadata: rawTokenResponse,
144
+ });
145
+ connId = conn.id;
156
146
  }
157
147
 
148
+ // 3. Write access_token: oauth_connection/{conn.id}/access_token
149
+ const tokenStored = await setSecureKeyAsync(
150
+ `oauth_connection/${connId}/access_token`,
151
+ tokens.accessToken,
152
+ );
153
+ if (!tokenStored) {
154
+ throw new Error("Failed to store access token in secure storage");
155
+ }
156
+
157
+ // 4. Write or clear refresh_token: oauth_connection/{conn.id}/refresh_token
158
158
  if (tokens.refreshToken) {
159
- const refreshStored = await setSecureKeyAsync(
160
- `credential:${service}:refresh_token`,
159
+ await setSecureKeyAsync(
160
+ `oauth_connection/${connId}/refresh_token`,
161
161
  tokens.refreshToken,
162
162
  );
163
- if (refreshStored) {
164
- upsertCredentialMetadata(service, "refresh_token", {});
165
- }
166
163
  } else {
167
164
  // Re-auth grants that omit refresh_token must clear any stale stored
168
165
  // token — otherwise withValidToken() will attempt refresh with invalid
169
166
  // credentials.
170
- await deleteSecureKeyAsync(`credential:${service}:refresh_token`);
171
- deleteCredentialMetadata(service, "refresh_token");
167
+ await deleteSecureKeyAsync(`oauth_connection/${connId}/refresh_token`);
172
168
  }
173
169
 
174
170
  // Run any provider-specific post-connect actions (e.g. Slack welcome DM)
175
171
  await runPostConnectHook({ service, rawTokenResponse });
176
172
 
177
- return { accountInfo: accountInfo ?? params.identityAccountInfo };
173
+ return { accountInfo: resolvedAccountInfo };
178
174
  }
@@ -48,7 +48,11 @@ function riskCacheKey(
48
48
  workingDir?: string,
49
49
  manifestOverride?: ManifestOverride,
50
50
  ): string {
51
- const inputJson = JSON.stringify(input);
51
+ // Strip `reason` before computing the cache key — it is cosmetic and varies
52
+ // per invocation even for identical tool operations, causing unnecessary
53
+ // cache misses.
54
+ const { reason: _reason, ...cacheableInput } = input;
55
+ const inputJson = JSON.stringify(cacheableInput);
52
56
  const hash = createHash("sha256")
53
57
  .update(inputJson)
54
58
  .update("\0")
@@ -801,12 +805,12 @@ export async function check(
801
805
  }
802
806
 
803
807
  // Workspace mode: auto-allow workspace-scoped operations that don't have
804
- // an explicit rule. Non-workspace operations fall through to risk-based policy.
805
- // High-risk operations always require approval regardless of scope.
808
+ // an explicit rule, but only when risk is Low. Medium and High risk operations
809
+ // fall through to risk-based policy and always require approval.
806
810
  if (
807
811
  permissionsMode === "workspace" &&
808
812
  !matchedRule &&
809
- risk !== RiskLevel.High
813
+ risk === RiskLevel.Low
810
814
  ) {
811
815
  // When sandbox is disabled, bash runs on the host — don't auto-allow
812
816
  const sandboxEnabled = getConfig().sandbox.enabled;
@@ -28,7 +28,6 @@ const COMPUTER_USE_TOOLS = [
28
28
  "computer_use_wait",
29
29
  "computer_use_open_app",
30
30
  "computer_use_run_applescript",
31
- "computer_use_request_control",
32
31
  // computer_use_done and computer_use_respond are terminal signal tools
33
32
  // (RiskLevel.Low) — they don't perform any computer action, so they
34
33
  // should NOT get an 'ask' rule.