@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
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Helpers for managing oauth_connection records for non-OAuth (manual-token)
3
+ * providers like slack_channel and telegram.
4
+ *
5
+ * These providers store credentials via the keychain (setSecureKeyAsync) but
6
+ * also maintain an oauth_connection row so that getConnectionByProvider() can
7
+ * be used as the single source of truth for connection status across the
8
+ * codebase.
9
+ */
10
+
11
+ import { credentialKey } from "../security/credential-key.js";
12
+ import { getSecureKey } from "../security/secure-keys.js";
13
+ import {
14
+ createConnection,
15
+ deleteConnection,
16
+ getConnectionByProvider,
17
+ updateConnection,
18
+ upsertApp,
19
+ } from "./oauth-store.js";
20
+
21
+ /** Sentinel client_id used for non-OAuth providers that don't have a real app. */
22
+ const MANUAL_TOKEN_CLIENT_ID = "manual-config";
23
+
24
+ /**
25
+ * Ensure an active oauth_connection row exists for the given manual-token
26
+ * provider. Creates the synthetic oauth_app row on first use.
27
+ *
28
+ * @param providerKey - The provider key (e.g. "slack_channel", "telegram")
29
+ * @param accountInfo - Optional account info to store (e.g. team name, bot username)
30
+ */
31
+ export async function ensureManualTokenConnection(
32
+ providerKey: string,
33
+ accountInfo?: string,
34
+ ): Promise<void> {
35
+ const existing = getConnectionByProvider(providerKey);
36
+ if (existing) {
37
+ // Update account info if provided
38
+ if (accountInfo !== undefined) {
39
+ updateConnection(existing.id, { accountInfo });
40
+ }
41
+ return;
42
+ }
43
+
44
+ // Create synthetic app + connection
45
+ const app = await upsertApp(providerKey, MANUAL_TOKEN_CLIENT_ID);
46
+
47
+ createConnection({
48
+ oauthAppId: app.id,
49
+ providerKey,
50
+ accountInfo,
51
+ grantedScopes: [],
52
+ hasRefreshToken: false,
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Remove the oauth_connection row for a manual-token provider.
58
+ *
59
+ * Note: This only removes the oauth_connection row. The caller is still
60
+ * responsible for deleting the keychain credentials separately.
61
+ */
62
+ export function removeManualTokenConnection(providerKey: string): void {
63
+ const conn = getConnectionByProvider(providerKey);
64
+ if (!conn) return;
65
+ deleteConnection(conn.id);
66
+ }
67
+
68
+ /**
69
+ * Backfill oauth_connection rows for manual-token providers that already
70
+ * have valid keychain credentials but are missing connection records.
71
+ *
72
+ * This handles the upgrade path from installations that stored credentials
73
+ * before the oauth_connection migration. Without this, existing Telegram
74
+ * and Slack channel integrations would appear disconnected after upgrading
75
+ * until the user reconfigures them.
76
+ *
77
+ * Safe to call on every startup — skips providers that already have a
78
+ * connection row.
79
+ */
80
+ export async function backfillManualTokenConnections(): Promise<void> {
81
+ // Telegram: requires both bot_token and webhook_secret
82
+ if (!getConnectionByProvider("telegram")) {
83
+ const hasBotToken = !!getSecureKey(credentialKey("telegram", "bot_token"));
84
+ const hasWebhookSecret = !!getSecureKey(
85
+ credentialKey("telegram", "webhook_secret"),
86
+ );
87
+ if (hasBotToken && hasWebhookSecret) {
88
+ await ensureManualTokenConnection("telegram");
89
+ }
90
+ }
91
+
92
+ // Slack channel: requires both bot_token and app_token
93
+ if (!getConnectionByProvider("slack_channel")) {
94
+ const hasBotToken = !!getSecureKey(
95
+ credentialKey("slack_channel", "bot_token"),
96
+ );
97
+ const hasAppToken = !!getSecureKey(
98
+ credentialKey("slack_channel", "app_token"),
99
+ );
100
+ if (hasBotToken && hasAppToken) {
101
+ await ensureManualTokenConnection("slack_channel");
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,582 @@
1
+ /**
2
+ * CRUD store for OAuth providers, apps, and connections.
3
+ *
4
+ * Backed by Drizzle + SQLite. All JSON fields (default_scopes, scope_policy,
5
+ * extra_params, granted_scopes, metadata) are stored as serialized JSON strings.
6
+ */
7
+
8
+ import { and, desc, eq, sql } from "drizzle-orm";
9
+ import { v4 as uuid } from "uuid";
10
+
11
+ import { getDb, rawChanges } from "../memory/db.js";
12
+ import {
13
+ oauthApps,
14
+ oauthConnections,
15
+ oauthProviders,
16
+ } from "../memory/schema/oauth.js";
17
+ import {
18
+ deleteSecureKeyAsync,
19
+ getSecureKey,
20
+ getSecureKeyAsync,
21
+ setSecureKeyAsync,
22
+ } from "../security/secure-keys.js";
23
+ import { getLogger } from "../util/logger.js";
24
+
25
+ const log = getLogger("oauth-store");
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Row types
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export type OAuthProviderRow = typeof oauthProviders.$inferSelect;
32
+ export type OAuthAppRow = typeof oauthApps.$inferSelect;
33
+ export type OAuthConnectionRow = typeof oauthConnections.$inferSelect;
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Provider operations
37
+ // ---------------------------------------------------------------------------
38
+
39
+ /**
40
+ * Seed well-known provider profiles into the database. Uses INSERT … ON
41
+ * CONFLICT DO UPDATE so that corrections to seed data (e.g. a fixed baseUrl)
42
+ * propagate to existing installations on the next startup.
43
+ */
44
+ export function seedProviders(
45
+ profiles: Array<{
46
+ providerKey: string;
47
+ authUrl: string;
48
+ tokenUrl: string;
49
+ tokenEndpointAuthMethod?: string;
50
+ userinfoUrl?: string;
51
+ pingUrl?: string;
52
+ baseUrl?: string;
53
+ defaultScopes: string[];
54
+ scopePolicy: Record<string, unknown>;
55
+ extraParams?: Record<string, string>;
56
+ callbackTransport?: string;
57
+ loopbackPort?: number;
58
+ }>,
59
+ ): void {
60
+ const db = getDb();
61
+ const now = Date.now();
62
+ for (const p of profiles) {
63
+ const authUrl = p.authUrl;
64
+ const tokenUrl = p.tokenUrl;
65
+ const tokenEndpointAuthMethod = p.tokenEndpointAuthMethod ?? null;
66
+ const userinfoUrl = p.userinfoUrl ?? null;
67
+ const pingUrl = p.pingUrl ?? null;
68
+ const baseUrl = p.baseUrl ?? null;
69
+ const defaultScopes = JSON.stringify(p.defaultScopes);
70
+ const scopePolicy = JSON.stringify(p.scopePolicy);
71
+ const extraParams = p.extraParams ? JSON.stringify(p.extraParams) : null;
72
+ const callbackTransport = p.callbackTransport ?? null;
73
+ const loopbackPort = p.loopbackPort ?? null;
74
+
75
+ db.insert(oauthProviders)
76
+ .values({
77
+ providerKey: p.providerKey,
78
+ authUrl,
79
+ tokenUrl,
80
+ tokenEndpointAuthMethod,
81
+ userinfoUrl,
82
+ baseUrl,
83
+ defaultScopes,
84
+ scopePolicy,
85
+ extraParams,
86
+ callbackTransport,
87
+ loopbackPort,
88
+ pingUrl,
89
+ createdAt: now,
90
+ updatedAt: now,
91
+ })
92
+ .onConflictDoUpdate({
93
+ target: oauthProviders.providerKey,
94
+ set: {
95
+ authUrl,
96
+ tokenUrl,
97
+ tokenEndpointAuthMethod,
98
+ userinfoUrl,
99
+ baseUrl,
100
+ defaultScopes,
101
+ scopePolicy,
102
+ extraParams,
103
+ callbackTransport,
104
+ loopbackPort,
105
+ pingUrl,
106
+ updatedAt: now,
107
+ },
108
+ })
109
+ .run();
110
+ }
111
+ }
112
+
113
+ /** Look up a provider by its primary key. */
114
+ export function getProvider(providerKey: string): OAuthProviderRow | undefined {
115
+ const db = getDb();
116
+ return db
117
+ .select()
118
+ .from(oauthProviders)
119
+ .where(eq(oauthProviders.providerKey, providerKey))
120
+ .get();
121
+ }
122
+
123
+ /** Return all registered providers. */
124
+ export function listProviders(): OAuthProviderRow[] {
125
+ const db = getDb();
126
+ return db.select().from(oauthProviders).all();
127
+ }
128
+
129
+ /**
130
+ * Register a new provider (for dynamic registration). Throws if the
131
+ * provider_key already exists.
132
+ */
133
+ export function registerProvider(params: {
134
+ providerKey: string;
135
+ authUrl: string;
136
+ tokenUrl: string;
137
+ tokenEndpointAuthMethod?: string;
138
+ userinfoUrl?: string;
139
+ pingUrl?: string;
140
+ baseUrl?: string;
141
+ defaultScopes: string[];
142
+ scopePolicy: Record<string, unknown>;
143
+ extraParams?: Record<string, string>;
144
+ callbackTransport?: string;
145
+ loopbackPort?: number;
146
+ }): OAuthProviderRow {
147
+ const db = getDb();
148
+ const now = Date.now();
149
+
150
+ const existing = getProvider(params.providerKey);
151
+ if (existing) {
152
+ throw new Error(`OAuth provider already exists: ${params.providerKey}`);
153
+ }
154
+
155
+ const row = {
156
+ providerKey: params.providerKey,
157
+ authUrl: params.authUrl,
158
+ tokenUrl: params.tokenUrl,
159
+ tokenEndpointAuthMethod: params.tokenEndpointAuthMethod ?? null,
160
+ userinfoUrl: params.userinfoUrl ?? null,
161
+ baseUrl: params.baseUrl ?? null,
162
+ defaultScopes: JSON.stringify(params.defaultScopes),
163
+ scopePolicy: JSON.stringify(params.scopePolicy),
164
+ extraParams: params.extraParams ? JSON.stringify(params.extraParams) : null,
165
+ callbackTransport: params.callbackTransport ?? null,
166
+ loopbackPort: params.loopbackPort ?? null,
167
+ pingUrl: params.pingUrl ?? null,
168
+ createdAt: now,
169
+ updatedAt: now,
170
+ };
171
+
172
+ db.insert(oauthProviders).values(row).run();
173
+
174
+ return row;
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // App operations
179
+ // ---------------------------------------------------------------------------
180
+
181
+ /**
182
+ * Insert or return an existing app by (provider_key, client_id).
183
+ * Generates a UUID on insert.
184
+ */
185
+ export async function upsertApp(
186
+ providerKey: string,
187
+ clientId: string,
188
+ clientSecretOpts?: {
189
+ clientSecretValue?: string;
190
+ clientSecretCredentialPath?: string;
191
+ },
192
+ ): Promise<OAuthAppRow> {
193
+ const { clientSecretValue, clientSecretCredentialPath } =
194
+ clientSecretOpts ?? {};
195
+
196
+ if (clientSecretValue && clientSecretCredentialPath) {
197
+ throw new Error(
198
+ "Cannot provide both clientSecretValue and clientSecretCredentialPath",
199
+ );
200
+ }
201
+
202
+ const defaultCredPath = (appId: string) => `oauth_app/${appId}/client_secret`;
203
+
204
+ // Verify the credential path points to an existing secret.
205
+ if (clientSecretCredentialPath) {
206
+ const existing = await getSecureKeyAsync(clientSecretCredentialPath);
207
+ if (existing === undefined) {
208
+ throw new Error(
209
+ `No secret found at credential path: ${clientSecretCredentialPath}`,
210
+ );
211
+ }
212
+ }
213
+
214
+ const db = getDb();
215
+
216
+ const existingRow = db
217
+ .select()
218
+ .from(oauthApps)
219
+ .where(
220
+ and(
221
+ eq(oauthApps.providerKey, providerKey),
222
+ eq(oauthApps.clientId, clientId),
223
+ ),
224
+ )
225
+ .get();
226
+
227
+ if (existingRow) {
228
+ if (clientSecretValue) {
229
+ const stored = await setSecureKeyAsync(
230
+ existingRow.clientSecretCredentialPath,
231
+ clientSecretValue,
232
+ );
233
+ if (!stored) {
234
+ throw new Error("Failed to store client_secret in secure storage");
235
+ }
236
+ }
237
+ if (clientSecretCredentialPath) {
238
+ db.update(oauthApps)
239
+ .set({
240
+ clientSecretCredentialPath,
241
+ updatedAt: Date.now(),
242
+ })
243
+ .where(eq(oauthApps.id, existingRow.id))
244
+ .run();
245
+ return db
246
+ .select()
247
+ .from(oauthApps)
248
+ .where(eq(oauthApps.id, existingRow.id))
249
+ .get()!;
250
+ }
251
+ return existingRow;
252
+ }
253
+
254
+ const now = Date.now();
255
+ const id = uuid();
256
+ const credPath = clientSecretCredentialPath ?? defaultCredPath(id);
257
+
258
+ if (clientSecretValue) {
259
+ const stored = await setSecureKeyAsync(credPath, clientSecretValue);
260
+ if (!stored) {
261
+ throw new Error("Failed to store client_secret in secure storage");
262
+ }
263
+ }
264
+
265
+ const row = {
266
+ id,
267
+ providerKey,
268
+ clientId,
269
+ clientSecretCredentialPath: credPath,
270
+ createdAt: now,
271
+ updatedAt: now,
272
+ };
273
+
274
+ db.insert(oauthApps).values(row).run();
275
+
276
+ return row;
277
+ }
278
+
279
+ /** Look up an app by its primary key. */
280
+ export function getApp(id: string): OAuthAppRow | undefined {
281
+ const db = getDb();
282
+ return db.select().from(oauthApps).where(eq(oauthApps.id, id)).get();
283
+ }
284
+
285
+ /** Look up an app by (provider_key, client_id). */
286
+ export function getAppByProviderAndClientId(
287
+ providerKey: string,
288
+ clientId: string,
289
+ ): OAuthAppRow | undefined {
290
+ const db = getDb();
291
+ return db
292
+ .select()
293
+ .from(oauthApps)
294
+ .where(
295
+ and(
296
+ eq(oauthApps.providerKey, providerKey),
297
+ eq(oauthApps.clientId, clientId),
298
+ ),
299
+ )
300
+ .get();
301
+ }
302
+
303
+ /**
304
+ * Get the most recently created app for a provider.
305
+ * Returns undefined if no app exists for this provider.
306
+ */
307
+ export function getMostRecentAppByProvider(
308
+ providerKey: string,
309
+ ): OAuthAppRow | undefined {
310
+ const db = getDb();
311
+ return db
312
+ .select()
313
+ .from(oauthApps)
314
+ .where(eq(oauthApps.providerKey, providerKey))
315
+ .orderBy(desc(oauthApps.createdAt))
316
+ .limit(1)
317
+ .get();
318
+ }
319
+
320
+ /** Return all OAuth apps. */
321
+ export function listApps(): OAuthAppRow[] {
322
+ const db = getDb();
323
+ return db.select().from(oauthApps).all();
324
+ }
325
+
326
+ /** Delete an app by ID. Cleans up the client_secret from secure storage. Returns true if a row was deleted. */
327
+ export async function deleteApp(id: string): Promise<boolean> {
328
+ const db = getDb();
329
+
330
+ const app = db.select().from(oauthApps).where(eq(oauthApps.id, id)).get();
331
+ if (!app) return false;
332
+
333
+ // Delete the DB row first so that if it fails (e.g. FK constraint from
334
+ // existing connections), the secret in secure storage remains intact.
335
+ db.delete(oauthApps).where(eq(oauthApps.id, id)).run();
336
+
337
+ const result = await deleteSecureKeyAsync(app.clientSecretCredentialPath);
338
+ if (result === "error") {
339
+ throw new Error(
340
+ `Deleted app ${id} but failed to remove client_secret from secure storage`,
341
+ );
342
+ }
343
+
344
+ return true;
345
+ }
346
+
347
+ // ---------------------------------------------------------------------------
348
+ // Connection operations
349
+ // ---------------------------------------------------------------------------
350
+
351
+ /**
352
+ * Create a new OAuth connection. Generates a UUID and sets status='active'.
353
+ * `metadata` is an optional JSON object for provider-specific token response data.
354
+ */
355
+ export function createConnection(params: {
356
+ oauthAppId: string;
357
+ providerKey: string;
358
+ accountInfo?: string;
359
+ grantedScopes: string[];
360
+ expiresAt?: number;
361
+ hasRefreshToken: boolean;
362
+ label?: string;
363
+ metadata?: Record<string, unknown>;
364
+ /** Override the creation timestamp. Useful in tests to ensure deterministic ordering. */
365
+ createdAt?: number;
366
+ }): OAuthConnectionRow {
367
+ const db = getDb();
368
+ const now = params.createdAt ?? Date.now();
369
+ const id = uuid();
370
+
371
+ const row = {
372
+ id,
373
+ oauthAppId: params.oauthAppId,
374
+ providerKey: params.providerKey,
375
+ accountInfo: params.accountInfo ?? null,
376
+ grantedScopes: JSON.stringify(params.grantedScopes),
377
+ expiresAt: params.expiresAt ?? null,
378
+ hasRefreshToken: params.hasRefreshToken ? 1 : 0,
379
+ status: "active" as const,
380
+ label: params.label ?? null,
381
+ metadata: params.metadata ? JSON.stringify(params.metadata) : null,
382
+ createdAt: now,
383
+ updatedAt: now,
384
+ };
385
+
386
+ db.insert(oauthConnections).values(row).run();
387
+
388
+ return row;
389
+ }
390
+
391
+ /** Look up a connection by its primary key. */
392
+ export function getConnection(id: string): OAuthConnectionRow | undefined {
393
+ const db = getDb();
394
+ return db
395
+ .select()
396
+ .from(oauthConnections)
397
+ .where(eq(oauthConnections.id, id))
398
+ .get();
399
+ }
400
+
401
+ /**
402
+ * Get the most recent active connection for a provider.
403
+ * When `clientId` is provided, only connections linked to the matching app are considered.
404
+ * Returns undefined if no active connection exists.
405
+ */
406
+ export function getConnectionByProvider(
407
+ providerKey: string,
408
+ clientId?: string,
409
+ ): OAuthConnectionRow | undefined {
410
+ const db = getDb();
411
+
412
+ if (clientId) {
413
+ const app = getAppByProviderAndClientId(providerKey, clientId);
414
+ if (!app) return undefined;
415
+ return db
416
+ .select()
417
+ .from(oauthConnections)
418
+ .where(
419
+ and(
420
+ eq(oauthConnections.providerKey, providerKey),
421
+ eq(oauthConnections.oauthAppId, app.id),
422
+ eq(oauthConnections.status, "active"),
423
+ ),
424
+ )
425
+ .orderBy(desc(oauthConnections.createdAt), sql`rowid DESC`)
426
+ .limit(1)
427
+ .get();
428
+ }
429
+
430
+ return db
431
+ .select()
432
+ .from(oauthConnections)
433
+ .where(
434
+ and(
435
+ eq(oauthConnections.providerKey, providerKey),
436
+ eq(oauthConnections.status, "active"),
437
+ ),
438
+ )
439
+ .orderBy(desc(oauthConnections.createdAt), sql`rowid DESC`)
440
+ .limit(1)
441
+ .get();
442
+ }
443
+
444
+ /**
445
+ * Check whether a provider has a usable OAuth connection: an active row in the
446
+ * database AND a corresponding access token in secure storage.
447
+ *
448
+ * This guards against the edge case where the connection row was created/updated
449
+ * but the secure-key write for the access token failed, which would make
450
+ * `resolveOAuthConnection()` throw at usage time.
451
+ */
452
+ export function isProviderConnected(providerKey: string): boolean {
453
+ const conn = getConnectionByProvider(providerKey);
454
+ if (!conn || conn.status !== "active") return false;
455
+ return getSecureKey(`oauth_connection/${conn.id}/access_token`) !== undefined;
456
+ }
457
+
458
+ /**
459
+ * Update fields on an existing connection. Returns true if a row was updated.
460
+ */
461
+ export function updateConnection(
462
+ id: string,
463
+ updates: Partial<{
464
+ oauthAppId: string;
465
+ accountInfo: string;
466
+ grantedScopes: string[];
467
+ /** Pass `null` to explicitly clear a stale expiresAt in the DB. */
468
+ expiresAt: number | null;
469
+ hasRefreshToken: boolean;
470
+ status: string;
471
+ label: string;
472
+ metadata: Record<string, unknown>;
473
+ }>,
474
+ ): boolean {
475
+ const db = getDb();
476
+ const now = Date.now();
477
+
478
+ // Build the set clause, serializing JSON fields and converting booleans.
479
+ // For expiresAt, null means "clear the column" so we check for undefined
480
+ // explicitly rather than truthiness.
481
+ const set: Record<string, unknown> = { updatedAt: now };
482
+ if (updates.oauthAppId !== undefined) set.oauthAppId = updates.oauthAppId;
483
+ if (updates.accountInfo !== undefined) set.accountInfo = updates.accountInfo;
484
+ if (updates.grantedScopes !== undefined)
485
+ set.grantedScopes = JSON.stringify(updates.grantedScopes);
486
+ if (updates.expiresAt !== undefined) set.expiresAt = updates.expiresAt;
487
+ if (updates.hasRefreshToken !== undefined)
488
+ set.hasRefreshToken = updates.hasRefreshToken ? 1 : 0;
489
+ if (updates.status !== undefined) set.status = updates.status;
490
+ if (updates.label !== undefined) set.label = updates.label;
491
+ if (updates.metadata !== undefined)
492
+ set.metadata = JSON.stringify(updates.metadata);
493
+
494
+ db.update(oauthConnections).set(set).where(eq(oauthConnections.id, id)).run();
495
+
496
+ return rawChanges() > 0;
497
+ }
498
+
499
+ /** List connections, optionally filtered by provider key and/or client ID. */
500
+ export function listConnections(
501
+ providerKey?: string,
502
+ clientId?: string,
503
+ ): OAuthConnectionRow[] {
504
+ const db = getDb();
505
+
506
+ let rows: OAuthConnectionRow[];
507
+ if (providerKey) {
508
+ rows = db
509
+ .select()
510
+ .from(oauthConnections)
511
+ .where(eq(oauthConnections.providerKey, providerKey))
512
+ .all();
513
+ } else {
514
+ rows = db.select().from(oauthConnections).all();
515
+ }
516
+
517
+ if (clientId) {
518
+ const matchingAppIds = new Set(
519
+ db
520
+ .select({ id: oauthApps.id })
521
+ .from(oauthApps)
522
+ .where(eq(oauthApps.clientId, clientId))
523
+ .all()
524
+ .map((a) => a.id),
525
+ );
526
+ return rows.filter((r) => matchingAppIds.has(r.oauthAppId));
527
+ }
528
+
529
+ return rows;
530
+ }
531
+
532
+ /** Delete a connection by ID. Returns true if a row was deleted. */
533
+ export function deleteConnection(id: string): boolean {
534
+ const db = getDb();
535
+ db.delete(oauthConnections).where(eq(oauthConnections.id, id)).run();
536
+ return rawChanges() > 0;
537
+ }
538
+
539
+ // ---------------------------------------------------------------------------
540
+ // Disconnect (full cleanup)
541
+ // ---------------------------------------------------------------------------
542
+
543
+ /**
544
+ * Fully disconnect an OAuth provider: delete the new-format secure keys
545
+ * (access_token and refresh_token) and remove the connection row from SQLite.
546
+ *
547
+ * Returns `"disconnected"` if a connection was found and cleaned up,
548
+ * `"not-found"` if no active connection existed for the given provider,
549
+ * or `"error"` if secure key deletion failed (connection row is preserved
550
+ * to avoid orphaning secrets).
551
+ */
552
+ export async function disconnectOAuthProvider(
553
+ providerKey: string,
554
+ clientId?: string,
555
+ ): Promise<"disconnected" | "not-found" | "error"> {
556
+ const conn = getConnectionByProvider(providerKey, clientId);
557
+ if (!conn) return "not-found";
558
+
559
+ const r1 = await deleteSecureKeyAsync(
560
+ `oauth_connection/${conn.id}/access_token`,
561
+ );
562
+ const r2 = await deleteSecureKeyAsync(
563
+ `oauth_connection/${conn.id}/refresh_token`,
564
+ );
565
+
566
+ if (r1 === "error" || r2 === "error") {
567
+ log.warn(
568
+ {
569
+ providerKey,
570
+ connectionId: conn.id,
571
+ accessTokenResult: r1,
572
+ refreshTokenResult: r2,
573
+ },
574
+ "Failed to delete OAuth secure keys — skipping connection row deletion to avoid orphaning secrets",
575
+ );
576
+ return "error";
577
+ }
578
+
579
+ deleteConnection(conn.id);
580
+
581
+ return "disconnected";
582
+ }