@vellumai/assistant 0.4.48 → 0.4.50

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 (423) hide show
  1. package/ARCHITECTURE.md +26 -35
  2. package/README.md +5 -26
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/memory.md +180 -119
  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 +2 -2
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/agent-loop.test.ts +3 -1
  12. package/src/__tests__/anthropic-provider.test.ts +249 -2
  13. package/src/__tests__/approval-cascade.test.ts +796 -0
  14. package/src/__tests__/approval-primitive.test.ts +0 -1
  15. package/src/__tests__/approval-routes-http.test.ts +4 -0
  16. package/src/__tests__/assistant-attachments.test.ts +12 -34
  17. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  18. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  22. package/src/__tests__/channel-guardian.test.ts +0 -2
  23. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  24. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  25. package/src/__tests__/checker.test.ts +13 -20
  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-schema.test.ts +1 -68
  29. package/src/__tests__/config-watcher.test.ts +0 -1
  30. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  31. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  32. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  33. package/src/__tests__/context-token-estimator.test.ts +196 -13
  34. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  35. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  36. package/src/__tests__/conversation-routes-guardian-reply.test.ts +152 -0
  37. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  38. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  39. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  40. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  41. package/src/__tests__/credential-vault-unit.test.ts +284 -49
  42. package/src/__tests__/credential-vault.test.ts +150 -16
  43. package/src/__tests__/credentials-cli.test.ts +71 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  45. package/src/__tests__/date-context.test.ts +93 -77
  46. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  47. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  48. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  51. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  52. package/src/__tests__/guardian-routing-invariants.test.ts +93 -1
  53. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  54. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  55. package/src/__tests__/heartbeat-service.test.ts +0 -1
  56. package/src/__tests__/history-repair.test.ts +245 -0
  57. package/src/__tests__/host-cu-proxy.test.ts +791 -0
  58. package/src/__tests__/host-shell-tool.test.ts +27 -15
  59. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  60. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  61. package/src/__tests__/integration-status.test.ts +32 -51
  62. package/src/__tests__/intent-routing.test.ts +0 -1
  63. package/src/__tests__/invite-redemption-service.test.ts +65 -1
  64. package/src/__tests__/invite-routes-http.test.ts +10 -9
  65. package/src/__tests__/keychain-broker-client.test.ts +14 -46
  66. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  68. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  69. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  70. package/src/__tests__/memory-regressions.test.ts +477 -2841
  71. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  72. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  73. package/src/__tests__/mime-builder.test.ts +28 -0
  74. package/src/__tests__/native-web-search.test.ts +1 -0
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +941 -15
  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 +870 -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-streaming.benchmark.test.ts +0 -1
  83. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  84. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  85. package/src/__tests__/recording-handler.test.ts +3 -4
  86. package/src/__tests__/registry.test.ts +2 -3
  87. package/src/__tests__/relay-server.test.ts +46 -1
  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__/schedule-tools.test.ts +32 -0
  91. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  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-certs.test.ts +1 -1
  95. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  96. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  97. package/src/__tests__/secure-keys.test.ts +7 -2
  98. package/src/__tests__/send-endpoint-busy.test.ts +24 -6
  99. package/src/__tests__/sequence-store.test.ts +0 -1
  100. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  101. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  102. package/src/__tests__/session-agent-loop.test.ts +19 -15
  103. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  104. package/src/__tests__/session-error.test.ts +124 -2
  105. package/src/__tests__/session-history-web-search.test.ts +918 -0
  106. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  107. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  108. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  109. package/src/__tests__/session-queue.test.ts +37 -27
  110. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  111. package/src/__tests__/session-slash-known.test.ts +1 -15
  112. package/src/__tests__/session-slash-queue.test.ts +1 -15
  113. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  114. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  115. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  116. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  117. package/src/__tests__/skill-include-graph.test.ts +66 -0
  118. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  119. package/src/__tests__/skill-load-tool.test.ts +149 -1
  120. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  121. package/src/__tests__/skills-install-extract.test.ts +93 -0
  122. package/src/__tests__/skills-uninstall.test.ts +1 -1
  123. package/src/__tests__/skills.test.ts +3 -3
  124. package/src/__tests__/skillssh-registry.test.ts +451 -0
  125. package/src/__tests__/slack-channel-config.test.ts +67 -3
  126. package/src/__tests__/slack-share-routes.test.ts +17 -19
  127. package/src/__tests__/system-prompt.test.ts +0 -1
  128. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  129. package/src/__tests__/terminal-tools.test.ts +4 -3
  130. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  131. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  132. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  135. package/src/__tests__/tool-executor.test.ts +0 -1
  136. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  137. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  138. package/src/__tests__/trust-store.test.ts +7 -13
  139. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  140. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  141. package/src/__tests__/twilio-routes.test.ts +0 -16
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  143. package/src/__tests__/voice-invite-redemption.test.ts +32 -1
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  145. package/src/agent/ax-tree-compaction.test.ts +286 -0
  146. package/src/agent/loop.ts +104 -131
  147. package/src/approvals/AGENTS.md +1 -1
  148. package/src/approvals/guardian-request-resolvers.ts +14 -2
  149. package/src/bundler/compiler-tools.ts +66 -2
  150. package/src/calls/call-domain.ts +133 -6
  151. package/src/calls/call-store.ts +6 -0
  152. package/src/calls/relay-server.ts +52 -18
  153. package/src/calls/relay-setup-router.ts +17 -1
  154. package/src/calls/twilio-config.ts +3 -8
  155. package/src/calls/twilio-routes.ts +1 -2
  156. package/src/calls/types.ts +3 -1
  157. package/src/calls/voice-ingress-preflight.ts +1 -1
  158. package/src/cli/commands/browser-relay.ts +18 -12
  159. package/src/cli/commands/completions.ts +0 -3
  160. package/src/cli/commands/credentials.ts +101 -15
  161. package/src/cli/commands/doctor.ts +4 -3
  162. package/src/cli/commands/mcp.ts +46 -59
  163. package/src/cli/commands/memory.ts +16 -165
  164. package/src/cli/commands/oauth/apps.ts +284 -0
  165. package/src/cli/commands/oauth/connections.ts +633 -0
  166. package/src/cli/commands/oauth/index.ts +52 -0
  167. package/src/cli/commands/oauth/providers.ts +256 -0
  168. package/src/cli/commands/sessions.ts +5 -2
  169. package/src/cli/commands/skills.ts +177 -339
  170. package/src/cli/http-client.ts +0 -20
  171. package/src/cli/main-screen.tsx +2 -2
  172. package/src/cli/program.ts +6 -11
  173. package/src/cli/reference.ts +1 -3
  174. package/src/cli.ts +4 -10
  175. package/src/config/assistant-feature-flags.ts +0 -3
  176. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  177. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  178. package/src/config/bundled-skills/computer-use/TOOLS.json +23 -5
  179. package/src/config/bundled-skills/computer-use/tools/{computer-use-request-control.ts → computer-use-observe.ts} +1 -5
  180. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  181. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  182. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  183. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  184. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  185. package/src/config/bundled-tool-registry.ts +2 -5
  186. package/src/config/env-registry.ts +14 -83
  187. package/src/config/env.ts +11 -50
  188. package/src/config/feature-flag-registry.json +16 -16
  189. package/src/config/loader.ts +0 -6
  190. package/src/config/schema.ts +4 -13
  191. package/src/config/schemas/memory-lifecycle.ts +0 -9
  192. package/src/config/schemas/memory-processing.ts +0 -180
  193. package/src/config/schemas/memory-retrieval.ts +32 -104
  194. package/src/config/schemas/memory.ts +0 -10
  195. package/src/config/skills.ts +21 -2
  196. package/src/config/types.ts +0 -4
  197. package/src/context/image-dimensions.ts +229 -0
  198. package/src/context/token-estimator.ts +75 -12
  199. package/src/context/window-manager.ts +53 -11
  200. package/src/daemon/assistant-attachments.ts +1 -13
  201. package/src/daemon/config-watcher.ts +61 -3
  202. package/src/daemon/daemon-control.ts +1 -1
  203. package/src/daemon/date-context.ts +114 -31
  204. package/src/daemon/handlers/config-ingress.ts +8 -33
  205. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  206. package/src/daemon/handlers/config-telegram.ts +32 -16
  207. package/src/daemon/handlers/sessions.ts +27 -36
  208. package/src/daemon/handlers/shared.ts +0 -130
  209. package/src/daemon/handlers/skills.ts +20 -1
  210. package/src/daemon/history-repair.ts +72 -8
  211. package/src/daemon/host-cu-proxy.ts +430 -0
  212. package/src/daemon/lifecycle.ts +67 -71
  213. package/src/daemon/mcp-reload-service.ts +2 -2
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/computer-use.ts +1 -129
  216. package/src/daemon/message-types/host-cu.ts +19 -0
  217. package/src/daemon/message-types/memory.ts +4 -16
  218. package/src/daemon/message-types/messages.ts +4 -0
  219. package/src/daemon/message-types/sessions.ts +4 -0
  220. package/src/daemon/server.ts +25 -21
  221. package/src/daemon/session-agent-loop-handlers.ts +40 -0
  222. package/src/daemon/session-agent-loop.ts +334 -48
  223. package/src/daemon/session-attachments.ts +1 -2
  224. package/src/daemon/session-error.ts +89 -6
  225. package/src/daemon/session-history.ts +17 -7
  226. package/src/daemon/session-media-retry.ts +6 -2
  227. package/src/daemon/session-memory.ts +69 -149
  228. package/src/daemon/session-process.ts +10 -1
  229. package/src/daemon/session-runtime-assembly.ts +49 -19
  230. package/src/daemon/session-slash.ts +1 -1
  231. package/src/daemon/session-surfaces.ts +43 -28
  232. package/src/daemon/session-tool-setup.ts +9 -10
  233. package/src/daemon/session.ts +150 -17
  234. package/src/daemon/tool-side-effects.ts +2 -8
  235. package/src/daemon/watch-handler.ts +2 -2
  236. package/src/events/tool-metrics-listener.ts +2 -2
  237. package/src/hooks/manager.ts +1 -4
  238. package/src/inbound/public-ingress-urls.ts +7 -7
  239. package/src/instrument.ts +61 -1
  240. package/src/logfire.ts +16 -5
  241. package/src/memory/admin.ts +2 -191
  242. package/src/memory/canonical-guardian-store.ts +38 -2
  243. package/src/memory/conversation-crud.ts +0 -33
  244. package/src/memory/conversation-key-store.ts +21 -0
  245. package/src/memory/conversation-queries.ts +22 -3
  246. package/src/memory/db-init.ts +32 -0
  247. package/src/memory/embedding-backend.ts +84 -8
  248. package/src/memory/embedding-types.ts +9 -1
  249. package/src/memory/indexer.ts +7 -46
  250. package/src/memory/items-extractor.ts +274 -76
  251. package/src/memory/job-handlers/backfill.ts +2 -127
  252. package/src/memory/job-handlers/cleanup.ts +2 -16
  253. package/src/memory/job-handlers/extraction.ts +2 -138
  254. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  255. package/src/memory/job-handlers/summarization.ts +3 -148
  256. package/src/memory/job-utils.ts +21 -59
  257. package/src/memory/jobs-store.ts +1 -159
  258. package/src/memory/jobs-worker.ts +9 -52
  259. package/src/memory/migrations/104-core-indexes.ts +3 -3
  260. package/src/memory/migrations/149-oauth-tables.ts +62 -0
  261. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  262. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  263. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  264. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  265. package/src/memory/migrations/154-drop-fts.ts +20 -0
  266. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  267. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  268. package/src/memory/migrations/index.ts +8 -0
  269. package/src/memory/qdrant-client.ts +148 -51
  270. package/src/memory/raw-query.ts +1 -1
  271. package/src/memory/retriever.test.ts +294 -273
  272. package/src/memory/retriever.ts +421 -645
  273. package/src/memory/schema/calls.ts +2 -0
  274. package/src/memory/schema/index.ts +1 -0
  275. package/src/memory/schema/memory-core.ts +3 -48
  276. package/src/memory/schema/oauth.ts +67 -0
  277. package/src/memory/search/formatting.ts +263 -176
  278. package/src/memory/search/lexical.ts +1 -254
  279. package/src/memory/search/ranking.ts +0 -455
  280. package/src/memory/search/semantic.ts +100 -14
  281. package/src/memory/search/staleness.ts +47 -0
  282. package/src/memory/search/tier-classifier.ts +21 -0
  283. package/src/memory/search/types.ts +15 -77
  284. package/src/memory/task-memory-cleanup.ts +4 -6
  285. package/src/messaging/provider.ts +4 -4
  286. package/src/messaging/providers/gmail/client.ts +82 -2
  287. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  288. package/src/messaging/providers/gmail/people-client.ts +10 -10
  289. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  290. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  291. package/src/messaging/registry.ts +2 -32
  292. package/src/notifications/copy-composer.ts +0 -5
  293. package/src/notifications/signal.ts +4 -5
  294. package/src/oauth/byo-connection.test.ts +133 -25
  295. package/src/oauth/byo-connection.ts +22 -6
  296. package/src/oauth/connect-orchestrator.ts +113 -57
  297. package/src/oauth/connect-types.ts +17 -23
  298. package/src/oauth/connection-resolver.ts +35 -11
  299. package/src/oauth/connection.ts +1 -1
  300. package/src/oauth/manual-token-connection.ts +104 -0
  301. package/src/oauth/oauth-store.ts +582 -0
  302. package/src/oauth/platform-connection.test.ts +29 -0
  303. package/src/oauth/platform-connection.ts +6 -5
  304. package/src/oauth/provider-behaviors.ts +124 -0
  305. package/src/oauth/scope-policy.ts +9 -2
  306. package/src/oauth/seed-providers.ts +167 -0
  307. package/src/oauth/token-persistence.ts +81 -77
  308. package/src/permissions/checker.ts +3 -3
  309. package/src/permissions/defaults.ts +1 -1
  310. package/src/permissions/prompter.ts +10 -1
  311. package/src/permissions/trust-store.ts +36 -1
  312. package/src/playbooks/playbook-compiler.ts +1 -1
  313. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  314. package/src/prompts/system-prompt.ts +46 -42
  315. package/src/providers/anthropic/client.ts +59 -20
  316. package/src/providers/retry.ts +1 -27
  317. package/src/providers/types.ts +7 -1
  318. package/src/runtime/AGENTS.md +9 -0
  319. package/src/runtime/auth/route-policy.ts +6 -6
  320. package/src/runtime/channel-reply-delivery.ts +0 -40
  321. package/src/runtime/gateway-client.ts +0 -7
  322. package/src/runtime/guardian-reply-router.ts +24 -22
  323. package/src/runtime/http-server.ts +10 -8
  324. package/src/runtime/http-types.ts +2 -2
  325. package/src/runtime/invite-redemption-service.ts +19 -1
  326. package/src/runtime/invite-service.ts +25 -0
  327. package/src/runtime/middleware/twilio-validation.ts +1 -11
  328. package/src/runtime/pending-interactions.ts +14 -12
  329. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  330. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  331. package/src/runtime/routes/conversation-routes.ts +81 -19
  332. package/src/runtime/routes/events-routes.ts +21 -11
  333. package/src/runtime/routes/host-cu-routes.ts +97 -0
  334. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  335. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  336. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  337. package/src/runtime/routes/log-export-routes.ts +126 -8
  338. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  339. package/src/runtime/routes/memory-item-routes.ts +503 -0
  340. package/src/runtime/routes/session-management-routes.ts +3 -3
  341. package/src/runtime/routes/settings-routes.ts +55 -48
  342. package/src/runtime/routes/surface-action-routes.ts +1 -1
  343. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  344. package/src/runtime/routes/watch-routes.ts +128 -0
  345. package/src/runtime/routes/workspace-routes.ts +2 -1
  346. package/src/schedule/integration-status.ts +10 -9
  347. package/src/security/credential-key.ts +0 -156
  348. package/src/security/keychain-broker-client.ts +22 -10
  349. package/src/security/oauth2.ts +1 -1
  350. package/src/security/secure-keys.ts +25 -3
  351. package/src/security/token-manager.ts +137 -64
  352. package/src/skills/catalog-install.ts +414 -0
  353. package/src/skills/include-graph.ts +32 -0
  354. package/src/skills/skillssh-registry.ts +503 -0
  355. package/src/telegram/bot-username.ts +2 -3
  356. package/src/tools/assets/search.ts +5 -1
  357. package/src/tools/browser/network-recorder.ts +1 -1
  358. package/src/tools/browser/network-recording-types.ts +1 -1
  359. package/src/tools/computer-use/definitions.ts +36 -11
  360. package/src/tools/computer-use/registry.ts +5 -6
  361. package/src/tools/credentials/broker.ts +1 -2
  362. package/src/tools/credentials/metadata-store.ts +17 -121
  363. package/src/tools/credentials/vault.ts +92 -167
  364. package/src/tools/memory/definitions.ts +4 -13
  365. package/src/tools/memory/handlers.test.ts +83 -103
  366. package/src/tools/memory/handlers.ts +50 -85
  367. package/src/tools/registry.ts +2 -7
  368. package/src/tools/schedule/create.ts +8 -1
  369. package/src/tools/schedule/update.ts +8 -1
  370. package/src/tools/skills/load.ts +85 -3
  371. package/src/tools/watch/watch-state.ts +0 -12
  372. package/src/util/logger.ts +7 -41
  373. package/src/util/platform.ts +9 -28
  374. package/src/watcher/providers/google-calendar.ts +2 -1
  375. package/src/__tests__/clarification-resolver.test.ts +0 -193
  376. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  377. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  378. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  379. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  380. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  381. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  382. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  383. package/src/__tests__/conflict-policy.test.ts +0 -269
  384. package/src/__tests__/conflict-store.test.ts +0 -372
  385. package/src/__tests__/contradiction-checker.test.ts +0 -361
  386. package/src/__tests__/entity-extractor.test.ts +0 -211
  387. package/src/__tests__/entity-search.test.ts +0 -1117
  388. package/src/__tests__/profile-compiler.test.ts +0 -392
  389. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  390. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  391. package/src/__tests__/session-profile-injection.test.ts +0 -557
  392. package/src/cli/commands/dev.ts +0 -129
  393. package/src/cli/commands/map.ts +0 -391
  394. package/src/cli/commands/oauth.ts +0 -77
  395. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  396. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  397. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  398. package/src/daemon/computer-use-session.ts +0 -1026
  399. package/src/daemon/ride-shotgun-handler.ts +0 -569
  400. package/src/daemon/session-conflict-gate.ts +0 -167
  401. package/src/daemon/session-dynamic-profile.ts +0 -77
  402. package/src/memory/clarification-resolver.ts +0 -417
  403. package/src/memory/conflict-intent.ts +0 -205
  404. package/src/memory/conflict-policy.ts +0 -127
  405. package/src/memory/conflict-store.ts +0 -410
  406. package/src/memory/contradiction-checker.ts +0 -508
  407. package/src/memory/entity-extractor.ts +0 -535
  408. package/src/memory/format-recall.ts +0 -47
  409. package/src/memory/fts-reconciler.ts +0 -165
  410. package/src/memory/job-handlers/conflict.ts +0 -200
  411. package/src/memory/profile-compiler.ts +0 -195
  412. package/src/memory/recall-cache.ts +0 -117
  413. package/src/memory/search/entity.ts +0 -535
  414. package/src/memory/search/query-expansion.test.ts +0 -70
  415. package/src/memory/search/query-expansion.ts +0 -118
  416. package/src/oauth/provider-base-urls.ts +0 -21
  417. package/src/oauth/provider-profiles.ts +0 -192
  418. package/src/prompts/computer-use-prompt.ts +0 -98
  419. package/src/runtime/routes/computer-use-routes.ts +0 -641
  420. package/src/runtime/routes/mcp-routes.ts +0 -20
  421. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  422. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  423. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -1,5 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
