@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
@@ -444,6 +444,7 @@ const WORKSPACE_SKIP_DIRS = new Set([
444
444
  "embedding-models",
445
445
  "data/qdrant",
446
446
  "data/attachments",
447
+ "conversations",
447
448
  ]);
448
449
 
449
450
  /** Files at the workspace root to skip (already covered by sanitized fields). */
@@ -513,6 +514,8 @@ function collectWorkspaceFiles(): Record<string, string> {
513
514
 
514
515
  // SQLite DB handling: dump as SQL text, then enforce size cap
515
516
  if (entry.endsWith(".db")) {
517
+ // Skip the dump entirely if the budget is already exhausted
518
+ if (totalBytes >= MAX_WORKSPACE_PAYLOAD_BYTES) continue;
516
519
  try {
517
520
  const proc = spawnSync("sqlite3", [fullPath, ".dump"], {
518
521
  timeout: 10_000,
@@ -347,6 +347,40 @@ describe("Memory Item Routes", () => {
347
347
  expect(body.items[1].id).toBe("i1");
348
348
  });
349
349
 
350
+ test("supports sort by accessCount descending", async () => {
351
+ insertItem({
352
+ id: "i1",
353
+ kind: "preference",
354
+ subject: "s1",
355
+ statement: "st1",
356
+ });
357
+ insertItem({
358
+ id: "i2",
359
+ kind: "preference",
360
+ subject: "s2",
361
+ statement: "st2",
362
+ });
363
+
364
+ getDb()
365
+ .update(memoryItems)
366
+ .set({ accessCount: 2 })
367
+ .where(eq(memoryItems.id, "i1"))
368
+ .run();
369
+ getDb()
370
+ .update(memoryItems)
371
+ .set({ accessCount: 7 })
372
+ .where(eq(memoryItems.id, "i2"))
373
+ .run();
374
+
375
+ const ctx = makeCtx({ sort: "accessCount", order: "desc" });
376
+ const res = await handler(ctx);
377
+ const body = (await res.json()) as {
378
+ items: Array<{ id: string }>;
379
+ };
380
+ expect(body.items[0].id).toBe("i2");
381
+ expect(body.items[1].id).toBe("i1");
382
+ });
383
+
350
384
  test("rejects invalid kind filter", async () => {
351
385
  const ctx = makeCtx({ kind: "bogus" });
352
386
  const res = await handler(ctx);
@@ -8,13 +8,17 @@
8
8
  * DELETE /v1/memory-items/:id — delete a memory item and its embeddings
9
9
  */
10
10
 
11
- import { and, asc, count, desc, eq, like, ne, or } from "drizzle-orm";
11
+ import { and, asc, count, desc, eq, inArray, like, ne, or } from "drizzle-orm";
12
12
  import { v4 as uuid } from "uuid";
13
13
 
14
14
  import { getDb } from "../../memory/db.js";
15
15
  import { computeMemoryFingerprint } from "../../memory/fingerprint.js";
16
16
  import { enqueueMemoryJob } from "../../memory/jobs-store.js";
17
- import { memoryEmbeddings, memoryItems } from "../../memory/schema.js";
17
+ import {
18
+ conversations,
19
+ memoryEmbeddings,
20
+ memoryItems,
21
+ } from "../../memory/schema.js";
18
22
  import { truncate } from "../../util/truncate.js";
19
23
  import { httpError } from "../http-errors.js";
20
24
  import type { RouteContext, RouteDefinition } from "../http-router.js";
@@ -37,6 +41,7 @@ type MemoryItemKind = (typeof VALID_KINDS)[number];
37
41
  const VALID_SORT_FIELDS = [
38
42
  "lastSeenAt",
39
43
  "importance",
44
+ "accessCount",
40
45
  "kind",
41
46
  "firstSeenAt",
42
47
  ] as const;
@@ -46,6 +51,7 @@ type SortField = (typeof VALID_SORT_FIELDS)[number];
46
51
  const SORT_COLUMN_MAP = {
47
52
  lastSeenAt: memoryItems.lastSeenAt,
48
53
  importance: memoryItems.importance,
54
+ accessCount: memoryItems.accessCount,
49
55
  kind: memoryItems.kind,
50
56
  firstSeenAt: memoryItems.firstSeenAt,
51
57
  } as const;
@@ -62,6 +68,54 @@ function isValidSortField(value: string): value is SortField {
62
68
  return (VALID_SORT_FIELDS as readonly string[]).includes(value);
63
69
  }
64
70
 
71
+ /**
72
+ * Resolve a `scopeLabel` for a memory item based on its `scopeId`.
73
+ *
74
+ * - `"default"` → `null`
75
+ * - `"private:<conversationId>"` → `"Private · <title>"` when the conversation
76
+ * has a title, or `"Private"` when it doesn't (or the conversation was deleted).
77
+ */
78
+ function resolveScopeLabel(
79
+ scopeId: string,
80
+ titleMap: Map<string, string | null>,
81
+ ): string | null {
82
+ if (scopeId === "default") return null;
83
+ if (scopeId.startsWith("private:")) {
84
+ const conversationId = scopeId.slice("private:".length);
85
+ const title = titleMap.get(conversationId);
86
+ return title ? `Private · ${title}` : "Private";
87
+ }
88
+ return null;
89
+ }
90
+
91
+ /**
92
+ * Batch-fetch conversation titles for a set of private-scoped memory items.
93
+ * Returns a Map from conversation ID → title (or null).
94
+ */
95
+ function buildConversationTitleMap(
96
+ db: ReturnType<typeof getDb>,
97
+ scopeIds: string[],
98
+ ): Map<string, string | null> {
99
+ const conversationIds = scopeIds
100
+ .filter((s) => s.startsWith("private:"))
101
+ .map((s) => s.slice("private:".length));
102
+
103
+ const uniqueIds = [...new Set(conversationIds)];
104
+ if (uniqueIds.length === 0) return new Map();
105
+
106
+ const rows = db
107
+ .select({ id: conversations.id, title: conversations.title })
108
+ .from(conversations)
109
+ .where(inArray(conversations.id, uniqueIds))
110
+ .all();
111
+
112
+ const map = new Map<string, string | null>();
113
+ for (const row of rows) {
114
+ map.set(row.id, row.title);
115
+ }
116
+ return map;
117
+ }
118
+
65
119
  // ---------------------------------------------------------------------------
66
120
  // GET /v1/memory-items
67
121
  // ---------------------------------------------------------------------------
@@ -103,6 +157,8 @@ export function handleListMemoryItems(url: URL): Response {
103
157
 
104
158
  // Build WHERE conditions
105
159
  const conditions = [];
160
+ // Hide system-managed capability memories (skill announcements) from the UI
161
+ conditions.push(ne(memoryItems.kind, "capability"));
106
162
  if (statusParam && statusParam !== "all") {
107
163
  conditions.push(eq(memoryItems.status, statusParam));
108
164
  }
@@ -141,7 +197,17 @@ export function handleListMemoryItems(url: URL): Response {
141
197
  .offset(offsetParam)
142
198
  .all();
143
199
 
144
- return Response.json({ items, total });
200
+ // Resolve scope labels for private-scoped items
201
+ const titleMap = buildConversationTitleMap(
202
+ db,
203
+ items.map((i) => i.scopeId),
204
+ );
205
+ const enrichedItems = items.map((item) => ({
206
+ ...item,
207
+ scopeLabel: resolveScopeLabel(item.scopeId, titleMap),
208
+ }));
209
+
210
+ return Response.json({ items: enrichedItems, total });
145
211
  }
146
212
 
147
213
  // ---------------------------------------------------------------------------
@@ -183,9 +249,14 @@ export function handleGetMemoryItem(ctx: RouteContext): Response {
183
249
  supersededBySubject = superseding?.subject;
184
250
  }
185
251
 
252
+ // Resolve scope label
253
+ const titleMap = buildConversationTitleMap(db, [item.scopeId]);
254
+ const scopeLabel = resolveScopeLabel(item.scopeId, titleMap);
255
+
186
256
  return Response.json({
187
257
  item: {
188
258
  ...item,
259
+ scopeLabel,
189
260
  ...(supersedesSubject !== undefined ? { supersedesSubject } : {}),
190
261
  ...(supersededBySubject !== undefined ? { supersededBySubject } : {}),
191
262
  },
@@ -299,7 +370,14 @@ export async function handleCreateMemoryItem(
299
370
  .where(eq(memoryItems.id, id))
300
371
  .get();
301
372
 
302
- return Response.json({ item: insertedRow }, { status: 201 });
373
+ // Enrich with scopeLabel for API consistency
374
+ const titleMap = buildConversationTitleMap(db, [scopeId]);
375
+ const scopeLabel = resolveScopeLabel(scopeId, titleMap);
376
+
377
+ return Response.json(
378
+ { item: { ...insertedRow, scopeLabel } },
379
+ { status: 201 },
380
+ );
303
381
  }
304
382
 
305
383
  // ---------------------------------------------------------------------------
@@ -426,7 +504,18 @@ export async function handleUpdateMemoryItem(
426
504
  .where(eq(memoryItems.id, id))
427
505
  .get();
428
506
 
429
- return Response.json({ item: updatedRow });
507
+ // Enrich with scopeLabel for API consistency
508
+ const patchTitleMap = buildConversationTitleMap(db, [
509
+ updatedRow?.scopeId ?? existing.scopeId,
510
+ ]);
511
+ const patchScopeLabel = resolveScopeLabel(
512
+ updatedRow?.scopeId ?? existing.scopeId,
513
+ patchTitleMap,
514
+ );
515
+
516
+ return Response.json({
517
+ item: { ...updatedRow, scopeLabel: patchScopeLabel },
518
+ });
430
519
  }
431
520
 
432
521
  // ---------------------------------------------------------------------------
@@ -160,7 +160,10 @@ export async function handleMigrationExport(req: Request): Promise<Response> {
160
160
  // Best-effort: if the DB can't be checkpointed (e.g. not a valid
161
161
  // SQLite file, missing WAL, etc.) we still proceed with the export
162
162
  // using whatever is on disk.
163
- log.warn({ err }, "WAL checkpoint failed — exporting without checkpoint");
163
+ log.warn(
164
+ { err },
165
+ "WAL checkpoint failed — exporting without checkpoint",
166
+ );
164
167
  }
165
168
  },
166
169
  });
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Route handlers for OAuth app and connection CRUD.
3
+ *
4
+ * Provides endpoints for managing user-supplied OAuth apps (e.g. "your own"
5
+ * Google client credentials) and their connections. All endpoints are
6
+ * bearer-token authenticated via the standard runtime auth middleware.
7
+ */
8
+
9
+ import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
10
+ import {
11
+ deleteApp,
12
+ disconnectOAuthProvider,
13
+ getApp,
14
+ getAppClientSecret,
15
+ getConnection,
16
+ getProvider,
17
+ listApps,
18
+ listConnections,
19
+ upsertApp,
20
+ } from "../../oauth/oauth-store.js";
21
+ import { httpError } from "../http-errors.js";
22
+ import type { RouteDefinition } from "../http-router.js";
23
+
24
+ function parseGrantedScopes(grantedScopes: string | string[] | null | undefined): string[] {
25
+ if (Array.isArray(grantedScopes)) {
26
+ return grantedScopes.filter((scope): scope is string => typeof scope === "string");
27
+ }
28
+
29
+ if (typeof grantedScopes !== "string" || grantedScopes.trim() === "") {
30
+ return [];
31
+ }
32
+
33
+ try {
34
+ const parsed = JSON.parse(grantedScopes) as unknown;
35
+ if (!Array.isArray(parsed)) return [];
36
+ return parsed.filter((scope): scope is string => typeof scope === "string");
37
+ } catch {
38
+ return [];
39
+ }
40
+ }
41
+
42
+ function normalizeHasRefreshToken(
43
+ hasRefreshToken: boolean | number | null | undefined,
44
+ ): boolean {
45
+ return hasRefreshToken === true || hasRefreshToken === 1;
46
+ }
47
+
48
+ /**
49
+ * Build route definitions for OAuth app and connection CRUD endpoints.
50
+ */
51
+ export function oauthAppsRouteDefinitions(): RouteDefinition[] {
52
+ return [
53
+ // GET /v1/oauth/apps — List apps filtered by provider_key query param.
54
+ {
55
+ endpoint: "oauth/apps",
56
+ method: "GET",
57
+ handler: ({ url }) => {
58
+ const providerKey = url.searchParams.get("provider_key");
59
+ if (!providerKey) {
60
+ return httpError(
61
+ "BAD_REQUEST",
62
+ "provider_key query parameter is required",
63
+ 400,
64
+ );
65
+ }
66
+
67
+ const allApps = listApps();
68
+ const filtered = allApps.filter(
69
+ (row) => row.providerKey === providerKey,
70
+ );
71
+
72
+ const providerRow = getProvider(providerKey);
73
+ const provider = providerRow
74
+ ? {
75
+ provider_key: providerRow.providerKey,
76
+ display_name: providerRow.displayName ?? null,
77
+ description: providerRow.description ?? null,
78
+ dashboard_url: providerRow.dashboardUrl ?? null,
79
+ client_id_placeholder: providerRow.clientIdPlaceholder ?? null,
80
+ requires_client_secret: providerRow.requiresClientSecret ?? 1,
81
+ }
82
+ : null;
83
+
84
+ return Response.json({
85
+ provider,
86
+ apps: filtered.map((row) => ({
87
+ id: row.id,
88
+ provider_key: row.providerKey,
89
+ client_id: row.clientId,
90
+ created_at: row.createdAt,
91
+ updated_at: row.updatedAt,
92
+ })),
93
+ });
94
+ },
95
+ },
96
+
97
+ // POST /v1/oauth/apps — Create an OAuth app.
98
+ {
99
+ endpoint: "oauth/apps",
100
+ method: "POST",
101
+ policyKey: "oauth/apps.create",
102
+ handler: async ({ req }) => {
103
+ const body = (await req.json()) as {
104
+ provider_key?: string;
105
+ client_id?: string;
106
+ client_secret?: string;
107
+ };
108
+
109
+ const { provider_key, client_id, client_secret } = body;
110
+
111
+ if (
112
+ !provider_key ||
113
+ typeof provider_key !== "string" ||
114
+ !client_id ||
115
+ typeof client_id !== "string" ||
116
+ !client_secret ||
117
+ typeof client_secret !== "string"
118
+ ) {
119
+ return httpError(
120
+ "BAD_REQUEST",
121
+ "provider_key, client_id, and client_secret are required non-empty strings",
122
+ 400,
123
+ );
124
+ }
125
+
126
+ const provider = getProvider(provider_key);
127
+ if (!provider) {
128
+ return httpError(
129
+ "NOT_FOUND",
130
+ `No OAuth provider registered for "${provider_key}"`,
131
+ 404,
132
+ );
133
+ }
134
+
135
+ const app = await upsertApp(provider_key, client_id, {
136
+ clientSecretValue: client_secret,
137
+ });
138
+
139
+ return Response.json(
140
+ {
141
+ app: {
142
+ id: app.id,
143
+ provider_key: app.providerKey,
144
+ client_id: app.clientId,
145
+ created_at: app.createdAt,
146
+ updated_at: app.updatedAt,
147
+ },
148
+ },
149
+ { status: 201 },
150
+ );
151
+ },
152
+ },
153
+
154
+ // DELETE /v1/oauth/apps/:id — Delete an OAuth app.
155
+ {
156
+ endpoint: "oauth/apps/:id",
157
+ method: "DELETE",
158
+ policyKey: "oauth/apps.delete",
159
+ handler: async ({ params }) => {
160
+ const app = getApp(params.id);
161
+ if (!app) {
162
+ return httpError("NOT_FOUND", `OAuth app not found: ${params.id}`, 404);
163
+ }
164
+
165
+ // Disconnect all connections for this app first to clean up tokens.
166
+ const connections = listConnections(app.providerKey, app.clientId);
167
+ for (const conn of connections) {
168
+ await disconnectOAuthProvider(app.providerKey, app.clientId, conn.id);
169
+ }
170
+
171
+ await deleteApp(params.id);
172
+
173
+ return Response.json({ ok: true });
174
+ },
175
+ },
176
+
177
+ // GET /v1/oauth/apps/:appId/connections — List connections for an app.
178
+ {
179
+ endpoint: "oauth/apps/:appId/connections",
180
+ method: "GET",
181
+ handler: ({ params }) => {
182
+ const app = getApp(params.appId);
183
+ if (!app) {
184
+ return httpError(
185
+ "NOT_FOUND",
186
+ `OAuth app not found: ${params.appId}`,
187
+ 404,
188
+ );
189
+ }
190
+
191
+ const connections = listConnections(app.providerKey, app.clientId);
192
+
193
+ return Response.json({
194
+ connections: connections.map((row) => ({
195
+ id: row.id,
196
+ provider_key: row.providerKey,
197
+ account_info: row.accountInfo,
198
+ granted_scopes: parseGrantedScopes(row.grantedScopes),
199
+ status: row.status,
200
+ has_refresh_token: normalizeHasRefreshToken(row.hasRefreshToken),
201
+ expires_at: row.expiresAt,
202
+ created_at: row.createdAt,
203
+ updated_at: row.updatedAt,
204
+ })),
205
+ });
206
+ },
207
+ },
208
+
209
+ // DELETE /v1/oauth/connections/:id — Disconnect a single connection.
210
+ {
211
+ endpoint: "oauth/connections/:id",
212
+ method: "DELETE",
213
+ handler: async ({ params }) => {
214
+ const conn = getConnection(params.id);
215
+ if (!conn) {
216
+ return httpError(
217
+ "NOT_FOUND",
218
+ `OAuth connection not found: ${params.id}`,
219
+ 404,
220
+ );
221
+ }
222
+
223
+ const result = await disconnectOAuthProvider(
224
+ conn.providerKey,
225
+ undefined,
226
+ conn.id,
227
+ );
228
+ if (result === "error") {
229
+ return httpError(
230
+ "INTERNAL_ERROR",
231
+ "Failed to clean up connection tokens. The connection was not removed.",
232
+ 500,
233
+ );
234
+ }
235
+
236
+ return Response.json({ ok: true });
237
+ },
238
+ },
239
+
240
+ // POST /v1/oauth/apps/:appId/connect — Start OAuth connect flow.
241
+ {
242
+ endpoint: "oauth/apps/:appId/connect",
243
+ method: "POST",
244
+ handler: async ({ req, params }) => {
245
+ const app = getApp(params.appId);
246
+ if (!app) {
247
+ return httpError(
248
+ "NOT_FOUND",
249
+ `OAuth app not found: ${params.appId}`,
250
+ 404,
251
+ );
252
+ }
253
+
254
+ let body: { scopes?: string[] } = {};
255
+ try {
256
+ const text = await req.text();
257
+ if (text) {
258
+ body = JSON.parse(text);
259
+ }
260
+ } catch {
261
+ // No body or invalid JSON — use defaults
262
+ }
263
+
264
+ const clientSecret = await getAppClientSecret(app);
265
+
266
+ const result = await orchestrateOAuthConnect({
267
+ service: app.providerKey,
268
+ clientId: app.clientId,
269
+ clientSecret,
270
+ requestedScopes: body.scopes,
271
+ isInteractive: false,
272
+ });
273
+
274
+ if (result.success && result.deferred) {
275
+ return Response.json({
276
+ auth_url: result.authUrl,
277
+ state: result.state,
278
+ });
279
+ }
280
+
281
+ if (!result.success) {
282
+ return Response.json({ error: result.error }, { status: 500 });
283
+ }
284
+
285
+ // Interactive success (shouldn't happen with isInteractive: false,
286
+ // but handle gracefully)
287
+ return Response.json({ ok: true });
288
+ },
289
+ },
290
+ ];
291
+ }
@@ -11,8 +11,11 @@ import {
11
11
  } from "../../config/loader.js";
12
12
  import type { CesClient } from "../../credential-execution/client.js";
13
13
  import { setSentryOrganizationId } from "../../instrument.js";
14
+ import { clearEmbeddingBackendCache } from "../../memory/embedding-backend.js";
14
15
  import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
15
16
  import { validateAnthropicApiKey } from "../../providers/anthropic/client.js";
17
+ import { validateGeminiApiKey } from "../../providers/gemini/client.js";
18
+ import { validateOpenAIApiKey } from "../../providers/openai/client.js";
16
19
  import { initializeProviders } from "../../providers/registry.js";
17
20
  import { credentialKey } from "../../security/credential-key.js";
18
21
  import {
@@ -131,7 +134,7 @@ export async function handleAddSecret(
131
134
  400,
132
135
  );
133
136
  }
134
- // Validate Anthropic API keys before storing
137
+ // Validate API keys before storing (Anthropic, OpenAI, Gemini)
135
138
  if (name === "anthropic") {
136
139
  const validation = await validateAnthropicApiKey(value);
137
140
  if (!validation.valid) {
@@ -144,7 +147,32 @@ export async function handleAddSecret(
144
147
  { status: 422 },
145
148
  );
146
149
  }
150
+ } else if (name === "openai") {
151
+ const validation = await validateOpenAIApiKey(value);
152
+ if (!validation.valid) {
153
+ log.warn(
154
+ { provider: name, reason: validation.reason },
155
+ "API key validation failed",
156
+ );
157
+ return Response.json(
158
+ { success: false, error: validation.reason },
159
+ { status: 422 },
160
+ );
161
+ }
162
+ } else if (name === "gemini") {
163
+ const validation = await validateGeminiApiKey(value);
164
+ if (!validation.valid) {
165
+ log.warn(
166
+ { provider: name, reason: validation.reason },
167
+ "API key validation failed",
168
+ );
169
+ return Response.json(
170
+ { success: false, error: validation.reason },
171
+ { status: 422 },
172
+ );
173
+ }
147
174
  }
175
+ // fireworks, openrouter, ollama — no validation (allow storage)
148
176
 
149
177
  const stored = await setSecureKeyAsync(name, value);
150
178
  if (!stored) {
@@ -319,6 +347,7 @@ export async function handleDeleteSecret(req: Request): Promise<Response> {
319
347
  500,
320
348
  );
321
349
  }
350
+ clearEmbeddingBackendCache();
322
351
  invalidateConfigCache();
323
352
  await initializeProviders(getConfig());
324
353
  log.info({ provider: name }, "API key deleted via HTTP");
@@ -644,6 +644,20 @@ export function settingsRouteDefinitions(): RouteDefinition[] {
644
644
  },
645
645
 
646
646
  // OAuth connect
647
+ {
648
+ endpoint: "oauth/start",
649
+ method: "POST",
650
+ policyKey: "oauth/start",
651
+ handler: async ({ req }) => {
652
+ const body = (await req.json()) as {
653
+ service?: string;
654
+ requestedScopes?: string[];
655
+ };
656
+ return handleOAuthConnectStart(body);
657
+ },
658
+ },
659
+ // Legacy alias for oauth/start (kept for backwards compatibility with
660
+ // older clients and platform proxy routes)
647
661
  {
648
662
  endpoint: "integrations/oauth/start",
649
663
  method: "POST",