@vellumai/assistant 0.6.1 → 0.6.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 (463) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docker-entrypoint.sh +12 -2
  4. package/docs/architecture/memory.md +1 -1
  5. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  7. package/openapi.yaml +184 -69
  8. package/package.json +41 -41
  9. package/scripts/generate-openapi.ts +1 -2
  10. package/src/__tests__/acp-session.test.ts +43 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +1 -0
  13. package/src/__tests__/app-source-watcher.test.ts +37 -11
  14. package/src/__tests__/approval-routes-http.test.ts +178 -1
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  17. package/src/__tests__/browser-manager.test.ts +40 -27
  18. package/src/__tests__/catalog-files.test.ts +862 -0
  19. package/src/__tests__/channel-approvals.test.ts +53 -0
  20. package/src/__tests__/checker.test.ts +104 -170
  21. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  22. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  23. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  24. package/src/__tests__/config-schema.test.ts +125 -48
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  26. package/src/__tests__/context-overflow-approval.test.ts +21 -6
  27. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  28. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  29. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  30. package/src/__tests__/conversation-attachments.test.ts +80 -4
  31. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  32. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  33. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  34. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  35. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  36. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  37. package/src/__tests__/conversation-queue.test.ts +45 -2
  38. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  39. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  40. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  42. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  43. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  44. package/src/__tests__/conversation-store.test.ts +195 -0
  45. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  46. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
  47. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  48. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  49. package/src/__tests__/credential-vault.test.ts +152 -13
  50. package/src/__tests__/credentials-cli.test.ts +2 -2
  51. package/src/__tests__/date-context.test.ts +4 -4
  52. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  53. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  54. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  55. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  56. package/src/__tests__/gemini-provider.test.ts +2 -2
  57. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  58. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  59. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  60. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  61. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  62. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  63. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  64. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  65. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  66. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  67. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  68. package/src/__tests__/host-browser-routes.test.ts +198 -0
  69. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  70. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  71. package/src/__tests__/host-file-proxy.test.ts +185 -1
  72. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  73. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  74. package/src/__tests__/host-shell-tool.test.ts +1 -11
  75. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  76. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  77. package/src/__tests__/inline-command-runner.test.ts +7 -5
  78. package/src/__tests__/integration-status.test.ts +6 -7
  79. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  80. package/src/__tests__/log-export-workspace.test.ts +190 -0
  81. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  82. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  83. package/src/__tests__/mcp-health-check.test.ts +10 -3
  84. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  85. package/src/__tests__/migration-export-http.test.ts +61 -2
  86. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  87. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  88. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  89. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  90. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  91. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  92. package/src/__tests__/oauth-cli.test.ts +707 -60
  93. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  94. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  95. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  96. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  97. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  98. package/src/__tests__/oauth-store.test.ts +1386 -182
  99. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  100. package/src/__tests__/onboarding-template-contract.test.ts +74 -55
  101. package/src/__tests__/openai-provider.test.ts +2 -2
  102. package/src/__tests__/outlook-categories.test.ts +1 -1
  103. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  104. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  105. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  106. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  107. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  108. package/src/__tests__/outlook-trash.test.ts +1 -1
  109. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  110. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  111. package/src/__tests__/permission-mode.test.ts +28 -56
  112. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  113. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  114. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  115. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  116. package/src/__tests__/require-fresh-approval.test.ts +40 -3
  117. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  118. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  119. package/src/__tests__/schedule-routes.test.ts +162 -0
  120. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  121. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  123. package/src/__tests__/set-permission-mode.test.ts +13 -250
  124. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  125. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  126. package/src/__tests__/slack-channel-config.test.ts +12 -15
  127. package/src/__tests__/subagent-detail.test.ts +44 -2
  128. package/src/__tests__/subagent-disposal.test.ts +1 -0
  129. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  130. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  131. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  132. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  134. package/src/__tests__/subagent-tools.test.ts +1 -0
  135. package/src/__tests__/subagent-types.test.ts +1 -0
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  137. package/src/__tests__/system-prompt.test.ts +72 -1
  138. package/src/__tests__/task-scheduler.test.ts +32 -6
  139. package/src/__tests__/telegram-config.test.ts +10 -13
  140. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  141. package/src/__tests__/terminal-tools.test.ts +11 -5
  142. package/src/__tests__/test-preload.ts +14 -0
  143. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  144. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  145. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  146. package/src/__tests__/tool-executor.test.ts +0 -1
  147. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  148. package/src/__tests__/top-level-renderer.test.ts +73 -1
  149. package/src/__tests__/transport-hints-queue.test.ts +62 -0
  150. package/src/__tests__/trust-store.test.ts +4 -4
  151. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  152. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  153. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  154. package/src/__tests__/workspace-policy.test.ts +2 -7
  155. package/src/acp/client-handler.ts +30 -4
  156. package/src/agent/loop.ts +12 -35
  157. package/src/approvals/guardian-request-resolvers.ts +21 -15
  158. package/src/browser-session/__tests__/manager.test.ts +297 -0
  159. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  160. package/src/browser-session/backends/extension.ts +26 -0
  161. package/src/browser-session/backends/local.ts +24 -0
  162. package/src/browser-session/events.ts +164 -0
  163. package/src/browser-session/index.ts +27 -0
  164. package/src/browser-session/manager.ts +159 -0
  165. package/src/browser-session/types.ts +28 -0
  166. package/src/channels/__tests__/types.test.ts +134 -0
  167. package/src/channels/types.ts +55 -0
  168. package/src/cli/__tests__/run-assistant-command.ts +34 -7
  169. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  170. package/src/cli/commands/browser-relay.ts +339 -409
  171. package/src/cli/commands/credentials.ts +3 -3
  172. package/src/cli/commands/default-action.ts +68 -1
  173. package/src/cli/commands/email.ts +18 -13
  174. package/src/cli/commands/mcp.ts +16 -4
  175. package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
  176. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  177. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  178. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  179. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  180. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  181. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  182. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  183. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  184. package/src/cli/commands/oauth/apps.ts +7 -4
  185. package/src/cli/commands/oauth/connect.ts +16 -2
  186. package/src/cli/commands/oauth/disconnect.ts +1 -1
  187. package/src/cli/commands/oauth/providers.ts +200 -36
  188. package/src/cli/commands/oauth/shared.ts +5 -5
  189. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  190. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  191. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  192. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/platform/index.ts +107 -10
  194. package/src/cli/commands/usage.ts +10 -9
  195. package/src/cli/lib/daemon-credential-client.ts +4 -0
  196. package/src/cli/program.ts +10 -3
  197. package/src/config/assistant-feature-flags.ts +59 -55
  198. package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
  199. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  200. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  201. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  202. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  203. package/src/config/bundled-skills/document/SKILL.md +4 -0
  204. package/src/config/bundled-skills/gmail/SKILL.md +12 -7
  205. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  206. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  207. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  208. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  209. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  210. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  211. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  212. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  213. package/src/config/env-registry.ts +14 -0
  214. package/src/config/env.ts +21 -0
  215. package/src/config/feature-flag-registry.json +46 -7
  216. package/src/config/loader.ts +56 -1
  217. package/src/config/sanitize-for-transfer.ts +47 -0
  218. package/src/config/schema.ts +46 -5
  219. package/src/config/schemas/host-browser.ts +66 -0
  220. package/src/config/schemas/memory-lifecycle.ts +1 -1
  221. package/src/config/schemas/memory-retrieval.ts +103 -0
  222. package/src/config/schemas/security.ts +0 -6
  223. package/src/config/schemas/services.ts +16 -0
  224. package/src/config/types.ts +0 -1
  225. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  226. package/src/context/window-manager.ts +19 -1
  227. package/src/credential-execution/approval-bridge.ts +49 -16
  228. package/src/credential-execution/managed-catalog.ts +3 -7
  229. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  230. package/src/daemon/app-source-watcher.ts +35 -0
  231. package/src/daemon/config-watcher.ts +6 -2
  232. package/src/daemon/context-overflow-approval.ts +5 -1
  233. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  234. package/src/daemon/conversation-agent-loop.ts +74 -19
  235. package/src/daemon/conversation-attachments.ts +40 -1
  236. package/src/daemon/conversation-messaging.ts +3 -0
  237. package/src/daemon/conversation-process.ts +66 -3
  238. package/src/daemon/conversation-queue-manager.ts +8 -0
  239. package/src/daemon/conversation-runtime-assembly.ts +159 -20
  240. package/src/daemon/conversation-surfaces.ts +78 -12
  241. package/src/daemon/conversation-tool-setup.ts +74 -11
  242. package/src/daemon/conversation-workspace.ts +12 -0
  243. package/src/daemon/conversation.ts +227 -11
  244. package/src/daemon/date-context.ts +10 -10
  245. package/src/daemon/first-greeting.ts +3 -2
  246. package/src/daemon/handlers/conversations.ts +9 -139
  247. package/src/daemon/handlers/shared.ts +65 -0
  248. package/src/daemon/handlers/skills.ts +232 -37
  249. package/src/daemon/host-bash-proxy.ts +48 -13
  250. package/src/daemon/host-browser-proxy.ts +191 -0
  251. package/src/daemon/host-cu-proxy.ts +36 -11
  252. package/src/daemon/host-file-proxy.ts +57 -9
  253. package/src/daemon/lifecycle.ts +86 -12
  254. package/src/daemon/message-protocol.ts +7 -0
  255. package/src/daemon/message-types/conversations.ts +59 -13
  256. package/src/daemon/message-types/host-browser.ts +100 -0
  257. package/src/daemon/message-types/messages.ts +5 -6
  258. package/src/daemon/message-types/notifications.ts +12 -0
  259. package/src/daemon/message-types/settings.ts +12 -0
  260. package/src/daemon/message-types/skills.ts +10 -0
  261. package/src/daemon/message-types/subagents.ts +2 -0
  262. package/src/daemon/server.ts +112 -35
  263. package/src/daemon/tool-side-effects.ts +6 -0
  264. package/src/daemon/transport-hints.ts +14 -0
  265. package/src/inbound/platform-callback-registration.ts +18 -17
  266. package/src/index.ts +1 -1
  267. package/src/mcp/client.ts +59 -24
  268. package/src/memory/app-store.ts +31 -1
  269. package/src/memory/conversation-crud.ts +38 -10
  270. package/src/memory/conversation-directories.ts +39 -0
  271. package/src/memory/conversation-group-migration.ts +65 -5
  272. package/src/memory/conversation-starters-cadence.ts +76 -0
  273. package/src/memory/conversation-title-service.ts +5 -2
  274. package/src/memory/db-init.ts +12 -0
  275. package/src/memory/embedding-backend.test.ts +75 -0
  276. package/src/memory/embedding-backend.ts +131 -5
  277. package/src/memory/embedding-gemini.test.ts +54 -0
  278. package/src/memory/embedding-gemini.ts +20 -9
  279. package/src/memory/embedding-local.ts +177 -18
  280. package/src/memory/graph/capability-seed.ts +3 -5
  281. package/src/memory/graph/consolidation.ts +10 -23
  282. package/src/memory/graph/extraction-job.ts +15 -0
  283. package/src/memory/graph/retriever.ts +40 -22
  284. package/src/memory/graph/store.test.ts +7 -3
  285. package/src/memory/graph/store.ts +47 -12
  286. package/src/memory/group-crud.ts +25 -9
  287. package/src/memory/llm-usage-store.ts +45 -4
  288. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  289. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  290. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  291. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  292. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  293. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  294. package/src/memory/migrations/index.ts +6 -0
  295. package/src/memory/migrations/registry.ts +8 -0
  296. package/src/memory/schema/conversations.ts +1 -0
  297. package/src/memory/schema/oauth.ts +18 -13
  298. package/src/messaging/provider.ts +1 -1
  299. package/src/notifications/broadcaster.ts +6 -0
  300. package/src/notifications/conversation-pairing.ts +12 -4
  301. package/src/notifications/emit-signal.ts +14 -0
  302. package/src/notifications/signal.ts +11 -0
  303. package/src/oauth/AGENTS.md +76 -0
  304. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  305. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  306. package/src/oauth/byo-connection.test.ts +8 -8
  307. package/src/oauth/byo-connection.ts +7 -7
  308. package/src/oauth/connect-orchestrator.ts +23 -21
  309. package/src/oauth/connect-types.ts +3 -3
  310. package/src/oauth/connection-resolver.test.ts +17 -4
  311. package/src/oauth/connection-resolver.ts +16 -16
  312. package/src/oauth/connection.ts +1 -1
  313. package/src/oauth/manual-token-connection.ts +13 -13
  314. package/src/oauth/oauth-store.ts +214 -100
  315. package/src/oauth/platform-connection.test.ts +5 -5
  316. package/src/oauth/platform-connection.ts +4 -4
  317. package/src/oauth/provider-serializer.ts +31 -5
  318. package/src/oauth/revoke.ts +76 -0
  319. package/src/oauth/seed-providers.ts +127 -87
  320. package/src/oauth/token-persistence.ts +1 -1
  321. package/src/permissions/checker.ts +3 -3
  322. package/src/permissions/defaults.ts +7 -8
  323. package/src/permissions/permission-mode.ts +4 -11
  324. package/src/permissions/prompter.ts +13 -3
  325. package/src/permissions/v2-consent-policy.ts +87 -0
  326. package/src/platform/client.ts +1 -1
  327. package/src/prompts/system-prompt.ts +18 -21
  328. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  329. package/src/prompts/templates/BOOTSTRAP.md +59 -96
  330. package/src/prompts/templates/SOUL.md +11 -11
  331. package/src/providers/anthropic/client.ts +1 -0
  332. package/src/providers/types.ts +1 -1
  333. package/src/runtime/AGENTS.md +23 -0
  334. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  335. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  336. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  337. package/src/runtime/assistant-event-hub.ts +24 -2
  338. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  339. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  341. package/src/runtime/auth/middleware.ts +98 -0
  342. package/src/runtime/auth/route-policy.ts +6 -7
  343. package/src/runtime/auth/token-service.ts +8 -0
  344. package/src/runtime/capability-tokens.ts +414 -0
  345. package/src/runtime/channel-approvals.ts +18 -5
  346. package/src/runtime/chrome-extension-registry.ts +332 -0
  347. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  348. package/src/runtime/guardian-decision-types.ts +7 -0
  349. package/src/runtime/http-server.ts +425 -70
  350. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  351. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  352. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  353. package/src/runtime/migrations/migration-transport.ts +6 -0
  354. package/src/runtime/migrations/migration-wizard.ts +22 -2
  355. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  356. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  357. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  358. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  359. package/src/runtime/pending-interactions.ts +29 -13
  360. package/src/runtime/routes/approval-routes.ts +90 -16
  361. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  362. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  363. package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
  364. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  365. package/src/runtime/routes/conversation-routes.ts +308 -28
  366. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  367. package/src/runtime/routes/group-routes.ts +22 -8
  368. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  369. package/src/runtime/routes/host-browser-routes.ts +279 -0
  370. package/src/runtime/routes/host-file-routes.ts +9 -1
  371. package/src/runtime/routes/identity-routes.ts +259 -16
  372. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  373. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  374. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  375. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  376. package/src/runtime/routes/log-export-routes.ts +60 -25
  377. package/src/runtime/routes/memory-item-routes.ts +1 -7
  378. package/src/runtime/routes/migration-routes.ts +87 -2
  379. package/src/runtime/routes/oauth-apps.ts +15 -17
  380. package/src/runtime/routes/oauth-providers.ts +4 -0
  381. package/src/runtime/routes/schedule-routes.ts +24 -11
  382. package/src/runtime/routes/settings-routes.ts +9 -97
  383. package/src/runtime/routes/skills-routes.ts +52 -2
  384. package/src/runtime/routes/subagents-routes.ts +14 -10
  385. package/src/runtime/routes/usage-routes.ts +8 -7
  386. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  387. package/src/runtime/routes/workspace-routes.ts +8 -1
  388. package/src/runtime/routes/workspace-utils.ts +2 -0
  389. package/src/schedule/scheduler.ts +7 -5
  390. package/src/security/ces-credential-client.ts +20 -0
  391. package/src/security/ces-rpc-credential-backend.ts +17 -0
  392. package/src/security/credential-backend.ts +5 -0
  393. package/src/security/oauth2.ts +42 -25
  394. package/src/security/secure-keys.ts +118 -25
  395. package/src/security/token-manager.ts +23 -10
  396. package/src/skills/catalog-files.ts +492 -0
  397. package/src/skills/inline-command-runner.ts +12 -14
  398. package/src/subagent/manager.ts +131 -26
  399. package/src/subagent/types.ts +19 -0
  400. package/src/tools/apps/executors.ts +11 -2
  401. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  402. package/src/tools/browser/auth-detector.ts +43 -12
  403. package/src/tools/browser/browser-execution.ts +645 -340
  404. package/src/tools/browser/browser-manager.ts +36 -12
  405. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  406. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  407. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  408. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  409. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  410. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  411. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  412. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  413. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  414. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  415. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  416. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  417. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  418. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  419. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  420. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  421. package/src/tools/browser/cdp-client/errors.ts +34 -0
  422. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  423. package/src/tools/browser/cdp-client/factory.ts +204 -0
  424. package/src/tools/browser/cdp-client/index.ts +14 -0
  425. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  426. package/src/tools/browser/cdp-client/types.ts +52 -0
  427. package/src/tools/filesystem/edit.ts +1 -1
  428. package/src/tools/filesystem/list.ts +1 -1
  429. package/src/tools/filesystem/read.ts +1 -1
  430. package/src/tools/filesystem/write.ts +2 -1
  431. package/src/tools/host-filesystem/edit.ts +1 -1
  432. package/src/tools/host-filesystem/read.ts +12 -15
  433. package/src/tools/host-filesystem/write.ts +1 -1
  434. package/src/tools/host-terminal/host-shell.ts +21 -16
  435. package/src/tools/permission-checker.ts +77 -100
  436. package/src/tools/registry.ts +0 -2
  437. package/src/tools/secret-detection-handler.ts +34 -1
  438. package/src/tools/shared/filesystem/image-read.ts +61 -40
  439. package/src/tools/skills/sandbox-runner.ts +3 -6
  440. package/src/tools/subagent/spawn.ts +47 -3
  441. package/src/tools/subagent/status.ts +2 -0
  442. package/src/tools/system/register.ts +2 -16
  443. package/src/tools/terminal/safe-env.ts +7 -0
  444. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  445. package/src/tools/terminal/sandbox.ts +4 -1
  446. package/src/tools/terminal/shell.ts +24 -21
  447. package/src/tools/tool-approval-handler.ts +48 -2
  448. package/src/tools/types.ts +2 -3
  449. package/src/util/platform.ts +14 -19
  450. package/src/watcher/provider-types.ts +1 -1
  451. package/src/workspace/migrations/029-seed-pkb.ts +1 -0
  452. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  453. package/src/workspace/migrations/registry.ts +2 -0
  454. package/src/workspace/top-level-renderer.ts +19 -1
  455. package/src/__tests__/chrome-cdp.test.ts +0 -419
  456. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  457. package/src/__tests__/permission-mode-store.test.ts +0 -277
  458. package/src/browser-extension-relay/protocol.ts +0 -63
  459. package/src/browser-extension-relay/server.ts +0 -203
  460. package/src/config/schemas/sandbox.ts +0 -14
  461. package/src/permissions/permission-mode-store.ts +0 -180
  462. package/src/tools/browser/chrome-cdp.ts +0 -239
  463. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -3,6 +3,11 @@
