@vellumai/assistant 0.5.1 → 0.5.3

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 (405) hide show
  1. package/ARCHITECTURE.md +163 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/docs/skills.md +100 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/agent-loop.test.ts +111 -0
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  8. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  9. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  10. package/src/__tests__/app-executors.test.ts +1 -291
  11. package/src/__tests__/app-git-history.test.ts +4 -4
  12. package/src/__tests__/app-routes-csp.test.ts +1 -0
  13. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  14. package/src/__tests__/attachments-store.test.ts +169 -21
  15. package/src/__tests__/attachments.test.ts +115 -1
  16. package/src/__tests__/btw-routes.test.ts +1 -0
  17. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  18. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  19. package/src/__tests__/checker.test.ts +54 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  22. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  23. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  24. package/src/__tests__/config-schema.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +297 -2
  27. package/src/__tests__/conversation-attachments.test.ts +17 -19
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  29. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  30. package/src/__tests__/conversation-error.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  32. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  33. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  34. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  35. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  36. package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
  37. package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
  38. package/src/__tests__/conversation-queue.test.ts +36 -1
  39. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  40. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  43. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  44. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  45. package/src/__tests__/conversation-store.test.ts +24 -21
  46. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  47. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  48. package/src/__tests__/conversation-title-service.test.ts +137 -0
  49. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  50. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  51. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  52. package/src/__tests__/conversation-wipe.test.ts +226 -0
  53. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  54. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  55. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  56. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  57. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  58. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  59. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  60. package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
  61. package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
  62. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
  63. package/src/__tests__/diagnostics-export.test.ts +70 -1
  64. package/src/__tests__/first-greeting.test.ts +80 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  66. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  67. package/src/__tests__/history-repair.test.ts +32 -10
  68. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  69. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  70. package/src/__tests__/inline-command-runner.test.ts +311 -0
  71. package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
  72. package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
  73. package/src/__tests__/list-messages-attachments.test.ts +96 -0
  74. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  75. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  76. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  77. package/src/__tests__/media-generate-image.test.ts +47 -94
  78. package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
  79. package/src/__tests__/memory-brief-time.test.ts +285 -0
  80. package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
  81. package/src/__tests__/memory-chunk-archive.test.ts +400 -0
  82. package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
  83. package/src/__tests__/memory-episode-archive.test.ts +370 -0
  84. package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  86. package/src/__tests__/memory-observation-archive.test.ts +375 -0
  87. package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
  88. package/src/__tests__/memory-recall-quality.test.ts +7 -7
  89. package/src/__tests__/memory-reducer-store.test.ts +728 -0
  90. package/src/__tests__/memory-reducer-types.test.ts +699 -0
  91. package/src/__tests__/memory-reducer.test.ts +698 -0
  92. package/src/__tests__/memory-regressions.test.ts +6 -4
  93. package/src/__tests__/memory-simplified-config.test.ts +281 -0
  94. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  95. package/src/__tests__/migration-export-http.test.ts +3 -1
  96. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  97. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  98. package/src/__tests__/mime-builder.test.ts +3 -2
  99. package/src/__tests__/non-member-access-request.test.ts +12 -1
  100. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  101. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  102. package/src/__tests__/oauth-store.test.ts +115 -0
  103. package/src/__tests__/parse-identity-fields.test.ts +129 -0
  104. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  105. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  106. package/src/__tests__/recording-handler.test.ts +17 -0
  107. package/src/__tests__/registry.test.ts +3 -8
  108. package/src/__tests__/relay-server.test.ts +1 -1
  109. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  110. package/src/__tests__/schema-transforms.test.ts +165 -5
  111. package/src/__tests__/server-history-render.test.ts +2 -2
  112. package/src/__tests__/skill-load-inline-command.test.ts +598 -0
  113. package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
  114. package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
  115. package/src/__tests__/skills-transitive-hash.test.ts +333 -0
  116. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  117. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  118. package/src/__tests__/starter-task-flow.test.ts +1 -0
  119. package/src/__tests__/suggestion-routes.test.ts +443 -0
  120. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  121. package/src/__tests__/swarm-recursion.test.ts +1 -0
  122. package/src/__tests__/swarm-tool.test.ts +1 -0
  123. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  124. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  125. package/src/__tests__/top-level-renderer.test.ts +22 -0
  126. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  127. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
  128. package/src/__tests__/web-fetch.test.ts +6 -2
  129. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  130. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  131. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  132. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  133. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  134. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  135. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  136. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  137. package/src/agent/attachments.ts +27 -1
  138. package/src/agent/loop.ts +29 -1
  139. package/src/avatar/traits-png-sync.ts +80 -25
  140. package/src/bundler/app-bundler.ts +4 -4
  141. package/src/calls/call-domain.ts +1 -0
  142. package/src/calls/voice-session-bridge.ts +1 -0
  143. package/src/cli/commands/auth.ts +92 -0
  144. package/src/cli/commands/avatar.ts +7 -6
  145. package/src/cli/commands/config.ts +2 -0
  146. package/src/cli/commands/oauth/providers.ts +29 -0
  147. package/src/cli/program.ts +12 -0
  148. package/src/cli.ts +15 -48
  149. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  150. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  151. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  152. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  153. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  154. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  155. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  156. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  157. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  158. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  159. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  160. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  161. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  162. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  163. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  164. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  165. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  166. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  170. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  171. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  172. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  173. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  174. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  175. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  176. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  177. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  178. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  179. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  180. package/src/config/bundled-tool-registry.ts +2 -14
  181. package/src/config/feature-flag-registry.json +24 -0
  182. package/src/config/loader.ts +65 -0
  183. package/src/config/raw-config-utils.ts +58 -0
  184. package/src/config/schema-utils.ts +28 -7
  185. package/src/config/schema.ts +20 -0
  186. package/src/config/schemas/elevenlabs.ts +18 -0
  187. package/src/config/schemas/memory-lifecycle.ts +4 -2
  188. package/src/config/schemas/memory-simplified.ts +101 -0
  189. package/src/config/schemas/memory-storage.ts +1 -1
  190. package/src/config/schemas/memory.ts +4 -0
  191. package/src/config/schemas/services.ts +8 -6
  192. package/src/config/skills.ts +50 -4
  193. package/src/contacts/contact-store.ts +13 -6
  194. package/src/contacts/contacts-write.ts +0 -1
  195. package/src/context/window-manager.ts +13 -2
  196. package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
  197. package/src/daemon/conversation-agent-loop.ts +127 -20
  198. package/src/daemon/conversation-attachments.ts +18 -36
  199. package/src/daemon/conversation-error.ts +2 -1
  200. package/src/daemon/conversation-history.ts +18 -4
  201. package/src/daemon/conversation-lifecycle.ts +50 -16
  202. package/src/daemon/conversation-messaging.ts +70 -26
  203. package/src/daemon/conversation-process.ts +58 -34
  204. package/src/daemon/conversation-runtime-assembly.ts +22 -38
  205. package/src/daemon/conversation-slash.ts +121 -256
  206. package/src/daemon/conversation-surfaces.ts +170 -24
  207. package/src/daemon/conversation-tool-setup.ts +0 -6
  208. package/src/daemon/conversation-workspace.ts +21 -1
  209. package/src/daemon/conversation.ts +69 -30
  210. package/src/daemon/first-greeting.ts +35 -0
  211. package/src/daemon/handlers/config-embeddings.ts +156 -0
  212. package/src/daemon/handlers/config-model.ts +62 -26
  213. package/src/daemon/handlers/conversations.ts +0 -23
  214. package/src/daemon/handlers/identity.ts +12 -1
  215. package/src/daemon/handlers/recording.ts +26 -21
  216. package/src/daemon/host-cu-proxy.ts +2 -2
  217. package/src/daemon/lifecycle.ts +115 -65
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/conversations.ts +18 -0
  220. package/src/daemon/message-types/messages.ts +1 -0
  221. package/src/daemon/message-types/shared.ts +2 -0
  222. package/src/daemon/message-types/surfaces.ts +2 -0
  223. package/src/daemon/message-types/upgrades.ts +23 -0
  224. package/src/daemon/server.ts +83 -12
  225. package/src/daemon/shutdown-handlers.ts +8 -5
  226. package/src/daemon/startup-error.ts +9 -0
  227. package/src/daemon/tool-side-effects.ts +11 -28
  228. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  229. package/src/followups/followup-store.ts +47 -1
  230. package/src/instrument.ts +0 -4
  231. package/src/media/app-icon-generator.ts +2 -2
  232. package/src/memory/app-git-service.ts +28 -16
  233. package/src/memory/app-store.ts +230 -41
  234. package/src/memory/archive-store.ts +400 -0
  235. package/src/memory/attachments-store.ts +558 -130
  236. package/src/memory/brief-formatting.ts +33 -0
  237. package/src/memory/brief-open-loops.ts +266 -0
  238. package/src/memory/brief-time.ts +161 -0
  239. package/src/memory/brief.ts +75 -0
  240. package/src/memory/conversation-attention-store.ts +70 -0
  241. package/src/memory/conversation-crud.ts +591 -8
  242. package/src/memory/conversation-directories.ts +125 -0
  243. package/src/memory/conversation-disk-view.ts +390 -0
  244. package/src/memory/conversation-key-store.ts +17 -5
  245. package/src/memory/conversation-queries.ts +5 -1
  246. package/src/memory/conversation-title-service.ts +21 -49
  247. package/src/memory/db-init.ts +40 -0
  248. package/src/memory/embedding-backend.ts +42 -53
  249. package/src/memory/embedding-gemini.test.ts +4 -4
  250. package/src/memory/embedding-local.ts +1 -3
  251. package/src/memory/embedding-ollama.ts +1 -3
  252. package/src/memory/embedding-openai.ts +1 -3
  253. package/src/memory/indexer.ts +114 -21
  254. package/src/memory/items-extractor.ts +42 -13
  255. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  256. package/src/memory/job-handlers/embedding.test.ts +2 -4
  257. package/src/memory/job-handlers/embedding.ts +83 -0
  258. package/src/memory/job-utils.ts +1 -1
  259. package/src/memory/jobs-store.ts +6 -0
  260. package/src/memory/jobs-worker.ts +12 -0
  261. package/src/memory/llm-request-log-store.ts +100 -1
  262. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  263. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  264. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  265. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  266. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  267. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  268. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  269. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  270. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  271. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  272. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  273. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  274. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  275. package/src/memory/migrations/185-memory-brief-state.ts +52 -0
  276. package/src/memory/migrations/186-memory-archive.ts +109 -0
  277. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
  278. package/src/memory/migrations/index.ts +10 -0
  279. package/src/memory/migrations/registry.ts +13 -0
  280. package/src/memory/qdrant-client.ts +23 -4
  281. package/src/memory/reducer-store.ts +271 -0
  282. package/src/memory/reducer-types.ts +99 -0
  283. package/src/memory/reducer.ts +453 -0
  284. package/src/memory/retriever.test.ts +601 -2
  285. package/src/memory/retriever.ts +85 -9
  286. package/src/memory/schema/conversations.ts +9 -0
  287. package/src/memory/schema/index.ts +2 -0
  288. package/src/memory/schema/infrastructure.ts +13 -7
  289. package/src/memory/schema/memory-archive.ts +121 -0
  290. package/src/memory/schema/memory-brief.ts +55 -0
  291. package/src/memory/schema/oauth.ts +6 -0
  292. package/src/memory/search/semantic.ts +17 -4
  293. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  294. package/src/notifications/copy-composer.ts +26 -0
  295. package/src/notifications/decision-engine.ts +14 -1
  296. package/src/notifications/emit-signal.ts +1 -1
  297. package/src/notifications/signal.ts +36 -0
  298. package/src/oauth/byo-connection.test.ts +1 -45
  299. package/src/oauth/byo-connection.ts +2 -8
  300. package/src/oauth/connect-orchestrator.ts +15 -11
  301. package/src/oauth/connection-resolver.test.ts +191 -0
  302. package/src/oauth/connection-resolver.ts +66 -38
  303. package/src/oauth/connection.ts +0 -1
  304. package/src/oauth/oauth-store.ts +99 -47
  305. package/src/oauth/platform-connection.test.ts +0 -1
  306. package/src/oauth/platform-connection.ts +11 -3
  307. package/src/oauth/seed-providers.ts +78 -3
  308. package/src/oauth/token-persistence.ts +16 -10
  309. package/src/permissions/checker.ts +160 -14
  310. package/src/permissions/defaults.ts +14 -0
  311. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  312. package/src/providers/anthropic/client.ts +8 -1
  313. package/src/providers/failover.ts +4 -1
  314. package/src/providers/gemini/client.ts +50 -0
  315. package/src/providers/model-catalog.ts +92 -0
  316. package/src/providers/model-intents.ts +29 -20
  317. package/src/providers/openai/client.ts +49 -0
  318. package/src/providers/types.ts +2 -0
  319. package/src/runtime/access-request-helper.ts +16 -7
  320. package/src/runtime/auth/credential-service.ts +3 -1
  321. package/src/runtime/auth/route-policy.ts +14 -1
  322. package/src/runtime/btw-sidechain.ts +101 -0
  323. package/src/runtime/channel-reply-delivery.ts +17 -1
  324. package/src/runtime/http-router.ts +3 -1
  325. package/src/runtime/http-server.ts +196 -141
  326. package/src/runtime/http-types.ts +1 -0
  327. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  328. package/src/runtime/routes/access-request-decision.ts +41 -0
  329. package/src/runtime/routes/app-management-routes.ts +6 -3
  330. package/src/runtime/routes/app-routes.ts +7 -3
  331. package/src/runtime/routes/approval-routes.ts +1 -0
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  333. package/src/runtime/routes/attachment-routes.ts +45 -15
  334. package/src/runtime/routes/btw-routes.ts +21 -61
  335. package/src/runtime/routes/conversation-management-routes.ts +74 -0
  336. package/src/runtime/routes/conversation-query-routes.ts +187 -10
  337. package/src/runtime/routes/conversation-routes.ts +269 -28
  338. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  339. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  340. package/src/runtime/routes/identity-routes.ts +2 -35
  341. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  342. package/src/runtime/routes/llm-context-normalization.ts +1212 -0
  343. package/src/runtime/routes/log-export-routes.ts +3 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  345. package/src/runtime/routes/memory-item-routes.ts +94 -5
  346. package/src/runtime/routes/migration-routes.ts +4 -1
  347. package/src/runtime/routes/oauth-apps.ts +291 -0
  348. package/src/runtime/routes/secret-routes.ts +30 -1
  349. package/src/runtime/routes/settings-routes.ts +14 -0
  350. package/src/runtime/routes/surface-action-routes.ts +68 -1
  351. package/src/runtime/routes/trace-event-routes.ts +4 -1
  352. package/src/schedule/schedule-store.ts +30 -21
  353. package/src/security/secure-keys.ts +21 -0
  354. package/src/signals/bash.ts +1 -1
  355. package/src/skills/inline-command-expansions.ts +204 -0
  356. package/src/skills/inline-command-render.ts +127 -0
  357. package/src/skills/inline-command-runner.ts +242 -0
  358. package/src/skills/transitive-version-hash.ts +88 -0
  359. package/src/swarm/backend-claude-code.ts +3 -6
  360. package/src/tasks/task-store.ts +43 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  362. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  363. package/src/tools/AGENTS.md +6 -10
  364. package/src/tools/apps/executors.ts +17 -232
  365. package/src/tools/claude-code/claude-code.ts +2 -3
  366. package/src/tools/credentials/vault.ts +7 -12
  367. package/src/tools/host-filesystem/read.ts +13 -10
  368. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  369. package/src/tools/permission-checker.ts +8 -1
  370. package/src/tools/schedule/list.ts +2 -7
  371. package/src/tools/schema-transforms.ts +5 -0
  372. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  373. package/src/tools/skills/execute.ts +1 -1
  374. package/src/tools/skills/load.ts +140 -6
  375. package/src/tools/tool-manifest.ts +0 -6
  376. package/src/tools/ui-surface/definitions.ts +2 -2
  377. package/src/util/device-id.ts +28 -5
  378. package/src/util/platform.ts +24 -0
  379. package/src/util/pricing.ts +1 -0
  380. package/src/util/retry.ts +1 -3
  381. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  382. package/src/workspace/migrations/006-services-config.ts +5 -0
  383. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  384. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  385. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  386. package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
  387. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  388. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  389. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  390. package/src/workspace/migrations/registry.ts +11 -1
  391. package/src/workspace/top-level-renderer.ts +12 -0
  392. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  393. package/src/__tests__/asset-search-tool.test.ts +0 -536
  394. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  395. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  396. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  397. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  398. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  399. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  400. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  401. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  402. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  403. package/src/daemon/media-visibility-policy.ts +0 -59
  404. package/src/tools/assets/materialize.ts +0 -248
  405. package/src/tools/assets/search.ts +0 -400