+ import { BackendError, VellumError } from "../util/errors.js";
3
4
  import {
4
5
  CredentialRequiredError,
5
6
  PlatformOAuthConnection,
@@ -116,6 +117,16 @@ describe("PlatformOAuthConnection", () => {
116
117
  await conn.request({ method: "GET", path: "/some/path" });
117
118
  });
118
119
 
120
+ test("error classes extend VellumError hierarchy", () => {
121
+ const credErr = new CredentialRequiredError();
122
+ expect(credErr).toBeInstanceOf(BackendError);
123
+ expect(credErr).toBeInstanceOf(VellumError);
124
+
125
+ const provErr = new ProviderUnreachableError();
126
+ expect(provErr).toBeInstanceOf(BackendError);
127
+ expect(provErr).toBeInstanceOf(VellumError);
128
+ });
129
+
119
130
  test("424 response throws CredentialRequiredError", async () => {
120
131
  globalThis.fetch = mock(async () => {
121
132
  return new Response("", { status: 424 });
@@ -145,6 +156,24 @@ describe("PlatformOAuthConnection", () => {
145
156
  );
146
157
  });
147
158
 
159
+ test("strips trailing slash from platformBaseUrl to avoid double slashes", async () => {
160
+ globalThis.fetch = mock(async (url: string | URL | Request) => {
161
+ expect(String(url)).toBe(
162
+ "https://platform.example.com/v1/assistants/asst-abc/external-provider-proxy/gmail/",
163
+ );
164
+ return new Response(
165
+ JSON.stringify({ status: 200, headers: {}, body: null }),
166
+ { status: 200 },
167
+ );
168
+ }) as unknown as typeof globalThis.fetch;
169
+
170
+ const conn = new PlatformOAuthConnection({
171
+ ...DEFAULT_OPTIONS,
172
+ platformBaseUrl: "https://platform.example.com/",
173
+ });
174
+ await conn.request({ method: "GET", path: "/test" });
175
+ });
176
+
148
177
  test("strips integration: prefix from providerKey for slug", async () => {
149
178
  globalThis.fetch = mock(async (url: string | URL | Request) => {
150
179
  expect(String(url)).toContain("/external-provider-proxy/slack/");
@@ -1,17 +1,18 @@
1
+ import { BackendError } from "../util/errors.js";
1
2
  import type {
2
3
  OAuthConnection,
3
4
  OAuthConnectionRequest,
4
5
  OAuthConnectionResponse,
5
6
  } from "./connection.js";
6
7
 
7
- export class CredentialRequiredError extends Error {
8
+ export class CredentialRequiredError extends BackendError {
8
9
  constructor(message = "Connection not set up on platform") {
9
10
  super(message);
10
11
  this.name = "CredentialRequiredError";
11
12
  }
12
13
  }
13
14
 
14
- export class ProviderUnreachableError extends Error {
15
+ export class ProviderUnreachableError extends BackendError {
15
16
  constructor(message = "Provider is unreachable") {
16
17
  super(message);
17
18
  this.name = "ProviderUnreachableError";
@@ -47,7 +48,7 @@ export class PlatformOAuthConnection implements OAuthConnection {
47
48
  this.accountInfo = options.accountInfo;
48
49
  this.grantedScopes = options.grantedScopes;
49
50
  this.assistantId = options.assistantId;
50
- this.platformBaseUrl = options.platformBaseUrl;
51
+ this.platformBaseUrl = options.platformBaseUrl.replace(/\/+$/, "");
51
52
  this.apiKey = options.apiKey;
52
53
  }
53
54
 
@@ -84,7 +85,7 @@ export class PlatformOAuthConnection implements OAuthConnection {
84
85
  }
85
86
 
86
87
  if (!response.ok) {
87
- throw new Error(
88
+ throw new BackendError(
88
89
  `Platform proxy returned unexpected status ${response.status}`,
89
90
  );
90
91
  }
@@ -103,7 +104,7 @@ export class PlatformOAuthConnection implements OAuthConnection {
103
104
  }
104
105
 
105
106
  async withToken<T>(_fn: (token: string) => Promise<T>): Promise<T> {
106
- throw new Error(
107
+ throw new BackendError(
107
108
  "Raw token access is not supported for platform-managed connections. Use connection.request() instead.",
108
109
  );
109
110
  }
@@ -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,167 @@
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
+ pingUrl?: string;
21
+ baseUrl?: string;
22
+ defaultScopes: string[];
23
+ scopePolicy: {
24
+ allowAdditionalScopes: boolean;
25
+ allowedOptionalScopes: string[];
26
+ forbiddenScopes: string[];
27
+ };
28
+ extraParams?: Record<string, string>;
29
+ callbackTransport?: string;
30
+ loopbackPort?: number;
31
+ }
32
+ > = {
33
+ "integration:gmail": {
34
+ providerKey: "integration:gmail",
35
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
36
+ tokenUrl: "https://oauth2.googleapis.com/token",
37
+ userinfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
38
+ pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
39
+ baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
40
+ defaultScopes: [
41
+ "https://www.googleapis.com/auth/gmail.readonly",
42
+ "https://www.googleapis.com/auth/gmail.modify",
43
+ "https://www.googleapis.com/auth/gmail.send",
44
+ "https://www.googleapis.com/auth/calendar.readonly",
45
+ "https://www.googleapis.com/auth/calendar.events",
46
+ "https://www.googleapis.com/auth/userinfo.email",
47
+ "https://www.googleapis.com/auth/contacts.readonly",
48
+ ],
49
+ scopePolicy: {
50
+ allowAdditionalScopes: false,
51
+ allowedOptionalScopes: [],
52
+ forbiddenScopes: [],
53
+ },
54
+ extraParams: { access_type: "offline", prompt: "consent" },
55
+ callbackTransport: "loopback",
56
+ },
57
+
58
+ "integration:slack": {
59
+ providerKey: "integration:slack",
60
+ authUrl: "https://slack.com/oauth/v2/authorize",
61
+ tokenUrl: "https://slack.com/api/oauth.v2.access",
62
+ pingUrl: "https://slack.com/api/auth.test",
63
+ baseUrl: "https://slack.com/api",
64
+ defaultScopes: [
65
+ "channels:read",
66
+ "channels:history",
67
+ "groups:read",
68
+ "groups:history",
69
+ "im:read",
70
+ "im:history",
71
+ "im:write",
72
+ "mpim:read",
73
+ "mpim:history",
74
+ "users:read",
75
+ "chat:write",
76
+ "search:read",
77
+ "reactions:write",
78
+ ],
79
+ scopePolicy: {
80
+ allowAdditionalScopes: false,
81
+ allowedOptionalScopes: [],
82
+ forbiddenScopes: [],
83
+ },
84
+ extraParams: {
85
+ user_scope:
86
+ "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",
87
+ },
88
+ callbackTransport: "loopback",
89
+ loopbackPort: 17322,
90
+ },
91
+
92
+ "integration:notion": {
93
+ providerKey: "integration:notion",
94
+ authUrl: "https://api.notion.com/v1/oauth/authorize",
95
+ tokenUrl: "https://api.notion.com/v1/oauth/token",
96
+ pingUrl: "https://api.notion.com/v1/users/me",
97
+ baseUrl: "https://api.notion.com",
98
+ defaultScopes: [],
99
+ scopePolicy: {
100
+ allowAdditionalScopes: false,
101
+ allowedOptionalScopes: [],
102
+ forbiddenScopes: [],
103
+ },
104
+ extraParams: { owner: "user" },
105
+ tokenEndpointAuthMethod: "client_secret_basic",
106
+ },
107
+
108
+ "integration:twitter": {
109
+ providerKey: "integration:twitter",
110
+ authUrl: "https://twitter.com/i/oauth2/authorize",
111
+ tokenUrl: "https://api.x.com/2/oauth2/token",
112
+ pingUrl: "https://api.x.com/2/users/me",
113
+ baseUrl: "https://api.x.com",
114
+ defaultScopes: [
115
+ "tweet.read",
116
+ "tweet.write",
117
+ "users.read",
118
+ "offline.access",
119
+ ],
120
+ scopePolicy: {
121
+ allowAdditionalScopes: false,
122
+ allowedOptionalScopes: [],
123
+ forbiddenScopes: [],
124
+ },
125
+ tokenEndpointAuthMethod: "client_secret_basic",
126
+ callbackTransport: "gateway",
127
+ },
128
+
129
+ // Manual-token providers: these don't use OAuth2 flows but need provider
130
+ // rows so that oauth_app and oauth_connection FK chains can reference them.
131
+ // The authUrl/tokenUrl values are placeholders — never used at runtime.
132
+ slack_channel: {
133
+ providerKey: "slack_channel",
134
+ authUrl: "urn:manual-token",
135
+ tokenUrl: "urn:manual-token",
136
+ pingUrl: "https://slack.com/api/auth.test",
137
+ baseUrl: "https://slack.com/api",
138
+ defaultScopes: [],
139
+ scopePolicy: {
140
+ allowAdditionalScopes: false,
141
+ allowedOptionalScopes: [],
142
+ forbiddenScopes: [],
143
+ },
144
+ },
145
+
146
+ telegram: {
147
+ providerKey: "telegram",
148
+ authUrl: "urn:manual-token",
149
+ tokenUrl: "urn:manual-token",
150
+ baseUrl: "https://api.telegram.org",
151
+ defaultScopes: [],
152
+ scopePolicy: {
153
+ allowAdditionalScopes: false,
154
+ allowedOptionalScopes: [],
155
+ forbiddenScopes: [],
156
+ },
157
+ },
158
+ };
159
+
160
+ /**
161
+ * Seed the oauth_providers table with well-known provider configurations.
162
+ * Uses INSERT … ON CONFLICT DO UPDATE so seed-data corrections propagate
163
+ * to existing installations. Safe to call on every startup.
164
+ */
165
+ export function seedOAuthProviders(): void {
166
+ seedProviders(Object.values(PROVIDER_SEED_DATA));
167
+ }
@@ -2,11 +2,14 @@
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
- import { credentialKey, migrateKeys } from "../security/credential-key.js";
10
13
  import type {
11
14
  OAuth2FlowResult,
12
15
  TokenEndpointAuthMethod,
@@ -15,9 +18,15 @@ import {
15
18
  deleteSecureKeyAsync,
16
19
  setSecureKeyAsync,
17
20
  } from "../security/secure-keys.js";
18
- import { upsertCredentialMetadata } from "../tools/credentials/metadata-store.js";
19
21
  import type { CredentialInjectionTemplate } from "../tools/credentials/policy-types.js";
20
22
  import { runPostConnectHook } from "../tools/credentials/post-connect-hooks.js";
23
+ import {
24
+ createConnection,
25
+ getApp,
26
+ getConnectionByProvider,
27
+ updateConnection,
28
+ upsertApp,
29
+ } from "./oauth-store.js";
21
30
 
22
31
  // ---------------------------------------------------------------------------
23
32
  // Types
@@ -37,6 +46,8 @@ export interface StoreOAuth2TokensParams {
37
46
  wellKnownInjectionTemplates?: CredentialInjectionTemplate[];
38
47
  /** Fallback account info from an identity verifier (e.g. @username, email). */
39
48
  identityAccountInfo?: string;
49
+ /** Pre-resolved oauth_app ID — skips the upsertApp() call if provided. */
50
+ oauthAppId?: string;
40
51
  }
41
52
 
42
53
  // ---------------------------------------------------------------------------
@@ -47,15 +58,13 @@ export interface StoreOAuth2TokensParams {
47
58
  * Store OAuth2 tokens and associated metadata after a successful flow.
48
59
  *
49
60
  * Persists the access token, optional refresh token, client credentials,
50
- * and metadata (scopes, expiry, account info) into the secure key store
51
- * and credential metadata file. Runs any registered post-connect hook
52
- * for the service.
61
+ * and metadata (scopes, expiry, account info) into the SQLite oauth_app /
62
+ * oauth_connection tables with new-format secure keys. Runs any registered
63
+ * post-connect hook for the service.
53
64
  */
54
65
  export async function storeOAuth2Tokens(
55
66
  params: StoreOAuth2TokensParams,
56
67
  ): Promise<{ accountInfo?: string }> {
57
- migrateKeys();
58
-
59
68
  const {
60
69
  service,
61
70
  tokens,
@@ -63,21 +72,9 @@ export async function storeOAuth2Tokens(
63
72
  rawTokenResponse,
64
73
  clientId,
65
74
  clientSecret,
66
- tokenUrl,
67
- tokenEndpointAuthMethod,
68
75
  userinfoUrl,
69
- allowedTools,
70
- wellKnownInjectionTemplates,
71
76
  } = params;
72
77
 
73
- const tokenStored = await setSecureKeyAsync(
74
- credentialKey(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
78
  const expiresAt = tokens.expiresIn
82
79
  ? Date.now() + tokens.expiresIn * 1000
83
80
  : null;
@@ -97,82 +94,89 @@ export async function storeOAuth2Tokens(
97
94
  }
98
95
  }
99
96
 
100
- // client_id is stored in metadata only (oauth2ClientId field) — not the
101
- // secure store. token-manager.ts reads it from meta?.oauth2ClientId.
102
- if (clientSecret) {
103
- const clientSecretStored = await setSecureKeyAsync(
104
- credentialKey(service, "client_secret"),
97
+ const resolvedAccountInfo = accountInfo ?? params.identityAccountInfo;
98
+
99
+ // -------------------------------------------------------------------
100
+ // SQLite oauth_app + oauth_connection + new-format secure keys
101
+ // -------------------------------------------------------------------
102
+
103
+ // 1. Upsert the oauth_app row (or use the pre-resolved ID).
104
+ const app = params.oauthAppId
105
+ ? (getApp(params.oauthAppId) ?? {
106
+ id: params.oauthAppId,
107
+ clientSecretCredentialPath: `oauth_app/${params.oauthAppId}/client_secret`,
108
+ })
109
+ : await upsertApp(
110
+ service,
111
+ clientId,
112
+ clientSecret ? { clientSecretValue: clientSecret } : undefined,
113
+ );
114
+
115
+ // When oauthAppId is pre-resolved, still persist clientSecret if provided.
116
+ if (params.oauthAppId && clientSecret) {
117
+ const stored = await setSecureKeyAsync(
118
+ app.clientSecretCredentialPath,
105
119
  clientSecret,
106
120
  );
107
- if (!clientSecretStored) {
121
+ if (!stored) {
108
122
  throw new Error("Failed to store client_secret in secure storage");
109
123
  }
110
124
  }
111
125
 
112
- upsertCredentialMetadata(service, "access_token", {
113
- allowedTools: allowedTools ?? [],
114
- expiresAt,
115
- grantedScopes,
116
- oauth2TokenUrl: tokenUrl,
117
- oauth2ClientId: clientId,
118
- ...(tokenEndpointAuthMethod
119
- ? { oauth2TokenEndpointAuthMethod: tokenEndpointAuthMethod }
120
- : {}),
121
- ...(wellKnownInjectionTemplates
122
- ? { injectionTemplates: wellKnownInjectionTemplates }
123
- : {}),
124
- });
125
-
126
- // Write accountInfo to config using a namespaced key (dynamic import to
127
- // avoid circular dependencies — the config loader may transitively depend
128
- // on credential modules).
129
- const resolvedAccountInfo = accountInfo ?? params.identityAccountInfo;
130
- if (resolvedAccountInfo) {
131
- try {
132
- const {
133
- invalidateConfigCache,
134
- loadRawConfig,
135
- saveRawConfig,
136
- setNestedValue,
137
- } = await import("../config/loader.js");
138
- const raw = loadRawConfig();
139
-
140
- // Write to the namespaced path
141
- setNestedValue(
142
- raw,
143
- `integrations.${service}.accountInfo`,
144
- resolvedAccountInfo,
145
- );
126
+ // 2. Upsert oauth_connection — reuse existing active connection for this
127
+ // provider, or create a new one.
128
+ const existingConn = getConnectionByProvider(service);
129
+ let connId: string;
130
+
131
+ const hasRefreshToken = !!tokens.refreshToken;
132
+
133
+ if (existingConn) {
134
+ connId = existingConn.id;
135
+ updateConnection(connId, {
136
+ oauthAppId: app.id,
137
+ accountInfo: resolvedAccountInfo,
138
+ grantedScopes,
139
+ expiresAt,
140
+ hasRefreshToken,
141
+ metadata: rawTokenResponse,
142
+ });
143
+ } else {
144
+ const conn = createConnection({
145
+ oauthAppId: app.id,
146
+ providerKey: service,
147
+ accountInfo: resolvedAccountInfo,
148
+ grantedScopes,
149
+ expiresAt: expiresAt ?? undefined,
150
+ hasRefreshToken,
151
+ metadata: rawTokenResponse,
152
+ });
153
+ connId = conn.id;
154
+ }
146
155
 
147
- saveRawConfig(raw);
148
- invalidateConfigCache();
149
- } catch {
150
- // Non-fatal — tokens stored even if config write fails
151
- }
156
+ // 3. Write access_token: oauth_connection/{conn.id}/access_token
157
+ const tokenStored = await setSecureKeyAsync(
158
+ `oauth_connection/${connId}/access_token`,
159
+ tokens.accessToken,
160
+ );
161
+ if (!tokenStored) {
162
+ throw new Error("Failed to store access token in secure storage");
152
163
  }
153
164
 
165
+ // 4. Write or clear refresh_token: oauth_connection/{conn.id}/refresh_token
154
166
  if (tokens.refreshToken) {
155
- const refreshStored = await setSecureKeyAsync(
156
- credentialKey(service, "refresh_token"),
167
+ await setSecureKeyAsync(
168
+ `oauth_connection/${connId}/refresh_token`,
157
169
  tokens.refreshToken,
158
170
  );
159
- if (refreshStored) {
160
- upsertCredentialMetadata(service, "access_token", {
161
- hasRefreshToken: true,
162
- });
163
- }
164
171
  } else {
165
172
  // Re-auth grants that omit refresh_token must clear any stale stored
166
173
  // token — otherwise withValidToken() will attempt refresh with invalid
167
174
  // credentials.
168
- await deleteSecureKeyAsync(credentialKey(service, "refresh_token"));
169
- upsertCredentialMetadata(service, "access_token", {
170
- hasRefreshToken: false,
171
- });
175
+ await deleteSecureKeyAsync(`oauth_connection/${connId}/refresh_token`);
172
176
  }
173
177
 
174
178
  // Run any provider-specific post-connect actions (e.g. Slack welcome DM)
175
179
  await runPostConnectHook({ service, rawTokenResponse });
176
180
 
177
- return { accountInfo: accountInfo ?? params.identityAccountInfo };
181
+ return { accountInfo: resolvedAccountInfo };
178
182
  }
@@ -805,12 +805,12 @@ export async function check(
805
805
  }
806
806
 
807
807
  // Workspace mode: auto-allow workspace-scoped operations that don't have
808
- // an explicit rule. Non-workspace operations fall through to risk-based policy.
809
- // 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.
810
810
  if (
811
811
  permissionsMode === "workspace" &&
812
812
  !matchedRule &&
813
- risk !== RiskLevel.High
813
+ risk === RiskLevel.Low
814
814
  ) {
815
815
  // When sandbox is disabled, bash runs on the host — don't auto-allow
816
816
  const sandboxEnabled = getConfig().sandbox.enabled;
@@ -20,6 +20,7 @@ const HOST_FILE_TOOLS = [
20
20
  "host_file_edit",
21
21
  ] as const;
22
22
  const COMPUTER_USE_TOOLS = [
23
+ "computer_use_observe",
23
24
  "computer_use_click",
24
25
  "computer_use_type_text",
25
26
  "computer_use_key",
@@ -28,7 +29,6 @@ const COMPUTER_USE_TOOLS = [
28
29
  "computer_use_wait",
29
30
  "computer_use_open_app",
30
31
  "computer_use_run_applescript",
31
- "computer_use_request_control",
32
32
  // computer_use_done and computer_use_respond are terminal signal tools
33
33
  // (RiskLevel.Low) — they don't perform any computer action, so they
34
34
  // should NOT get an 'ask' rule.