3
3
  *
4
4
  * Backed by Drizzle + SQLite. All JSON fields (default_scopes, scope_policy,
5
5
  * extra_params, granted_scopes, metadata) are stored as serialized JSON strings.
6
+ *
7
+ * Note: TS field names use camelCase from the platform's naming
8
+ * (provider, authorizeUrl, tokenExchangeUrl, displayLabel, authorizeParams),
9
+ * while underlying SQL columns retain their original snake_case names
10
+ * (provider_key, auth_url, token_url, display_name, extra_params).
6
11
  */
7
12
 
8
13
  import {
@@ -26,6 +31,7 @@ import {
26
31
  setSecureKeyAsync,
27
32
  } from "../security/secure-keys.js";
28
33
  import { getLogger } from "../util/logger.js";
34
+ import { tryRevokeOAuthToken } from "./revoke.js";
29
35
 
30
36
  const log = getLogger("oauth-store");
31
37
 
@@ -43,14 +49,16 @@ export type OAuthConnectionRow = typeof oauthConnections.$inferSelect;
43
49
 
44
50
  /**
45
51
  * Seed well-known provider profiles into the database. Uses INSERT … ON
46
- * CONFLICT DO UPDATE so that implementation fields (authUrl, tokenUrl,
47
- * tokenEndpointAuthMethod, userinfoUrl, extraParams,
48
- * pingUrl, pingMethod, pingHeaders, pingBody, managedServiceConfigKey,
52
+ * CONFLICT DO UPDATE so that implementation fields (authorizeUrl, tokenExchangeUrl,
53
+ * refreshUrl, tokenEndpointAuthMethod, userinfoUrl, authorizeParams,
54
+ * pingUrl, pingMethod, pingHeaders, pingBody, revokeUrl, revokeBodyTemplate,
55
+ * managedServiceConfigKey,
49
56
  * loopbackPort, injectionTemplates, appType, setupNotes,
50
57
  * identityUrl, identityMethod, identityHeaders, identityBody,
51
- * identityResponsePaths, identityFormat, identityOkField, featureFlag)
52
- * and display metadata (displayName, description, dashboardUrl,
53
- * clientIdPlaceholder, requiresClientSecret) propagate to existing
58
+ * identityResponsePaths, identityFormat, identityOkField, featureFlag,
59
+ * scopeSeparator)
60
+ * and display metadata (displayLabel, description, dashboardUrl,
61
+ * clientIdPlaceholder, logoUrl, requiresClientSecret) propagate to existing
54
62
  * installations on every startup, while user-customizable fields
55
63
  * (defaultScopes, scopePolicy) are only written on the
56
64
  * initial insert. baseUrl is backfilled from seed data when null
@@ -59,24 +67,29 @@ export type OAuthConnectionRow = typeof oauthConnections.$inferSelect;
59
67
  */
60
68
  export function seedProviders(
61
69
  profiles: Array<{
62
- providerKey: string;
63
- authUrl: string;
64
- tokenUrl: string;
70
+ provider: string;
71
+ authorizeUrl: string;
72
+ tokenExchangeUrl: string;
73
+ refreshUrl?: string;
65
74
  tokenEndpointAuthMethod?: string;
66
75
  userinfoUrl?: string;
67
76
  pingUrl?: string;
68
77
  pingMethod?: string;
69
78
  pingHeaders?: Record<string, string>;
70
79
  pingBody?: unknown;
80
+ revokeUrl?: string;
81
+ revokeBodyTemplate?: Record<string, string>;
71
82
  baseUrl?: string;
72
83
  defaultScopes: string[];
73
84
  scopePolicy: Record<string, unknown>;
74
- extraParams?: Record<string, string>;
85
+ scopeSeparator?: string;
86
+ authorizeParams?: Record<string, string>;
75
87
  managedServiceConfigKey?: string;
76
- displayName?: string;
88
+ displayLabel?: string;
77
89
  description?: string;
78
90
  dashboardUrl?: string | null;
79
91
  clientIdPlaceholder?: string | null;
92
+ logoUrl?: string | null;
80
93
  requiresClientSecret?: boolean;
81
94
  loopbackPort?: number;
82
95
  injectionTemplates?: Array<{
@@ -100,24 +113,41 @@ export function seedProviders(
100
113
  const db = getDb();
101
114
  const now = Date.now();
102
115
  for (const p of profiles) {
103
- const authUrl = p.authUrl;
104
- const tokenUrl = p.tokenUrl;
105
- const tokenEndpointAuthMethod = p.tokenEndpointAuthMethod ?? null;
116
+ const authorizeUrl = p.authorizeUrl;
117
+ const tokenExchangeUrl = p.tokenExchangeUrl;
118
+ const refreshUrl = p.refreshUrl ?? null;
119
+ // Coerce undefined and empty string to the default. The schema declares
120
+ // this column as NOT NULL with default "client_secret_post"; passing null
121
+ // here would be a type error, and an empty string is never a valid OAuth
122
+ // token endpoint auth method.
123
+ const tokenEndpointAuthMethod =
124
+ p.tokenEndpointAuthMethod || "client_secret_post";
106
125
  const userinfoUrl = p.userinfoUrl ?? null;
107
126
  const pingUrl = p.pingUrl ?? null;
108
127
  const pingMethod = p.pingMethod ?? null;
109
128
  const pingHeaders = p.pingHeaders ? JSON.stringify(p.pingHeaders) : null;
110
129
  const pingBody =
111
130
  p.pingBody !== undefined ? JSON.stringify(p.pingBody) : null;
131
+ const revokeUrl = p.revokeUrl ?? null;
132
+ const revokeBodyTemplate = p.revokeBodyTemplate
133
+ ? JSON.stringify(p.revokeBodyTemplate)
134
+ : null;
112
135
  const baseUrl = p.baseUrl ?? null;
113
136
  const defaultScopes = JSON.stringify(p.defaultScopes);
114
137
  const scopePolicy = JSON.stringify(p.scopePolicy);
115
- const extraParams = p.extraParams ? JSON.stringify(p.extraParams) : null;
138
+ // Coerce empty string to the default space separator. An empty separator
139
+ // would join scopes into a single concatenated token (e.g. "readwrite"),
140
+ // which is never a valid OAuth authorize URL value.
141
+ const scopeSeparator = p.scopeSeparator || " ";
142
+ const authorizeParams = p.authorizeParams
143
+ ? JSON.stringify(p.authorizeParams)
144
+ : null;
116
145
  const managedServiceConfigKey = p.managedServiceConfigKey ?? null;
117
- const displayName = p.displayName ?? null;
146
+ const displayLabel = p.displayLabel ?? null;
118
147
  const description = p.description ?? null;
119
148
  const dashboardUrl = p.dashboardUrl ?? null;
120
149
  const clientIdPlaceholder = p.clientIdPlaceholder ?? null;
150
+ const logoUrl = p.logoUrl ?? null;
121
151
  const requiresClientSecret = p.requiresClientSecret !== false ? 1 : 0;
122
152
  const loopbackPort = p.loopbackPort ?? null;
123
153
  const injectionTemplates = p.injectionTemplates
@@ -141,24 +171,29 @@ export function seedProviders(
141
171
 
142
172
  db.insert(oauthProviders)
143
173
  .values({
144
- providerKey: p.providerKey,
145
- authUrl,
146
- tokenUrl,
174
+ provider: p.provider,
175
+ authorizeUrl,
176
+ tokenExchangeUrl,
177
+ refreshUrl,
147
178
  tokenEndpointAuthMethod,
148
179
  userinfoUrl,
149
180
  baseUrl,
150
181
  defaultScopes,
151
182
  scopePolicy,
152
- extraParams,
183
+ scopeSeparator,
184
+ authorizeParams,
153
185
  pingUrl,
154
186
  pingMethod,
155
187
  pingHeaders,
156
188
  pingBody,
189
+ revokeUrl,
190
+ revokeBodyTemplate,
157
191
  managedServiceConfigKey,
158
- displayName,
192
+ displayLabel,
159
193
  description,
160
194
  dashboardUrl,
161
195
  clientIdPlaceholder,
196
+ logoUrl,
162
197
  requiresClientSecret,
163
198
  loopbackPort,
164
199
  injectionTemplates,
@@ -176,23 +211,28 @@ export function seedProviders(
176
211
  updatedAt: now,
177
212
  })
178
213
  .onConflictDoUpdate({
179
- target: oauthProviders.providerKey,
214
+ target: oauthProviders.provider,
180
215
  set: {
181
- authUrl,
182
- tokenUrl,
216
+ authorizeUrl,
217
+ tokenExchangeUrl,
218
+ refreshUrl,
183
219
  tokenEndpointAuthMethod,
184
220
  userinfoUrl,
185
221
  baseUrl: sql`COALESCE(${oauthProviders.baseUrl}, ${baseUrl})`,
186
- extraParams,
222
+ scopeSeparator,
223
+ authorizeParams,
187
224
  pingUrl,
188
225
  pingMethod,
189
226
  pingHeaders,
190
227
  pingBody,
228
+ revokeUrl,
229
+ revokeBodyTemplate,
191
230
  managedServiceConfigKey,
192
- displayName,
231
+ displayLabel,
193
232
  description,
194
233
  dashboardUrl,
195
234
  clientIdPlaceholder,
235
+ logoUrl,
196
236
  requiresClientSecret,
197
237
  loopbackPort,
198
238
  injectionTemplates,
@@ -214,12 +254,12 @@ export function seedProviders(
214
254
  }
215
255
 
216
256
  /** Look up a provider by its primary key. */
217
- export function getProvider(providerKey: string): OAuthProviderRow | undefined {
257
+ export function getProvider(provider: string): OAuthProviderRow | undefined {
218
258
  const db = getDb();
219
259
  return db
220
260
  .select()
221
261
  .from(oauthProviders)
222
- .where(eq(oauthProviders.providerKey, providerKey))
262
+ .where(eq(oauthProviders.provider, provider))
223
263
  .get();
224
264
  }
225
265
 
@@ -234,24 +274,29 @@ export function listProviders(): OAuthProviderRow[] {
234
274
  * provider_key already exists.
235
275
  */
236
276
  export function registerProvider(params: {
237
- providerKey: string;
238
- authUrl: string;
239
- tokenUrl: string;
277
+ provider: string;
278
+ authorizeUrl: string;
279
+ tokenExchangeUrl: string;
280
+ refreshUrl?: string;
240
281
  tokenEndpointAuthMethod?: string;
241
282
  userinfoUrl?: string;
242
283
  pingUrl?: string;
243
284
  pingMethod?: string;
244
285
  pingHeaders?: Record<string, string>;
245
286
  pingBody?: unknown;
287
+ revokeUrl?: string;
288
+ revokeBodyTemplate?: Record<string, string>;
246
289
  baseUrl?: string;
247
290
  defaultScopes: string[];
248
291
  scopePolicy: Record<string, unknown>;
249
- extraParams?: Record<string, string>;
292
+ scopeSeparator?: string;
293
+ authorizeParams?: Record<string, string>;
250
294
  managedServiceConfigKey?: string;
251
- displayName?: string;
295
+ displayLabel?: string;
252
296
  description?: string;
253
297
  dashboardUrl?: string;
254
298
  clientIdPlaceholder?: string;
299
+ logoUrl?: string | null;
255
300
  requiresClientSecret?: number;
256
301
  loopbackPort?: number;
257
302
  injectionTemplates?: Array<{
@@ -274,31 +319,42 @@ export function registerProvider(params: {
274
319
  const db = getDb();
275
320
  const now = Date.now();
276
321
 
277
- const existing = getProvider(params.providerKey);
322
+ const existing = getProvider(params.provider);
278
323
  if (existing) {
279
- throw new Error(`OAuth provider already exists: ${params.providerKey}`);
324
+ throw new Error(`OAuth provider already exists: ${params.provider}`);
280
325
  }
281
326
 
282
327
  const row = {
283
- providerKey: params.providerKey,
284
- authUrl: params.authUrl,
285
- tokenUrl: params.tokenUrl,
286
- tokenEndpointAuthMethod: params.tokenEndpointAuthMethod ?? null,
328
+ provider: params.provider,
329
+ authorizeUrl: params.authorizeUrl,
330
+ tokenExchangeUrl: params.tokenExchangeUrl,
331
+ refreshUrl: params.refreshUrl ?? null,
332
+ tokenEndpointAuthMethod:
333
+ params.tokenEndpointAuthMethod || "client_secret_post",
287
334
  userinfoUrl: params.userinfoUrl ?? null,
288
335
  baseUrl: params.baseUrl ?? null,
289
336
  defaultScopes: JSON.stringify(params.defaultScopes),
290
337
  scopePolicy: JSON.stringify(params.scopePolicy),
291
- extraParams: params.extraParams ? JSON.stringify(params.extraParams) : null,
338
+ // Coerce empty string to the default space separator (see seedProviders).
339
+ scopeSeparator: params.scopeSeparator || " ",
340
+ authorizeParams: params.authorizeParams
341
+ ? JSON.stringify(params.authorizeParams)
342
+ : null,
292
343
  pingUrl: params.pingUrl ?? null,
293
344
  pingMethod: params.pingMethod ?? null,
294
345
  pingHeaders: params.pingHeaders ? JSON.stringify(params.pingHeaders) : null,
295
346
  pingBody:
296
347
  params.pingBody !== undefined ? JSON.stringify(params.pingBody) : null,
348
+ revokeUrl: params.revokeUrl ?? null,
349
+ revokeBodyTemplate: params.revokeBodyTemplate
350
+ ? JSON.stringify(params.revokeBodyTemplate)
351
+ : null,
297
352
  managedServiceConfigKey: params.managedServiceConfigKey ?? null,
298
- displayName: params.displayName ?? null,
353
+ displayLabel: params.displayLabel ?? null,
299
354
  description: params.description ?? null,
300
355
  dashboardUrl: params.dashboardUrl ?? null,
301
356
  clientIdPlaceholder: params.clientIdPlaceholder ?? null,
357
+ logoUrl: params.logoUrl ?? null,
302
358
  requiresClientSecret: params.requiresClientSecret ?? 1,
303
359
  loopbackPort: params.loopbackPort ?? null,
304
360
  injectionTemplates: params.injectionTemplates
@@ -333,31 +389,36 @@ export function registerProvider(params: {
333
389
  /**
334
390
  * Update mutable fields on an existing provider. Only the fields explicitly
335
391
  * provided (not `undefined`) are written; everything else is left unchanged.
336
- * JSON fields (defaultScopes, scopePolicy, extraParams, pingHeaders, pingBody)
392
+ * JSON fields (defaultScopes, scopePolicy, authorizeParams, pingHeaders, pingBody)
337
393
  * are serialized with JSON.stringify before storage.
338
394
  *
339
395
  * Returns the updated provider row, or `undefined` if no provider with the
340
396
  * given key exists.
341
397
  */
342
398
  export function updateProvider(
343
- providerKey: string,
399
+ provider: string,
344
400
  params: Partial<{
345
- authUrl: string;
346
- tokenUrl: string;
401
+ authorizeUrl: string;
402
+ tokenExchangeUrl: string;
403
+ refreshUrl: string;
347
404
  tokenEndpointAuthMethod: string;
348
405
  userinfoUrl: string;
349
406
  pingUrl: string;
350
407
  pingMethod: string;
351
408
  pingHeaders: Record<string, string>;
352
409
  pingBody: unknown;
410
+ revokeUrl: string | null;
411
+ revokeBodyTemplate: Record<string, string> | null;
353
412
  baseUrl: string;
354
413
  defaultScopes: string[];
355
414
  scopePolicy: Record<string, unknown>;
356
- extraParams: Record<string, string>;
357
- displayName: string;
415
+ scopeSeparator: string;
416
+ authorizeParams: Record<string, string>;
417
+ displayLabel: string;
358
418
  description: string;
359
419
  dashboardUrl: string;
360
420
  clientIdPlaceholder: string;
421
+ logoUrl: string | null;
361
422
  requiresClientSecret: boolean;
362
423
  loopbackPort: number;
363
424
  injectionTemplates: Array<{
@@ -378,16 +439,19 @@ export function updateProvider(
378
439
  featureFlag: string;
379
440
  }>,
380
441
  ): OAuthProviderRow | undefined {
381
- const existing = getProvider(providerKey);
442
+ const existing = getProvider(provider);
382
443
  if (!existing) return undefined;
383
444
 
384
445
  const db = getDb();
385
446
  const set: Record<string, unknown> = { updatedAt: Date.now() };
386
447
 
387
- if (params.authUrl !== undefined) set.authUrl = params.authUrl;
388
- if (params.tokenUrl !== undefined) set.tokenUrl = params.tokenUrl;
448
+ if (params.authorizeUrl !== undefined) set.authorizeUrl = params.authorizeUrl;
449
+ if (params.tokenExchangeUrl !== undefined)
450
+ set.tokenExchangeUrl = params.tokenExchangeUrl;
451
+ if (params.refreshUrl !== undefined) set.refreshUrl = params.refreshUrl;
389
452
  if (params.tokenEndpointAuthMethod !== undefined)
390
- set.tokenEndpointAuthMethod = params.tokenEndpointAuthMethod;
453
+ set.tokenEndpointAuthMethod =
454
+ params.tokenEndpointAuthMethod || "client_secret_post";
391
455
  if (params.userinfoUrl !== undefined) set.userinfoUrl = params.userinfoUrl;
392
456
  if (params.pingUrl !== undefined) set.pingUrl = params.pingUrl;
393
457
  if (params.pingMethod !== undefined) set.pingMethod = params.pingMethod;
@@ -395,18 +459,28 @@ export function updateProvider(
395
459
  set.pingHeaders = JSON.stringify(params.pingHeaders);
396
460
  if (params.pingBody !== undefined)
397
461
  set.pingBody = JSON.stringify(params.pingBody);
462
+ if (params.revokeUrl !== undefined) set.revokeUrl = params.revokeUrl;
463
+ if (params.revokeBodyTemplate !== undefined)
464
+ set.revokeBodyTemplate =
465
+ params.revokeBodyTemplate === null
466
+ ? null
467
+ : JSON.stringify(params.revokeBodyTemplate);
398
468
  if (params.baseUrl !== undefined) set.baseUrl = params.baseUrl;
399
469
  if (params.defaultScopes !== undefined)
400
470
  set.defaultScopes = JSON.stringify(params.defaultScopes);
401
471
  if (params.scopePolicy !== undefined)
402
472
  set.scopePolicy = JSON.stringify(params.scopePolicy);
403
- if (params.extraParams !== undefined)
404
- set.extraParams = JSON.stringify(params.extraParams);
405
- if (params.displayName !== undefined) set.displayName = params.displayName;
473
+ if (params.scopeSeparator !== undefined)
474
+ // Coerce empty string to the default space separator (see seedProviders).
475
+ set.scopeSeparator = params.scopeSeparator || " ";
476
+ if (params.authorizeParams !== undefined)
477
+ set.authorizeParams = JSON.stringify(params.authorizeParams);
478
+ if (params.displayLabel !== undefined) set.displayLabel = params.displayLabel;
406
479
  if (params.description !== undefined) set.description = params.description;
407
480
  if (params.dashboardUrl !== undefined) set.dashboardUrl = params.dashboardUrl;
408
481
  if (params.clientIdPlaceholder !== undefined)
409
482
  set.clientIdPlaceholder = params.clientIdPlaceholder;
483
+ if (params.logoUrl !== undefined) set.logoUrl = params.logoUrl;
410
484
  if (params.requiresClientSecret !== undefined)
411
485
  set.requiresClientSecret = params.requiresClientSecret ? 1 : 0;
412
486
  if (params.loopbackPort !== undefined) set.loopbackPort = params.loopbackPort;
@@ -432,10 +506,10 @@ export function updateProvider(
432
506
 
433
507
  db.update(oauthProviders)
434
508
  .set(set)
435
- .where(eq(oauthProviders.providerKey, providerKey))
509
+ .where(eq(oauthProviders.provider, provider))
436
510
  .run();
437
511
 
438
- return getProvider(providerKey);
512
+ return getProvider(provider);
439
513
  }
440
514
 
441
515
  /**
@@ -445,14 +519,12 @@ export function updateProvider(
445
519
  * Note: SQLite enforces the foreign-key constraint from `oauth_apps.provider_key`,
446
520
  * so deleting a provider that has existing apps will throw.
447
521
  */
448
- export function deleteProvider(providerKey: string): boolean {
449
- const existing = getProvider(providerKey);
522
+ export function deleteProvider(provider: string): boolean {
523
+ const existing = getProvider(provider);
450
524
  if (!existing) return false;
451
525
 
452
526
  const db = getDb();
453
- db.delete(oauthProviders)
454
- .where(eq(oauthProviders.providerKey, providerKey))
455
- .run();
527
+ db.delete(oauthProviders).where(eq(oauthProviders.provider, provider)).run();
456
528
  return rawChanges() > 0;
457
529
  }
458
530
 
@@ -465,7 +537,7 @@ export function deleteProvider(providerKey: string): boolean {
465
537
  * Generates a UUID on insert.
466
538
  */
467
539
  export async function upsertApp(
468
- providerKey: string,
540
+ provider: string,
469
541
  clientId: string,
470
542
  clientSecretOpts?: {
471
543
  clientSecretValue?: string;
@@ -499,10 +571,7 @@ export async function upsertApp(
499
571
  .select()
500
572
  .from(oauthApps)
501
573
  .where(
502
- and(
503
- eq(oauthApps.providerKey, providerKey),
504
- eq(oauthApps.clientId, clientId),
505
- ),
574
+ and(eq(oauthApps.provider, provider), eq(oauthApps.clientId, clientId)),
506
575
  )
507
576
  .get();
508
577
 
@@ -549,7 +618,7 @@ export async function upsertApp(
549
618
 
550
619
  const row = {
551
620
  id,
552
- providerKey,
621
+ provider,
553
622
  clientId,
554
623
  clientSecretCredentialPath: credPath,
555
624
  createdAt: now,
@@ -597,7 +666,7 @@ export async function getAppClientSecret(
597
666
 
598
667
  /** Look up an app by (provider_key, client_id). */
599
668
  export function getAppByProviderAndClientId(
600
- providerKey: string,
669
+ provider: string,
601
670
  clientId: string,
602
671
  ): OAuthAppRow | undefined {
603
672
  const db = getDb();
@@ -605,10 +674,7 @@ export function getAppByProviderAndClientId(
605
674
  .select()
606
675
  .from(oauthApps)
607
676
  .where(
608
- and(
609
- eq(oauthApps.providerKey, providerKey),
610
- eq(oauthApps.clientId, clientId),
611
- ),
677
+ and(eq(oauthApps.provider, provider), eq(oauthApps.clientId, clientId)),
612
678
  )
613
679
  .get();
614
680
  }
@@ -618,13 +684,13 @@ export function getAppByProviderAndClientId(
618
684
  * Returns undefined if no app exists for this provider.
619
685
  */
620
686
  export function getMostRecentAppByProvider(
621
- providerKey: string,
687
+ provider: string,
622
688
  ): OAuthAppRow | undefined {
623
689
  const db = getDb();
624
690
  return db
625
691
  .select()
626
692
  .from(oauthApps)
627
- .where(eq(oauthApps.providerKey, providerKey))
693
+ .where(eq(oauthApps.provider, provider))
628
694
  .orderBy(desc(oauthApps.createdAt))
629
695
  .limit(1)
630
696
  .get();
@@ -670,7 +736,7 @@ export async function deleteApp(id: string): Promise<boolean> {
670
736
  */
671
737
  export function createConnection(params: {
672
738
  oauthAppId: string;
673
- providerKey: string;
739
+ provider: string;
674
740
  accountInfo?: string;
675
741
  grantedScopes: string[];
676
742
  expiresAt?: number;
@@ -687,7 +753,7 @@ export function createConnection(params: {
687
753
  const row = {
688
754
  id,
689
755
  oauthAppId: params.oauthAppId,
690
- providerKey: params.providerKey,
756
+ provider: params.provider,
691
757
  accountInfo: params.accountInfo ?? null,
692
758
  grantedScopes: JSON.stringify(params.grantedScopes),
693
759
  expiresAt: params.expiresAt ?? null,
@@ -724,14 +790,14 @@ export function getConnection(id: string): OAuthConnectionRow | undefined {
724
790
  * Returns `undefined` when no matching active connection exists.
725
791
  */
726
792
  export function getActiveConnection(
727
- providerKey: string,
793
+ provider: string,
728
794
  options?: { clientId?: string; account?: string },
729
795
  ): OAuthConnectionRow | undefined {
730
796
  const { clientId, account } = options ?? {};
731
797
  const db = getDb();
732
798
 
733
799
  const conditions = [
734
- eq(oauthConnections.providerKey, providerKey),
800
+ eq(oauthConnections.provider, provider),
735
801
  eq(oauthConnections.status, "active"),
736
802
  ];
737
803
 
@@ -740,7 +806,7 @@ export function getActiveConnection(
740
806
  }
741
807
 
742
808
  if (clientId) {
743
- const app = getAppByProviderAndClientId(providerKey, clientId);
809
+ const app = getAppByProviderAndClientId(provider, clientId);
744
810
  if (!app) return undefined;
745
811
  conditions.push(eq(oauthConnections.oauthAppId, app.id));
746
812
  }
@@ -756,26 +822,26 @@ export function getActiveConnection(
756
822
 
757
823
  /** @deprecated Use {@link getActiveConnection} instead. */
758
824
  export function getConnectionByProvider(
759
- providerKey: string,
825
+ provider: string,
760
826
  clientId?: string,
761
827
  ): OAuthConnectionRow | undefined {
762
- return getActiveConnection(providerKey, { clientId });
828
+ return getActiveConnection(provider, { clientId });
763
829
  }
764
830
 
765
831
  /** @deprecated Use {@link getActiveConnection} instead. */
766
832
  export function getConnectionByProviderAndAccount(
767
- providerKey: string,
833
+ provider: string,
768
834
  accountInfo?: string,
769
835
  clientId?: string,
770
836
  ): OAuthConnectionRow | undefined {
771
- return getActiveConnection(providerKey, { clientId, account: accountInfo });
837
+ return getActiveConnection(provider, { clientId, account: accountInfo });
772
838
  }
773
839
 
774
840
  /**
775
841
  * Get ALL active connections for a provider (supports multi-account).
776
842
  */
777
843
  export function listActiveConnectionsByProvider(
778
- providerKey: string,
844
+ provider: string,
779
845
  ): OAuthConnectionRow[] {
780
846
  const db = getDb();
781
847
  return db
@@ -783,7 +849,7 @@ export function listActiveConnectionsByProvider(
783
849
  .from(oauthConnections)
784
850
  .where(
785
851
  and(
786
- eq(oauthConnections.providerKey, providerKey),
852
+ eq(oauthConnections.provider, provider),
787
853
  eq(oauthConnections.status, "active"),
788
854
  ),
789
855
  )
@@ -799,10 +865,8 @@ export function listActiveConnectionsByProvider(
799
865
  * but the secure-key write for the access token failed, which would make
800
866
  * `resolveOAuthConnection()` throw at usage time.
801
867
  */
802
- export async function isProviderConnected(
803
- providerKey: string,
804
- ): Promise<boolean> {
805
- const conn = getActiveConnection(providerKey);
868
+ export async function isProviderConnected(provider: string): Promise<boolean> {
869
+ const conn = getActiveConnection(provider);
806
870
  if (!conn || conn.status !== "active") return false;
807
871
  return (
808
872
  (await getSecureKeyAsync(oauthConnectionAccessTokenPath(conn.id))) !==
@@ -853,24 +917,24 @@ export function updateConnection(
853
917
 
854
918
  /** List connections, optionally filtered by provider key and/or client ID. */
855
919
  export function listConnections(
856
- providerKey?: string,
920
+ provider?: string,
857
921
  clientId?: string,
858
922
  ): OAuthConnectionRow[] {
859
923
  const db = getDb();
860
924
 
861
925
  let rows: OAuthConnectionRow[];
862
- if (providerKey) {
926
+ if (provider) {
863
927
  rows = db
864
928
  .select()
865
929
  .from(oauthConnections)
866
- .where(eq(oauthConnections.providerKey, providerKey))
867
- .orderBy(oauthConnections.providerKey, oauthConnections.id)
930
+ .where(eq(oauthConnections.provider, provider))
931
+ .orderBy(oauthConnections.provider, oauthConnections.id)
868
932
  .all();
869
933
  } else {
870
934
  rows = db
871
935
  .select()
872
936
  .from(oauthConnections)
873
- .orderBy(oauthConnections.providerKey, oauthConnections.id)
937
+ .orderBy(oauthConnections.provider, oauthConnections.id)
874
938
  .all();
875
939
  }
876
940
 
@@ -901,28 +965,78 @@ export function deleteConnection(id: string): boolean {
901
965
  // ---------------------------------------------------------------------------
902
966
 
903
967
  /**
904
- * Fully disconnect an OAuth provider: delete the new-format secure keys
905
- * (access_token and refresh_token) and remove the connection row from SQLite.
968
+ * Fully disconnect an OAuth provider:
969
+ * 1. Best-effort upstream token revoke when the provider has `revokeUrl`
970
+ * configured (mirrors platform's `try_revoke_token`).
971
+ * 2. Delete the new-format secure keys (access_token and refresh_token).
972
+ * 3. Remove the connection row from SQLite.
973
+ *
974
+ * The upstream revoke step is strictly best-effort: any failure (network
975
+ * error, non-2xx response, missing access token, etc.) is logged as a
976
+ * warning and the local cleanup proceeds anyway. The connection is always
977
+ * cleaned up locally regardless of whether the upstream call succeeds.
906
978
  *
907
979
  * When `connectionId` is provided, disconnects that specific connection
908
980
  * (useful for multi-account providers). Otherwise falls back to the most
909
981
  * recent active connection.
910
982
  *
911
- * Returns `"disconnected"` if a connection was found and cleaned up,
983
+ * Returns `"disconnected"` if a connection was found and locally cleaned up,
912
984
  * `"not-found"` if no active connection existed for the given provider,
913
985
  * or `"error"` if secure key deletion failed (connection row is preserved
914
986
  * to avoid orphaning secrets).
915
987
  */
916
988
  export async function disconnectOAuthProvider(
917
- providerKey: string,
989
+ provider: string,
918
990
  clientId?: string,
919
991
  connectionId?: string,
920
992
  ): Promise<"disconnected" | "not-found" | "error"> {
921
993
  const conn = connectionId
922
994
  ? getConnection(connectionId)
923
- : getActiveConnection(providerKey, { clientId });
995
+ : getActiveConnection(provider, { clientId });
924
996
  if (!conn) return "not-found";
925
997
 
998
+ // Best-effort upstream revoke. Mirrors platform's try_revoke_token in
999
+ // django/app/assistant/oauth/providers/base.py. Failures here never
1000
+ // block local cleanup — the connection is always cleaned up locally
1001
+ // regardless of whether the upstream call succeeds.
1002
+ try {
1003
+ const providerRow = getProvider(conn.provider);
1004
+ if (providerRow?.revokeUrl) {
1005
+ const app = getApp(conn.oauthAppId);
1006
+ const accessToken = await getSecureKeyAsync(
1007
+ oauthConnectionAccessTokenPath(conn.id),
1008
+ );
1009
+ if (app && accessToken) {
1010
+ const bodyTemplate = providerRow.revokeBodyTemplate
1011
+ ? (JSON.parse(providerRow.revokeBodyTemplate) as Record<
1012
+ string,
1013
+ unknown
1014
+ >)
1015
+ : null;
1016
+ await tryRevokeOAuthToken({
1017
+ provider: conn.provider,
1018
+ revokeUrl: providerRow.revokeUrl,
1019
+ bodyTemplate,
1020
+ accessToken,
1021
+ clientId: app.clientId,
1022
+ });
1023
+ }
1024
+ }
1025
+ } catch (err) {
1026
+ // tryRevokeOAuthToken already swallows fetch errors, but the lookups
1027
+ // (getProvider/getApp/getSecureKeyAsync/JSON.parse) could throw too.
1028
+ // Defense in depth: never let the local cleanup path die because of
1029
+ // anything in the revoke setup.
1030
+ log.warn(
1031
+ {
1032
+ provider: conn.provider,
1033
+ connectionId: conn.id,
1034
+ err: err instanceof Error ? err.message : String(err),
1035
+ },
1036
+ "Error preparing upstream OAuth revoke (best-effort, continuing local cleanup)",
1037
+ );
1038
+ }
1039
+
926
1040
  // Wrap the assistant's secure-key functions into the SecureKeyBackend
927
1041
  // interface expected by the shared deleteOAuthTokens helper.
928
1042
  const backend: SecureKeyBackend = {
@@ -945,7 +1059,7 @@ export async function disconnectOAuthProvider(
945
1059
  // way to surface the failure.
946
1060
  log.warn(
947
1061
  {
948
- providerKey,
1062
+ provider,
949
1063
  connectionId: conn.id,
950
1064
  accessTokenResult,
951
1065
  refreshTokenResult,