@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
@@ -44,6 +44,62 @@ mock.module("../tools/registry.js", () => ({
44
44
  registerTool: () => {},
45
45
  }));
46
46
 
47
+ // ---------------------------------------------------------------------------
48
+ // Mock oauth-store to avoid SQLite dependency in unit tests
49
+ // ---------------------------------------------------------------------------
50
+
51
+ let mockGetMostRecentAppByProvider: ReturnType<
52
+ typeof mock<(key: string) => unknown>
53
+ >;
54
+ let mockGetAppByProviderAndClientId: ReturnType<
55
+ typeof mock<(key: string, clientId: string) => unknown>
56
+ >;
57
+ let mockGetProvider: ReturnType<typeof mock<(key: string) => unknown>>;
58
+
59
+ mock.module("../oauth/oauth-store.js", () => {
60
+ mockGetMostRecentAppByProvider = mock(() => undefined);
61
+ mockGetAppByProviderAndClientId = mock(() => undefined);
62
+ mockGetProvider = mock(() => undefined);
63
+ return {
64
+ getMostRecentAppByProvider: mockGetMostRecentAppByProvider,
65
+ getAppByProviderAndClientId: mockGetAppByProviderAndClientId,
66
+ getProvider: mockGetProvider,
67
+ listConnections: mock(() => []),
68
+ seedProviders: mock(() => {}),
69
+ disconnectOAuthProvider: mock(async () => "not-found" as const),
70
+ };
71
+ });
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Mock public ingress URL — not available in unit tests. The connect
75
+ // orchestrator dynamically imports this for non-interactive flows.
76
+ // ---------------------------------------------------------------------------
77
+
78
+ mock.module("../inbound/public-ingress-urls.js", () => ({
79
+ getPublicBaseUrl: () => {
80
+ throw new Error("No public ingress URL configured");
81
+ },
82
+ }));
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Mock prepareOAuth2Flow — unit tests should not start real loopback HTTP
86
+ // servers. The connect orchestrator still runs its own validation logic
87
+ // (scope policy, non-interactive ingress checks, etc.) but the actual
88
+ // OAuth flow setup is stubbed.
89
+ // ---------------------------------------------------------------------------
90
+
91
+ mock.module("../security/oauth2.js", () => ({
92
+ prepareOAuth2Flow: mock(async () => ({
93
+ authUrl: "https://mock-auth-url.example.com/authorize",
94
+ state: "mock-state",
95
+ completion: new Promise(() => {}),
96
+ })),
97
+ startOAuth2Flow: mock(async () => ({
98
+ grantedScopes: [],
99
+ tokens: { access_token: "mock-token" },
100
+ })),
101
+ }));
102
+
47
103
  // ---------------------------------------------------------------------------
48
104
  // Imports under test
49
105
  // ---------------------------------------------------------------------------