@@ -45,7 +45,9 @@ export type OAuthConnectionRow = typeof oauthConnections.$inferSelect;
45
45
  * Seed well-known provider profiles into the database. Uses INSERT … ON
46
46
  * CONFLICT DO UPDATE so that implementation fields (authUrl, tokenUrl,
47
47
  * tokenEndpointAuthMethod, userinfoUrl, extraParams, callbackTransport,
48
- * pingUrl) propagate to existing installations on every startup, while
48
+ * pingUrl, managedServiceConfigKey) and display metadata (displayName,
49
+ * description, dashboardUrl, clientIdPlaceholder, requiresClientSecret)
50
+ * propagate to existing installations on every startup, while
49
51
  * user-customizable fields (defaultScopes, scopePolicy, baseUrl) are
50
52
  * only written on the initial insert.
51
53
  */
@@ -62,6 +64,12 @@ export function seedProviders(
62
64
  scopePolicy: Record<string, unknown>;
63
65
  extraParams?: Record<string, string>;
64
66
  callbackTransport?: string;
67
+ managedServiceConfigKey?: string;
68
+ displayName?: string;
69
+ description?: string;
70
+ dashboardUrl?: string | null;
71
+ clientIdPlaceholder?: string | null;
72
+ requiresClientSecret?: boolean;
65
73
  }>,