@@ -473,18 +529,50 @@ describe("credential_store tool — prompt action", () => {
473
529
  // ---------------------------------------------------------------------------
474
530
 
475
531
  describe("credential_store tool — oauth2_connect error paths", () => {
532
+ /** Well-known provider rows returned by the mocked getProvider */
533
+ const wellKnownProviders: Record<string, object> = {
534
+ "integration:gmail": {
535
+ key: "integration:gmail",
536
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
537
+ tokenUrl: "https://oauth2.googleapis.com/token",
538
+ defaultScopes: JSON.stringify(["https://mail.google.com/"]),
539
+ scopePolicy: JSON.stringify({}),
540
+ callbackTransport: "loopback",
541
+ loopbackPort: 8756,
542
+ },
543
+ "integration:slack": {
544
+ key: "integration:slack",
545
+ authUrl: "https://slack.com/oauth/v2/authorize",
546
+ tokenUrl: "https://slack.com/api/oauth.v2.access",
547
+ defaultScopes: JSON.stringify(["channels:read"]),
548
+ scopePolicy: JSON.stringify({}),
549
+ },
550
+ };
551
+
476
552
  beforeEach(() => {
477
553
  if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
478
554
  mkdirSync(TEST_DIR, { recursive: true });
479
555
  _setStorePath(STORE_PATH);
480
556
  _resetBackend();
481
557
  _setMetadataPath(join(TEST_DIR, "metadata.json"));
558
+ // Return well-known provider rows so vault.ts knows gmail/slack are
559
+ // registered, and custom providers return undefined.
560
+ mockGetProvider.mockImplementation(
561
+ (key: string) => wellKnownProviders[key] ?? undefined,
562
+ );
563
+ mockGetMostRecentAppByProvider.mockClear();
564
+ mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
565
+ mockGetAppByProviderAndClientId.mockClear();
566
+ mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
482
567
  });
483
568
 
484
569
  afterEach(() => {
485
570
  _setMetadataPath(null);
486
571
  _setStorePath(null);
487
572
  _resetBackend();
573
+ mockGetProvider.mockImplementation(() => undefined);
574
+ mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
575
+ mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
488
576
  });
489
577
 
490
578
  test("requires service parameter", async () => {
@@ -496,55 +584,38 @@ describe("credential_store tool — oauth2_connect error paths", () => {
496
584
  expect(result.content).toContain("service is required");
497
585
  });
498
586
 
499
- test("requires auth_url for unknown service", async () => {
500
- const result = await credentialStoreTool.execute(
501
- {
502
- action: "oauth2_connect",
503
- service: "custom-svc",
504
- token_url: "https://t",
505
- scopes: ["read"],
506
- },
507
- _ctx,
508
- );
509
- expect(result.isError).toBe(true);
510
- expect(result.content).toContain("auth_url is required");
511
- });
512
-
513
- test("requires token_url for unknown service", async () => {
514
- const result = await credentialStoreTool.execute(
515
- {
516
- action: "oauth2_connect",
517
- service: "custom-svc",
518
- auth_url: "https://a",
519
- scopes: ["read"],
520
- },
521
- _ctx,
522
- );
523
- expect(result.isError).toBe(true);
524
- expect(result.content).toContain("token_url is required");
525
- });
526
-
527
- test("requires scopes for unknown service", async () => {
587
+ test("rejects unknown service without registered provider", async () => {
528
588
  const result = await credentialStoreTool.execute(
529
589
  {
530
590
  action: "oauth2_connect",
531
591
  service: "custom-svc",
532
592
  auth_url: "https://a",
533
593
  token_url: "https://t",
594
+ scopes: ["read"],
534
595
  },
535
596
  _ctx,
536
597
  );
537
598
  expect(result.isError).toBe(true);
538
- expect(result.content).toContain("scopes is required");
599
+ expect(result.content).toContain("no OAuth provider registered");
539
600
  });
540
601
 
541
602
  test("requires client_id", async () => {
603
+ mockGetProvider.mockImplementation((key: string) => {
604
+ if (key === "custom-svc") {
605
+ return {
606
+ key: "custom-svc",
607
+ authUrl: "https://auth.example.com",
608
+ tokenUrl: "https://token.example.com",
609
+ defaultScopes: JSON.stringify(["read"]),
610
+ scopePolicy: JSON.stringify({}),
611
+ };
612
+ }
613
+ return wellKnownProviders[key] ?? undefined;
614
+ });
542
615
  const result = await credentialStoreTool.execute(
543
616
  {
544
617
  action: "oauth2_connect",
545
618
  service: "custom-svc",
546
- auth_url: "https://auth.example.com",
547
- token_url: "https://token.example.com",
548
619
  scopes: ["read"],
549
620
  },
550
621
  _ctx,
@@ -554,6 +625,21 @@ describe("credential_store tool — oauth2_connect error paths", () => {
554
625
  });
555
626
 
556
627
  test("requires interactive context", async () => {
628
+ // Register custom-svc as a provider so the orchestrator finds it
629
+ // and reaches the non-interactive check (gateway transport).
630
+ mockGetProvider.mockImplementation((key: string) => {
631
+ if (key === "custom-svc") {
632
+ return {
633
+ key: "custom-svc",
634
+ authUrl: "https://auth.example.com",
635
+ tokenUrl: "https://token.example.com",
636
+ defaultScopes: JSON.stringify(["read"]),
637
+ scopePolicy: JSON.stringify({}),
638
+ };
639
+ }
640
+ return wellKnownProviders[key] ?? undefined;
641
+ });
642
+
557
643
  const result = await credentialStoreTool.execute(
558
644
  {
559
645
  action: "oauth2_connect",
@@ -596,18 +682,26 @@ describe("credential_store tool — oauth2_connect error paths", () => {
596
682
  expect(result.content).toContain("client_id is required");
597
683
  });
598
684
 
599
- test("uses stored client_id from metadata", async () => {
600
- // Store client_id in metadata (the canonical source) and client_secret
601
- // in the secure store — the requiresClientSecret guardrail will
602
- // short-circuit if client_secret is missing, so we need both to
603
- // validate that stored client_id is resolved correctly.
604
- upsertCredentialMetadata("integration:gmail", "access_token", {
605
- oauth2ClientId: "stored-client-id-123",
606
- });
607
- setSecureKey(
608
- credentialKey("integration:gmail", "client_secret"),
609
- "test-secret",
610
- );
685
+ test("uses stored client_id from oauth-store DB", async () => {
686
+ // Mock getMostRecentAppByProvider to return an app with a client_id
687
+ // and store client_secret in the secure store.
688
+ mockGetMostRecentAppByProvider.mockImplementation(() => ({
689
+ id: "test-app-id",
690
+ providerKey: "integration:gmail",
691
+ clientId: "stored-client-id-123",
692
+ clientSecretCredentialPath: "oauth_app/test-app-id/client_secret",
693
+ createdAt: Date.now(),
694
+ }));
695
+ mockGetProvider.mockImplementation(() => ({
696
+ key: "integration:gmail",
697
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
698
+ tokenUrl: "https://oauth2.googleapis.com/token",
699
+ defaultScopes: JSON.stringify(["https://mail.google.com/"]),
700
+ scopePolicy: JSON.stringify({}),
701
+ callbackTransport: "loopback",
702
+ loopbackPort: 8756,
703
+ }));
704
+ setSecureKey("oauth_app/test-app-id/client_secret", "test-secret");
611
705
 
612
706
  const result = await credentialStoreTool.execute(
613
707
  {
@@ -624,14 +718,151 @@ describe("credential_store tool — oauth2_connect error paths", () => {
624
718
  expect(result.content).toContain("To connect gmail, open this link");
625
719
  expect(result.content).not.toContain("client_id is required");
626
720
  expect(result.content).not.toContain("client_secret is required");
721
+
722
+ // Reset mocks
723
+ mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
724
+ mockGetProvider.mockImplementation(() => undefined);
725
+ });
726
+
727
+ test("uses getAppByProviderAndClientId when client_id is provided without client_secret", async () => {
728
+ // When client_id is supplied but client_secret is not, the vault should
729
+ // look up the matching app via getAppByProviderAndClientId (not the
730
+ // most-recent-app heuristic) so the secret comes from the correct app.
731
+ mockGetAppByProviderAndClientId.mockImplementation(
732
+ (providerKey: string, cId: string) => {
733
+ if (
734
+ providerKey === "integration:gmail" &&
735
+ cId === "caller-supplied-client-id"
736
+ ) {
737
+ return {
738
+ id: "matched-app-id",
739
+ providerKey: "integration:gmail",
740
+ clientId: "caller-supplied-client-id",
741
+ clientSecretCredentialPath:
742
+ "oauth_app/matched-app-id/client_secret",
743
+ createdAt: Date.now(),
744
+ };
745
+ }
746
+ return undefined;
747
+ },
748
+ );
749
+ mockGetProvider.mockImplementation(() => ({
750
+ key: "integration:gmail",
751
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
752
+ tokenUrl: "https://oauth2.googleapis.com/token",
753
+ defaultScopes: JSON.stringify(["https://mail.google.com/"]),
754
+ scopePolicy: JSON.stringify({}),
755
+ callbackTransport: "loopback",
756
+ loopbackPort: 8756,
757
+ }));
758
+ setSecureKey("oauth_app/matched-app-id/client_secret", "matched-secret");
759
+
760
+ const result = await credentialStoreTool.execute(
761
+ {
762
+ action: "oauth2_connect",
763
+ service: "gmail",
764
+ client_id: "caller-supplied-client-id",
765
+ },
766
+ { ..._ctx, isInteractive: false },
767
+ );
768
+
769
+ // Should succeed — client_secret resolved from the matched app
770
+ expect(result.isError).toBe(false);
771
+ expect(result.content).toContain("To connect gmail, open this link");
772
+ // getMostRecentAppByProvider should NOT have been called since client_id was known
773
+ expect(mockGetMostRecentAppByProvider).not.toHaveBeenCalled();
774
+
775
+ // Reset mocks
776
+ mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
777
+ mockGetProvider.mockImplementation(() => undefined);
778
+ });
779
+
780
+ test("falls back to getMostRecentAppByProvider when client_id is not provided", async () => {
781
+ // When neither client_id nor client_secret is provided, the vault should
782
+ // use getMostRecentAppByProvider (the fallback heuristic).
783
+ mockGetMostRecentAppByProvider.mockImplementation(() => ({
784
+ id: "recent-app-id",
785
+ providerKey: "integration:gmail",
786
+ clientId: "recent-client-id",
787
+ clientSecretCredentialPath: "oauth_app/recent-app-id/client_secret",
788
+ createdAt: Date.now(),
789
+ }));
790
+ mockGetProvider.mockImplementation(() => ({
791
+ key: "integration:gmail",
792
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
793
+ tokenUrl: "https://oauth2.googleapis.com/token",
794
+ defaultScopes: JSON.stringify(["https://mail.google.com/"]),
795
+ scopePolicy: JSON.stringify({}),
796
+ callbackTransport: "loopback",
797
+ loopbackPort: 8756,
798
+ }));
799
+ setSecureKey("oauth_app/recent-app-id/client_secret", "recent-secret");
800
+
801
+ const result = await credentialStoreTool.execute(
802
+ {
803
+ action: "oauth2_connect",
804
+ service: "gmail",
805
+ },
806
+ { ..._ctx, isInteractive: false },
807
+ );
808
+
809
+ expect(result.isError).toBe(false);
810
+ expect(result.content).toContain("To connect gmail, open this link");
811
+ // getAppByProviderAndClientId should NOT have been called since client_id was unknown
812
+ expect(mockGetAppByProviderAndClientId).not.toHaveBeenCalled();
813
+
814
+ // Reset mocks
815
+ mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
816
+ mockGetProvider.mockImplementation(() => undefined);
817
+ });
818
+
819
+ test("getAppByProviderAndClientId returning undefined leaves client_secret unresolved", async () => {
820
+ // When client_id is provided but getAppByProviderAndClientId returns no
821
+ // matching app, client_secret remains unresolved and the vault should
822
+ // report the missing secret error.
823
+ mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
824
+ mockGetProvider.mockImplementation(() => ({
825
+ key: "integration:gmail",
826
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
827
+ tokenUrl: "https://oauth2.googleapis.com/token",
828
+ defaultScopes: JSON.stringify(["https://mail.google.com/"]),
829
+ }));
830
+
831
+ const result = await credentialStoreTool.execute(
832
+ {
833
+ action: "oauth2_connect",
834
+ service: "gmail",
835
+ client_id: "unknown-client-id",
836
+ },
837
+ _ctx,
838
+ );
839
+
840
+ expect(result.isError).toBe(true);
841
+ expect(result.content).toContain("client_secret is required for gmail");
842
+ // getMostRecentAppByProvider should NOT have been called
843
+ expect(mockGetMostRecentAppByProvider).not.toHaveBeenCalled();
844
+
845
+ // Reset mocks
846
+ mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
847
+ mockGetProvider.mockImplementation(() => undefined);
627
848
  });
628
849
 
629
850
  test("rejects when client_secret is missing for service that requires it", async () => {
630
- // Store only client_id in metadata client_secret is intentionally
631
- // absent to validate the requiresClientSecret guardrail.
632
- upsertCredentialMetadata("integration:gmail", "access_token", {
633
- oauth2ClientId: "stored-client-id-456",
634
- });
851
+ // Mock getMostRecentAppByProvider to return an app with client_id but
852
+ // no client_secret in secure storage — validates the requiresClientSecret
853
+ // guardrail.
854
+ mockGetMostRecentAppByProvider.mockImplementation(() => ({
855
+ id: "test-app-id-no-secret",
856
+ providerKey: "integration:gmail",
857
+ clientId: "stored-client-id-456",
858
+ createdAt: Date.now(),
859
+ }));
860
+ mockGetProvider.mockImplementation(() => ({
861
+ key: "integration:gmail",
862
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
863
+ tokenUrl: "https://oauth2.googleapis.com/token",
864
+ defaultScopes: JSON.stringify(["https://mail.google.com/"]),
865
+ }));
635
866
 
636
867
  const result = await credentialStoreTool.execute(
637
868
  {
@@ -643,6 +874,10 @@ describe("credential_store tool — oauth2_connect error paths", () => {
643
874
 
644
875
  expect(result.isError).toBe(true);
645
876
  expect(result.content).toContain("client_secret is required for gmail");
877
+
878
+ // Reset mocks
879
+ mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
880
+ mockGetProvider.mockImplementation(() => undefined);
646
881
  });
647
882
  });
648
883
 
@@ -65,6 +65,69 @@ mock.module("../security/oauth2.js", () => {
65
65
  };
66
66
  });
67
67
 
68
+ // ---------------------------------------------------------------------------
69
+ // Mock oauth-store — token-manager reads refresh config from SQLite
70
+ // ---------------------------------------------------------------------------
71
+
72
+ /** Mutable per-test map of provider connections for getConnectionByProvider */
73
+ const mockConnections = new Map<
74
+ string,
75
+ {
76
+ id: string;
77
+ providerKey: string;
78
+ oauthAppId: string;
79
+ expiresAt: number | null;
80
+ }
81
+ >();
82
+ const mockApps = new Map<
83
+ string,
84
+ {
85
+ id: string;
86
+ providerKey: string;
87
+ clientId: string;
88
+ clientSecretCredentialPath: string;
89
+ }
90
+ >();
91
+ const mockProviders = new Map<
92
+ string,
93
+ {
94
+ key: string;
95
+ tokenUrl: string;
96
+ tokenEndpointAuthMethod?: string;
97
+ }
98
+ >();
99
+
100
+ let mockDisconnectOAuthProvider: ReturnType<
101
+ typeof mock<
102
+ (providerKey: string) => Promise<"disconnected" | "not-found" | "error">
103
+ >
104
+ >;
105
+
106
+ mock.module("../oauth/oauth-store.js", () => {
107
+ mockDisconnectOAuthProvider = mock((providerKey: string) =>
108
+ Promise.resolve(
109
+ mockConnections.has(providerKey)
110
+ ? ("disconnected" as const)
111
+ : ("not-found" as const),
112
+ ),
113
+ );
114
+ return {
115
+ disconnectOAuthProvider: mockDisconnectOAuthProvider,
116
+ getConnectionByProvider: (service: string) => mockConnections.get(service),
117
+ getConnection: (id: string) => {
118
+ for (const conn of mockConnections.values()) {
119
+ if (conn.id === id) return conn;
120
+ }
121
+ return undefined;
122
+ },
123
+ getApp: (id: string) => mockApps.get(id),
124
+ getProvider: (key: string) => mockProviders.get(key),
125
+ updateConnection: () => {},
126
+ getMostRecentAppByProvider: () => undefined,
127
+ listConnections: () => [],
128
+ };
129
+ });
130
+
68
131
  // ---------------------------------------------------------------------------
69
132
  // Import the module under test
70
133
  // ---------------------------------------------------------------------------
@@ -85,7 +148,6 @@ import {
85
148
  import {
86
149
  _setMetadataPath,
87
150
  getCredentialMetadata,
88
- upsertCredentialMetadata,
89
151
  } from "../tools/credentials/metadata-store.js";
90
152
  import { credentialStoreTool } from "../tools/credentials/vault.js";
91
153
  import type { ToolContext } from "../tools/types.js";
@@ -214,12 +276,15 @@ describe("credential_store tool", () => {
214
276
  }
215
277
  _setStorePath(STORE_PATH);
216
278
  _setMetadataPath(join(TEST_DIR, "metadata.json"));
279
+ mockDisconnectOAuthProvider.mockClear();
280
+ mockConnections.clear();
217
281
  });
218
282
 
219
283
  afterEach(() => {
220
284
  _setMetadataPath(null);
221
285
  _setStorePath(null);
222
286
  _resetBackend();
287
+ mockConnections.clear();
223
288
  });
224
289
 
225
290
  afterAll(() => {
@@ -664,6 +729,44 @@ describe("credential_store tool", () => {
664
729
  expect(result.isError).toBe(true);
665
730
  expect(result.content).toContain("field is required");
666
731
  });
732
+
733
+ test("delete also disconnects OAuth connection for the service", async () => {
734
+ // Store a credential via the real tool so metadata exists
735
+ await credentialStoreTool.execute(
736
+ {
737
+ action: "store",
738
+ service: "integration:gmail",
739
+ field: "api_key",
740
+ value: "test-value",
741
+ },
742
+ _ctx,
743
+ );
744
+
745
+ // Simulate an active OAuth connection for this service
746
+ mockConnections.set("integration:gmail", {
747
+ id: "conn-gmail",
748
+ providerKey: "integration:gmail",
749
+ oauthAppId: "app-gmail",
750
+ expiresAt: Date.now() + 3600_000,
751
+ });
752
+
753
+ const result = await credentialStoreTool.execute(
754
+ {
755
+ action: "delete",
756
+ service: "integration:gmail",
757
+ field: "api_key",
758
+ },
759
+ _ctx,
760
+ );
761
+
762
+ expect(result.isError).toBe(false);
763
+ expect(result.content).toContain("Deleted credential");
764
+ // Verify disconnectOAuthProvider was called with the service name
765
+ expect(mockDisconnectOAuthProvider).toHaveBeenCalledTimes(1);
766
+ expect(mockDisconnectOAuthProvider).toHaveBeenCalledWith(
767
+ "integration:gmail",
768
+ );
769
+ });
667
770
  });
668
771
 
669
772
  // -----------------------------------------------------------------------
@@ -1172,6 +1275,10 @@ describe("withValidToken refresh deduplication", () => {
1172
1275
  _resetRefreshBreakers();
1173
1276
  _resetInflightRefreshes();
1174
1277
  mockRefreshOAuth2Token.mockClear();
1278
+ // Clear mock oauth-store maps
1279
+ mockConnections.clear();
1280
+ mockApps.clear();
1281
+ mockProviders.clear();
1175
1282
  });
1176
1283
 
1177
1284
  afterEach(() => {
@@ -1180,6 +1287,9 @@ describe("withValidToken refresh deduplication", () => {
1180
1287
  _resetBackend();
1181
1288
  _resetRefreshBreakers();
1182
1289
  _resetInflightRefreshes();
1290
+ mockConnections.clear();
1291
+ mockApps.clear();
1292
+ mockProviders.clear();
1183
1293
  });
1184
1294
 
1185
1295
  afterAll(() => {
@@ -1187,26 +1297,49 @@ describe("withValidToken refresh deduplication", () => {
1187
1297
  });
1188
1298
 
1189
1299
  /**
1190
- * Helper: set up a service with an access token, refresh token, and OAuth2
1191
- * metadata so that token refresh can proceed through doRefresh().
1300
+ * Helper: set up a service with an access token, refresh token, and
1301
+ * mock DB data so that token refresh can proceed through doRefresh().
1302
+ *
1303
+ * OAuth-specific fields (tokenUrl, clientId, expiresAt) are now stored
1304
+ * in the SQLite oauth-store. The mock maps simulate the DB layer.
1192
1305
  */
1193
1306
  function setupService(
1194
1307
  service: string,
1195
1308
  opts?: { expired?: boolean; accessToken?: string },
1196
1309
  ) {
1197
1310
  const accessToken = opts?.accessToken ?? "old-access-token";
1198
- setSecureKey(credentialKey(service, "access_token"), accessToken);
1311
+
1312
+ // Seed mock oauth-store maps so token-manager can resolve refresh config
1313
+ const appId = `app-${service}`;
1314
+ const connId = `conn-${service}`;
1315
+
1316
+ // Store access token under the oauth_connection key path that
1317
+ // withValidToken reads (not the legacy credentialKey path).
1318
+ setSecureKey(`oauth_connection/${connId}/access_token`, accessToken);
1319
+ mockProviders.set(service, {
1320
+ key: service,
1321
+ tokenUrl: "https://oauth.example.com/token",
1322
+ });
1323
+ mockApps.set(appId, {
1324
+ id: appId,
1325
+ providerKey: service,
1326
+ clientId: "test-client-id",
1327
+ clientSecretCredentialPath: `oauth_app/${appId}/client_secret`,
1328
+ });
1329
+ mockConnections.set(service, {
1330
+ id: connId,
1331
+ providerKey: service,
1332
+ oauthAppId: appId,
1333
+ expiresAt: opts?.expired
1334
+ ? Date.now() - 60_000 // expired 1 minute ago
1335
+ : Date.now() + 3600_000, // expires in 1 hour
1336
+ });
1337
+ // Store refresh token and client_secret in secure keys (token-manager reads them)
1199
1338
  setSecureKey(
1200
- credentialKey(service, "refresh_token"),
1339
+ `oauth_connection/${connId}/refresh_token`,
1201
1340
  "valid-refresh-token",
1202
1341
  );
1203
- upsertCredentialMetadata(service, "access_token", {
1204
- oauth2TokenUrl: "https://oauth.example.com/token",
1205
- oauth2ClientId: "test-client-id",
1206
- ...(opts?.expired
1207
- ? { expiresAt: Date.now() - 60_000 } // expired 1 minute ago
1208
- : { expiresAt: Date.now() + 3600_000 }), // expires in 1 hour
1209
- });
1342
+ setSecureKey(`oauth_app/${appId}/client_secret`, "test-client-secret");
1210
1343
  }
1211
1344
 
1212
1345
  test("3 concurrent 401 refreshes for the same service call doRefresh exactly once", async () => {
@@ -1335,22 +1468,23 @@ describe("withValidToken refresh deduplication", () => {
1335
1468
 
1336
1469
  const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
1337
1470
 
1338
- // First call triggers a refresh
1471
+ // First call triggers a refresh (old token → 401 → refresh → token-1)
1339
1472
  const r1 = await withValidToken(
1340
1473
  "integration:gmail",
1341
1474
  async (token: string) => {
1342
- if (token === "old-access-token") throw err401;
1475
+ if (token !== "token-1") throw err401;
1343
1476
  return token;
1344
1477
  },
1345
1478
  );
1346
1479
  expect(r1).toBe("token-1");
1347
1480
  expect(refreshCount).toBe(1);
1348
1481
 
1349
- // Set up so the next call will also get a 401 (token-1 stored from first refresh)
1482
+ // Second call also triggers a 401 to verify dedup state was cleaned up
1483
+ // and a new refresh is allowed (not deduplicated with the first).
1350
1484
  const r2 = await withValidToken(
1351
1485
  "integration:gmail",
1352
1486
  async (token: string) => {
1353
- if (token === "token-1") throw err401;
1487
+ if (token !== "token-2") throw err401;
1354
1488
  return token;
1355
1489
  },
1356
1490
  );