66
74
  ): void {
67
75
  const db = getDb();
@@ -77,6 +85,12 @@ export function seedProviders(
77
85
  const scopePolicy = JSON.stringify(p.scopePolicy);
78
86
  const extraParams = p.extraParams ? JSON.stringify(p.extraParams) : null;
79
87
  const callbackTransport = p.callbackTransport ?? null;
88
+ const managedServiceConfigKey = p.managedServiceConfigKey ?? null;
89
+ const displayName = p.displayName ?? null;
90
+ const description = p.description ?? null;
91
+ const dashboardUrl = p.dashboardUrl ?? null;
92
+ const clientIdPlaceholder = p.clientIdPlaceholder ?? null;
93
+ const requiresClientSecret = p.requiresClientSecret !== false ? 1 : 0;
80
94
 
81
95
  db.insert(oauthProviders)
82
96
  .values({
@@ -91,6 +105,12 @@ export function seedProviders(
91
105
  extraParams,
92
106
  callbackTransport,
93
107
  pingUrl,
108
+ managedServiceConfigKey,
109
+ displayName,
110
+ description,
111
+ dashboardUrl,
112
+ clientIdPlaceholder,
113
+ requiresClientSecret,
94
114
  createdAt: now,
95
115
  updatedAt: now,
96
116
  })
@@ -104,6 +124,12 @@ export function seedProviders(
104
124
  extraParams,
105
125
  callbackTransport,
106
126
  pingUrl,
127
+ managedServiceConfigKey,
128
+ displayName,
129
+ description,
130
+ dashboardUrl,
131
+ clientIdPlaceholder,
132
+ requiresClientSecret,
107
133
  updatedAt: now,
108
134
  },
109
135
  })
@@ -143,6 +169,12 @@ export function registerProvider(params: {
143
169
  scopePolicy: Record<string, unknown>;
144
170
  extraParams?: Record<string, string>;
145
171
  callbackTransport?: string;
172
+ managedServiceConfigKey?: string;
173
+ displayName?: string;
174
+ description?: string;
175
+ dashboardUrl?: string;
176
+ clientIdPlaceholder?: string;
177
+ requiresClientSecret?: number;
146
178
  }): OAuthProviderRow {
147
179
  const db = getDb();
148
180
  const now = Date.now();
@@ -164,6 +196,12 @@ export function registerProvider(params: {
164
196
  extraParams: params.extraParams ? JSON.stringify(params.extraParams) : null,
165
197
  callbackTransport: params.callbackTransport ?? null,
166
198
  pingUrl: params.pingUrl ?? null,
199
+ managedServiceConfigKey: params.managedServiceConfigKey ?? null,
200
+ displayName: params.displayName ?? null,
201
+ description: params.description ?? null,
202
+ dashboardUrl: params.dashboardUrl ?? null,
203
+ clientIdPlaceholder: params.clientIdPlaceholder ?? null,
204
+ requiresClientSecret: params.requiresClientSecret ?? 1,
167
205
  createdAt: now,
168
206
  updatedAt: now,
169
207
  };
@@ -232,6 +270,16 @@ export async function upsertApp(
232
270
  if (!stored) {
233
271
  throw new Error("Failed to store client_secret in secure storage");
234
272
  }
273
+ // Bump updatedAt so the rollback guard in the new-row insertion path
274
+ // can detect that a concurrent caller has claimed this row. Without
275
+ // this, a concurrent inserter's rollback DELETE would still match on
276
+ // the original updatedAt and delete the row we just validated.
277
+ const newUpdatedAt = Date.now();
278
+ db.update(oauthApps)
279
+ .set({ updatedAt: newUpdatedAt })
280
+ .where(eq(oauthApps.id, existingRow.id))
281
+ .run();
282
+ return { ...existingRow, updatedAt: newUpdatedAt };
235
283
  }
236
284
  if (clientSecretCredentialPath) {
237
285
  db.update(oauthApps)
@@ -272,7 +320,14 @@ export async function upsertApp(
272
320
  if (!stored) {
273
321
  // Roll back the just-inserted row to avoid an orphaned app pointing
274
322
  // at a non-existent client_secret in secure storage.
275
- db.delete(oauthApps).where(eq(oauthApps.id, id)).run();
323
+ //
324
+ // Guard: only delete if updatedAt still matches our insertion timestamp.
325
+ // A concurrent upsertApp call may have observed this row, successfully
326
+ // stored the secret, and updated the row — deleting it would orphan that
327
+ // caller's valid reference.
328
+ db.delete(oauthApps)
329
+ .where(and(eq(oauthApps.id, id), eq(oauthApps.updatedAt, now)))
330
+ .run();
276
331
  throw new Error("Failed to store client_secret in secure storage");
277
332
  }
278
333
  }
@@ -286,6 +341,15 @@ export function getApp(id: string): OAuthAppRow | undefined {
286
341
  return db.select().from(oauthApps).where(eq(oauthApps.id, id)).get();
287
342
  }
288
343
 
344
+ /** Read an app client_secret from secure storage. */
345
+ export async function getAppClientSecret(
346
+ appOrId: OAuthAppRow | string,
347
+ ): Promise<string | undefined> {
348
+ const app = typeof appOrId === "string" ? getApp(appOrId) : appOrId;
349
+ if (!app) return undefined;
350
+ return getSecureKeyAsync(app.clientSecretCredentialPath);
351
+ }
352
+
289
353
  /** Look up an app by (provider_key, client_id). */
290
354
  export function getAppByProviderAndClientId(
291
355
  providerKey: string,
@@ -407,71 +471,59 @@ export function getConnection(id: string): OAuthConnectionRow | undefined {
407
471
 
408
472
  /**
409
473
  * Get the most recent active connection for a provider.
410
- * When `clientId` is provided, only connections linked to the matching app are considered.
411
- * Returns undefined if no active connection exists.
474
+ *
475
+ * Optional filters narrow the result:
476
+ * - `account` — match a specific account identifier (e.g. email).
477
+ * - `clientId` — restrict to connections linked to a specific OAuth app.
478
+ *
479
+ * Returns `undefined` when no matching active connection exists.
412
480
  */
413
- export function getConnectionByProvider(
481
+ export function getActiveConnection(
414
482
  providerKey: string,
415
- clientId?: string,
483
+ options?: { clientId?: string; account?: string },
416
484
  ): OAuthConnectionRow | undefined {
485
+ const { clientId, account } = options ?? {};
417
486
  const db = getDb();
418
487
 
488
+ const conditions = [
489
+ eq(oauthConnections.providerKey, providerKey),
490
+ eq(oauthConnections.status, "active"),
491
+ ];
492
+
493
+ if (account) {
494
+ conditions.push(eq(oauthConnections.accountInfo, account));
495
+ }
496
+
419
497
  if (clientId) {
420
498
  const app = getAppByProviderAndClientId(providerKey, clientId);
421
499
  if (!app) return undefined;
422
- return db
423
- .select()
424
- .from(oauthConnections)
425
- .where(
426
- and(
427
- eq(oauthConnections.providerKey, providerKey),
428
- eq(oauthConnections.oauthAppId, app.id),
429
- eq(oauthConnections.status, "active"),
430
- ),
431
- )
432
- .orderBy(desc(oauthConnections.createdAt), sql`rowid DESC`)
433
- .limit(1)
434
- .get();
500
+ conditions.push(eq(oauthConnections.oauthAppId, app.id));
435
501
  }
436
502
 
437
503
  return db
438
504
  .select()
439
505
  .from(oauthConnections)
440
- .where(
441
- and(
442
- eq(oauthConnections.providerKey, providerKey),
443
- eq(oauthConnections.status, "active"),
444
- ),
445
- )
506
+ .where(and(...conditions))
446
507
  .orderBy(desc(oauthConnections.createdAt), sql`rowid DESC`)
447
508
  .limit(1)
448
509
  .get();
449
510
  }
450
511
 
451
- /**
452
- * Get the active connection for a provider matching a specific account.
453
- * Falls back to getConnectionByProvider when accountInfo is undefined.
454
- */
512
+ /** @deprecated Use {@link getActiveConnection} instead. */
513
+ export function getConnectionByProvider(
514
+ providerKey: string,
515
+ clientId?: string,
516
+ ): OAuthConnectionRow | undefined {
517
+ return getActiveConnection(providerKey, { clientId });
518
+ }
519
+
520
+ /** @deprecated Use {@link getActiveConnection} instead. */
455
521
  export function getConnectionByProviderAndAccount(
456
522
  providerKey: string,
457
523
  accountInfo?: string,
524
+ clientId?: string,
458
525
  ): OAuthConnectionRow | undefined {
459
- if (!accountInfo) return getConnectionByProvider(providerKey);
460
-
461
- const db = getDb();
462
- return db
463
- .select()
464
- .from(oauthConnections)
465
- .where(
466
- and(
467
- eq(oauthConnections.providerKey, providerKey),
468
- eq(oauthConnections.accountInfo, accountInfo),
469
- eq(oauthConnections.status, "active"),
470
- ),
471
- )
472
- .orderBy(desc(oauthConnections.createdAt), sql`rowid DESC`)
473
- .limit(1)
474
- .get();
526
+ return getActiveConnection(providerKey, { clientId, account: accountInfo });
475
527
  }
476
528
 
477
529
  /**
@@ -505,7 +557,7 @@ export function listActiveConnectionsByProvider(
505
557
  export async function isProviderConnected(
506
558
  providerKey: string,
507
559
  ): Promise<boolean> {
508
- const conn = getConnectionByProvider(providerKey);
560
+ const conn = getActiveConnection(providerKey);
509
561
  if (!conn || conn.status !== "active") return false;
510
562
  return (
511
563
  (await getSecureKeyAsync(oauthConnectionAccessTokenPath(conn.id))) !==
@@ -623,7 +675,7 @@ export async function disconnectOAuthProvider(
623
675
  ): Promise<"disconnected" | "not-found" | "error"> {
624
676
  const conn = connectionId
625
677
  ? getConnection(connectionId)
626
- : getConnectionByProvider(providerKey, clientId);
678
+ : getActiveConnection(providerKey, { clientId });
627
679
  if (!conn) return "not-found";
628
680
 
629
681
  // Wrap the assistant's secure-key functions into the SecureKeyBackend
@@ -12,7 +12,6 @@ const DEFAULT_OPTIONS = {
12
12
  providerKey: "integration:google",
13
13
  externalId: "ext-123",
14
14
  accountInfo: "user@example.com",
15
- grantedScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
16
15
  assistantId: "asst-abc",
17
16
  platformBaseUrl: "https://platform.example.com",
18
17
  apiKey: "test-api-key",
@@ -24,7 +24,6 @@ export interface PlatformOAuthConnectionOptions {
24
24
  providerKey: string;
25
25
  externalId: string;
26
26
  accountInfo: string | null;
27
- grantedScopes: string[];
28
27
  assistantId: string;
29
28
  platformBaseUrl: string;
30
29
  apiKey: string;
@@ -35,18 +34,27 @@ export class PlatformOAuthConnection implements OAuthConnection {
35
34
  readonly providerKey: string;
36
35
  readonly externalId: string;
37
36
  readonly accountInfo: string | null;
38
- readonly grantedScopes: string[];
39
37
 
40
38
  private readonly assistantId: string;
41
39
  private readonly platformBaseUrl: string;
42
40
  private readonly apiKey: string;
43
41
 
44
42
  constructor(options: PlatformOAuthConnectionOptions) {
43
+ const missing: string[] = [];
44
+ if (!options.platformBaseUrl) missing.push("platform base URL");
45
+ if (!options.apiKey) missing.push("assistant API key");
46
+ if (!options.assistantId) missing.push("assistant ID");
47
+ if (missing.length > 0) {
48
+ throw new BackendError(
49
+ `Platform-managed connection for "${options.providerKey}" cannot be created: missing ${missing.join(", ")}. ` +
50
+ `Log in to the Vellum platform or switch to using your own OAuth app.`,
51
+ );
52
+ }
53
+
45
54
  this.id = options.id;
46
55
  this.providerKey = options.providerKey;
47
56
  this.externalId = options.externalId;
48
57
  this.accountInfo = options.accountInfo;
49
- this.grantedScopes = options.grantedScopes;
50
58
  this.assistantId = options.assistantId;
51
59
  this.platformBaseUrl = options.platformBaseUrl.replace(/\/+$/, "");
52
60
  this.apiKey = options.apiKey;
@@ -5,9 +5,11 @@ import { seedProviders } from "./oauth-store.js";
5
5
  *
6
6
  * These values are upserted into the `oauth_providers` SQLite table on
7
7
  * every startup. Only Vellum implementation fields (authUrl, tokenUrl,
8
- * tokenEndpointAuthMethod, extraParams, callbackTransport, pingUrl) are
9
- * overwritten on subsequent startups user-customizable
10
- * fields (defaultScopes, scopePolicy, userinfoUrl, baseUrl) are only
8
+ * tokenEndpointAuthMethod, userinfoUrl, extraParams, callbackTransport,
9
+ * pingUrl, managedServiceConfigKey) and display metadata (displayName,
10
+ * description, dashboardUrl, clientIdPlaceholder, requiresClientSecret)
11
+ * are overwritten on subsequent startups — user-customizable
12
+ * fields (defaultScopes, scopePolicy, baseUrl) are only
11
13
  * written on initial insert and preserved across restarts.
12
14
  *
13
15
  * Code-side behavioral fields (identityVerifier, injectionTemplates,
@@ -32,6 +34,12 @@ const PROVIDER_SEED_DATA: Record<
32
34
  };
33
35
  extraParams?: Record<string, string>;
34
36
  callbackTransport?: string;
37
+ managedServiceConfigKey?: string;
38
+ displayName: string;
39
+ description: string;
40
+ dashboardUrl: string | null;
41
+ clientIdPlaceholder: string | null;
42
+ requiresClientSecret?: boolean;
35
43
  }
36
44
  > = {
37
45
  "integration:google": {
@@ -41,6 +49,10 @@ const PROVIDER_SEED_DATA: Record<
41
49
  userinfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
42
50
  pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
43
51
  baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
52
+ displayName: "Google",
53
+ description: "Gmail, Calendar, and Contacts",
54
+ dashboardUrl: "https://console.cloud.google.com/apis/credentials",
55
+ clientIdPlaceholder: "123456789.apps.googleusercontent.com",
44
56
  defaultScopes: [
45
57
  "https://www.googleapis.com/auth/gmail.readonly",
46
58
  "https://www.googleapis.com/auth/gmail.modify",
@@ -60,6 +72,7 @@ const PROVIDER_SEED_DATA: Record<
60
72
  },
61
73
  extraParams: { access_type: "offline", prompt: "consent" },
62
74
  callbackTransport: "loopback",
75
+ managedServiceConfigKey: "google-oauth",
63
76
  },
64
77
 
65
78
  "integration:slack": {
@@ -68,6 +81,10 @@ const PROVIDER_SEED_DATA: Record<
68
81
  tokenUrl: "https://slack.com/api/oauth.v2.access",
69
82
  pingUrl: "https://slack.com/api/auth.test",
70
83
  baseUrl: "https://slack.com/api",
84
+ displayName: "Slack",
85
+ description: "Workspace messaging",
86
+ dashboardUrl: "https://api.slack.com/apps",
87
+ clientIdPlaceholder: null,
71
88
  defaultScopes: [
72
89
  "channels:read",
73
90
  "channels:history",
@@ -101,6 +118,10 @@ const PROVIDER_SEED_DATA: Record<
101
118
  tokenUrl: "https://api.notion.com/v1/oauth/token",
102
119
  pingUrl: "https://api.notion.com/v1/users/me",
103
120
  baseUrl: "https://api.notion.com",
121
+ displayName: "Notion",
122
+ description: "Pages and databases",
123
+ dashboardUrl: "https://www.notion.so/my-integrations",
124
+ clientIdPlaceholder: null,
104
125
  defaultScopes: [],
105
126
  scopePolicy: {
106
127
  allowAdditionalScopes: false,
@@ -118,6 +139,10 @@ const PROVIDER_SEED_DATA: Record<
118
139
  tokenUrl: "https://api.x.com/2/oauth2/token",
119
140
  pingUrl: "https://api.x.com/2/users/me",
120
141
  baseUrl: "https://api.x.com",
142
+ displayName: "Twitter",
143
+ description: "Posts and direct messages",
144
+ dashboardUrl: "https://developer.twitter.com/en/portal/dashboard",
145
+ clientIdPlaceholder: null,
121
146
  defaultScopes: [
122
147
  "tweet.read",
123
148
  "tweet.write",
@@ -139,6 +164,10 @@ const PROVIDER_SEED_DATA: Record<
139
164
  tokenUrl: "https://github.com/login/oauth/access_token",
140
165
  pingUrl: "https://api.github.com/user",
141
166
  baseUrl: "https://api.github.com",
167
+ displayName: "GitHub",
168
+ description: "Repositories and issues",
169
+ dashboardUrl: "https://github.com/settings/developers",
170
+ clientIdPlaceholder: null,
142
171
  defaultScopes: ["repo", "read:user", "notifications"],
143
172
  scopePolicy: {
144
173
  allowAdditionalScopes: true,
@@ -159,6 +188,10 @@ const PROVIDER_SEED_DATA: Record<
159
188
  tokenUrl: "https://api.linear.app/oauth/token",
160
189
  pingUrl: "https://api.linear.app/graphql",
161
190
  baseUrl: "https://api.linear.app",
191
+ displayName: "Linear",
192
+ description: "Issues and projects",
193
+ dashboardUrl: "https://linear.app/settings/api",
194
+ clientIdPlaceholder: null,
162
195
  defaultScopes: ["read", "write", "issues:create"],
163
196
  scopePolicy: {
164
197
  allowAdditionalScopes: false,
@@ -175,6 +208,10 @@ const PROVIDER_SEED_DATA: Record<
175
208
  tokenUrl: "https://accounts.spotify.com/api/token",
176
209
  pingUrl: "https://api.spotify.com/v1/me",
177
210
  baseUrl: "https://api.spotify.com/v1",
211
+ displayName: "Spotify",
212
+ description: "Music and playlists",
213
+ dashboardUrl: "https://developer.spotify.com/dashboard",
214
+ clientIdPlaceholder: null,
178
215
  defaultScopes: [
179
216
  "user-read-playback-state",
180
217
  "user-modify-playback-state",
@@ -201,6 +238,10 @@ const PROVIDER_SEED_DATA: Record<
201
238
  tokenUrl: "https://todoist.com/oauth/access_token",
202
239
  pingUrl: "https://api.todoist.com/rest/v2/projects",
203
240
  baseUrl: "https://api.todoist.com/rest/v2",
241
+ displayName: "Todoist",
242
+ description: "Tasks and projects",
243
+ dashboardUrl: "https://developer.todoist.com/appconsole.html",
244
+ clientIdPlaceholder: null,
204
245
  defaultScopes: ["data:read_write"],
205
246
  scopePolicy: {
206
247
  allowAdditionalScopes: false,
@@ -216,6 +257,10 @@ const PROVIDER_SEED_DATA: Record<
216
257
  tokenUrl: "https://discord.com/api/v10/oauth2/token",
217
258
  pingUrl: "https://discord.com/api/v10/users/@me",
218
259
  baseUrl: "https://discord.com/api/v10",
260
+ displayName: "Discord",
261
+ description: "Servers and messages",
262
+ dashboardUrl: "https://discord.com/developers/applications",
263
+ clientIdPlaceholder: null,
219
264
  defaultScopes: [
220
265
  "identify",
221
266
  "guilds",
@@ -236,6 +281,10 @@ const PROVIDER_SEED_DATA: Record<
236
281
  tokenUrl: "https://api.dropboxapi.com/oauth2/token",
237
282
  pingUrl: "https://api.dropboxapi.com/2/users/get_current_account",
238
283
  baseUrl: "https://api.dropboxapi.com/2",
284
+ displayName: "Dropbox",
285
+ description: "Files and folders",
286
+ dashboardUrl: "https://www.dropbox.com/developers/apps",
287
+ clientIdPlaceholder: null,
239
288
  defaultScopes: [
240
289
  "files.metadata.read",
241
290
  "files.content.read",
@@ -257,6 +306,10 @@ const PROVIDER_SEED_DATA: Record<
257
306
  tokenUrl: "https://app.asana.com/-/oauth_token",
258
307
  pingUrl: "https://app.asana.com/api/1.0/users/me",
259
308
  baseUrl: "https://app.asana.com/api/1.0",
309
+ displayName: "Asana",
310
+ description: "Tasks and projects",
311
+ dashboardUrl: "https://app.asana.com/0/my-apps",
312
+ clientIdPlaceholder: null,
260
313
  defaultScopes: ["default"],
261
314
  scopePolicy: {
262
315
  allowAdditionalScopes: false,
@@ -272,6 +325,10 @@ const PROVIDER_SEED_DATA: Record<
272
325
  tokenUrl: "https://airtable.com/oauth2/v1/token",
273
326
  pingUrl: "https://api.airtable.com/v0/meta/whoami",
274
327
  baseUrl: "https://api.airtable.com/v0",
328
+ displayName: "Airtable",
329
+ description: "Bases and records",
330
+ dashboardUrl: "https://airtable.com/create/tokens",
331
+ clientIdPlaceholder: null,
275
332
  defaultScopes: [
276
333
  "data.records:read",
277
334
  "data.records:write",
@@ -292,6 +349,10 @@ const PROVIDER_SEED_DATA: Record<
292
349
  tokenUrl: "https://api.hubapi.com/oauth/v1/token",
293
350
  pingUrl: "https://api.hubapi.com/crm/v3/objects/contacts?limit=1",
294
351
  baseUrl: "https://api.hubapi.com",
352
+ displayName: "HubSpot",
353
+ description: "CRM contacts and deals",
354
+ dashboardUrl: "https://developers.hubspot.com/",
355
+ clientIdPlaceholder: null,
295
356
  defaultScopes: [
296
357
  "crm.objects.contacts.read",
297
358
  "crm.objects.contacts.write",
@@ -316,6 +377,10 @@ const PROVIDER_SEED_DATA: Record<
316
377
  tokenUrl: "https://api.figma.com/v1/oauth/token",
317
378
  pingUrl: "https://api.figma.com/v1/me",
318
379
  baseUrl: "https://api.figma.com/v1",
380
+ displayName: "Figma",
381
+ description: "Design files and comments",
382
+ dashboardUrl: "https://www.figma.com/developers/apps",
383
+ clientIdPlaceholder: null,
319
384
  defaultScopes: ["files:read", "file_comments:write"],
320
385
  scopePolicy: {
321
386
  allowAdditionalScopes: false,
@@ -335,6 +400,11 @@ const PROVIDER_SEED_DATA: Record<
335
400
  tokenUrl: "urn:manual-token",
336
401
  pingUrl: "https://slack.com/api/auth.test",
337
402
  baseUrl: "https://slack.com/api",
403
+ displayName: "Slack Channel",
404
+ description: "Channel bot token",
405
+ dashboardUrl: null,
406
+ clientIdPlaceholder: null,
407
+ requiresClientSecret: false,
338
408
  defaultScopes: [],
339
409
  scopePolicy: {
340
410
  allowAdditionalScopes: false,
@@ -348,6 +418,11 @@ const PROVIDER_SEED_DATA: Record<
348
418
  authUrl: "urn:manual-token",
349
419
  tokenUrl: "urn:manual-token",
350
420
  baseUrl: "https://api.telegram.org",
421
+ displayName: "Telegram",
422
+ description: "Bot messaging",
423
+ dashboardUrl: null,
424
+ clientIdPlaceholder: null,
425
+ requiresClientSecret: false,
351
426
  defaultScopes: [],
352
427
  scopePolicy: {
353
428
  allowAdditionalScopes: false,
@@ -28,9 +28,8 @@ import {
28
28
  import { runPostConnectHook } from "../tools/credentials/post-connect-hooks.js";
29
29
  import {
30
30
  createConnection,
31
+ getActiveConnection,
31
32
  getApp,
32
- getConnectionByProvider,
33
- getConnectionByProviderAndAccount,
34
33
  listActiveConnectionsByProvider,
35
34
  updateConnection,
36
35
  upsertApp,
@@ -59,8 +58,12 @@ export interface StoreOAuth2TokensParams {
59
58
  clientId: string;
60
59
  clientSecret?: string;
61
60
  userinfoUrl?: string;
62
- /** Fallback account info from an identity verifier (e.g. @username, email). */
63
- identityAccountInfo?: string;
61
+ /**
62
+ * Best-effort account identifier parsed from the provider's identity
63
+ * endpoint (e.g. email, @username, display name). The format varies by
64
+ * provider and may be undefined if the API call fails.
65
+ */
66
+ parsedAccountIdentifier?: string;
64
67
  /** Pre-resolved oauth_app ID — skips the upsertApp() call if provided. */
65
68
  oauthAppId?: string;
66
69
  }
@@ -94,6 +97,10 @@ export async function storeOAuth2Tokens(
94
97
  ? Date.now() + tokens.expiresIn * 1000
95
98
  : null;
96
99
 
100
+ // Account identifier parsing is best-effort. The format varies by provider
101
+ // (email for Google, username for GitHub, display name for Spotify, etc.)
102
+ // and may be undefined if the userinfo/identity API call fails or the
103
+ // required scope wasn't granted.
97
104
  let accountInfo: string | undefined;
98
105
  if (userinfoUrl && grantedScopes.some((s) => s.includes("userinfo"))) {
99
106
  try {
@@ -109,7 +116,7 @@ export async function storeOAuth2Tokens(
109
116
  }
110
117
  }
111
118
 
112
- const resolvedAccountInfo = accountInfo ?? params.identityAccountInfo;
119
+ const resolvedAccountInfo = accountInfo ?? params.parsedAccountIdentifier;
113
120
 
114
121
  // -------------------------------------------------------------------
115
122
  // SQLite oauth_app + oauth_connection + new-format secure keys
@@ -144,12 +151,11 @@ export async function storeOAuth2Tokens(
144
151
  // lookup so that re-auth without userinfo still updates the right row.
145
152
  // However, treat provider-only matches as ambiguous when multiple active
146
153
  // connections exist to avoid overwriting the wrong account's tokens.
147
- let existingConn: ReturnType<typeof getConnectionByProvider>;
154
+ let existingConn: ReturnType<typeof getActiveConnection>;
148
155
  if (resolvedAccountInfo) {
149
- existingConn = getConnectionByProviderAndAccount(
150
- service,
151
- resolvedAccountInfo,
152
- );
156
+ existingConn = getActiveConnection(service, {
157
+ account: resolvedAccountInfo,
158
+ });
153
159
  } else {
154
160
  const activeConns = listActiveConnectionsByProvider(service);
155
161
  // Only reuse the provider-only match when it's unambiguous (single connection).