@vellumai/assistant 0.6.2 → 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 (396) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docs/architecture/memory.md +1 -1
  4. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  5. package/openapi.yaml +184 -69
  6. package/package.json +41 -41
  7. package/scripts/generate-openapi.ts +1 -2
  8. package/src/__tests__/acp-session.test.ts +43 -0
  9. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  10. package/src/__tests__/app-executors.test.ts +1 -0
  11. package/src/__tests__/app-source-watcher.test.ts +37 -11
  12. package/src/__tests__/approval-routes-http.test.ts +178 -1
  13. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  14. package/src/__tests__/browser-manager.test.ts +40 -27
  15. package/src/__tests__/catalog-files.test.ts +862 -0
  16. package/src/__tests__/channel-approvals.test.ts +53 -0
  17. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +125 -48
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  21. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  22. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  24. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  25. package/src/__tests__/conversation-attachments.test.ts +80 -4
  26. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  27. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  28. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  29. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  30. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  31. package/src/__tests__/conversation-queue.test.ts +45 -2
  32. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  33. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  35. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  36. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  37. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  38. package/src/__tests__/conversation-store.test.ts +195 -0
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  40. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  41. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  42. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  43. package/src/__tests__/credential-vault.test.ts +152 -13
  44. package/src/__tests__/credentials-cli.test.ts +2 -2
  45. package/src/__tests__/date-context.test.ts +4 -4
  46. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  47. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  48. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  49. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  50. package/src/__tests__/gemini-provider.test.ts +2 -2
  51. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  52. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  53. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  54. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  55. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  56. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  57. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  58. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  59. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  60. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  61. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  62. package/src/__tests__/host-browser-routes.test.ts +198 -0
  63. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  64. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  65. package/src/__tests__/host-file-proxy.test.ts +185 -1
  66. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  67. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  68. package/src/__tests__/host-shell-tool.test.ts +1 -11
  69. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  70. package/src/__tests__/integration-status.test.ts +6 -7
  71. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  72. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  73. package/src/__tests__/mcp-health-check.test.ts +10 -3
  74. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  75. package/src/__tests__/migration-export-http.test.ts +61 -2
  76. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  77. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  78. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  80. package/src/__tests__/oauth-cli.test.ts +707 -60
  81. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  82. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  83. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  84. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  85. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  86. package/src/__tests__/oauth-store.test.ts +1386 -182
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  88. package/src/__tests__/onboarding-template-contract.test.ts +75 -57
  89. package/src/__tests__/openai-provider.test.ts +2 -2
  90. package/src/__tests__/outlook-categories.test.ts +1 -1
  91. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  92. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  93. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  94. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  95. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  96. package/src/__tests__/outlook-trash.test.ts +1 -1
  97. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  98. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  99. package/src/__tests__/permission-mode.test.ts +28 -56
  100. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  101. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  102. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  103. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  104. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  105. package/src/__tests__/schedule-routes.test.ts +162 -0
  106. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  107. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  108. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  109. package/src/__tests__/set-permission-mode.test.ts +13 -250
  110. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  111. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  112. package/src/__tests__/slack-channel-config.test.ts +12 -15
  113. package/src/__tests__/subagent-detail.test.ts +44 -2
  114. package/src/__tests__/subagent-disposal.test.ts +1 -0
  115. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  116. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  117. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  118. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  119. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  120. package/src/__tests__/subagent-tools.test.ts +1 -0
  121. package/src/__tests__/subagent-types.test.ts +1 -0
  122. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  123. package/src/__tests__/system-prompt.test.ts +72 -1
  124. package/src/__tests__/task-scheduler.test.ts +32 -6
  125. package/src/__tests__/telegram-config.test.ts +10 -13
  126. package/src/__tests__/terminal-tools.test.ts +9 -0
  127. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  128. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  129. package/src/__tests__/top-level-renderer.test.ts +73 -1
  130. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  131. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  132. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  133. package/src/acp/client-handler.ts +30 -4
  134. package/src/agent/loop.ts +12 -6
  135. package/src/approvals/guardian-request-resolvers.ts +21 -15
  136. package/src/browser-session/__tests__/manager.test.ts +297 -0
  137. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  138. package/src/browser-session/backends/extension.ts +26 -0
  139. package/src/browser-session/backends/local.ts +24 -0
  140. package/src/browser-session/events.ts +164 -0
  141. package/src/browser-session/index.ts +27 -0
  142. package/src/browser-session/manager.ts +159 -0
  143. package/src/browser-session/types.ts +28 -0
  144. package/src/channels/__tests__/types.test.ts +134 -0
  145. package/src/channels/types.ts +53 -3
  146. package/src/cli/commands/browser-relay.ts +339 -409
  147. package/src/cli/commands/credentials.ts +3 -3
  148. package/src/cli/commands/email.ts +18 -13
  149. package/src/cli/commands/mcp.ts +16 -4
  150. package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
  151. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  152. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  153. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  154. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  155. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  156. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  157. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  158. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  159. package/src/cli/commands/oauth/apps.ts +7 -4
  160. package/src/cli/commands/oauth/connect.ts +6 -3
  161. package/src/cli/commands/oauth/disconnect.ts +1 -1
  162. package/src/cli/commands/oauth/providers.ts +200 -36
  163. package/src/cli/commands/oauth/shared.ts +5 -5
  164. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  165. package/src/cli/commands/platform/index.ts +107 -10
  166. package/src/cli/commands/usage.ts +10 -9
  167. package/src/cli/lib/daemon-credential-client.ts +4 -0
  168. package/src/cli/program.ts +1 -1
  169. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  170. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  171. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  172. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  173. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  174. package/src/config/bundled-skills/document/SKILL.md +4 -0
  175. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  176. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  177. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  178. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  179. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  180. package/src/config/env-registry.ts +14 -0
  181. package/src/config/env.ts +21 -0
  182. package/src/config/feature-flag-registry.json +44 -5
  183. package/src/config/loader.ts +56 -1
  184. package/src/config/sanitize-for-transfer.ts +47 -0
  185. package/src/config/schema.ts +46 -5
  186. package/src/config/schemas/host-browser.ts +66 -0
  187. package/src/config/schemas/memory-lifecycle.ts +1 -1
  188. package/src/config/schemas/memory-retrieval.ts +103 -0
  189. package/src/config/schemas/security.ts +0 -6
  190. package/src/config/schemas/services.ts +8 -0
  191. package/src/config/types.ts +0 -1
  192. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  193. package/src/context/window-manager.ts +19 -1
  194. package/src/credential-execution/approval-bridge.ts +49 -15
  195. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  196. package/src/daemon/app-source-watcher.ts +35 -0
  197. package/src/daemon/context-overflow-approval.ts +5 -0
  198. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  199. package/src/daemon/conversation-agent-loop.ts +58 -24
  200. package/src/daemon/conversation-attachments.ts +40 -0
  201. package/src/daemon/conversation-process.ts +48 -1
  202. package/src/daemon/conversation-runtime-assembly.ts +118 -36
  203. package/src/daemon/conversation-surfaces.ts +37 -36
  204. package/src/daemon/conversation-tool-setup.ts +74 -8
  205. package/src/daemon/conversation-workspace.ts +12 -0
  206. package/src/daemon/conversation.ts +226 -8
  207. package/src/daemon/date-context.ts +10 -10
  208. package/src/daemon/first-greeting.ts +3 -2
  209. package/src/daemon/handlers/conversations.ts +9 -140
  210. package/src/daemon/handlers/shared.ts +58 -0
  211. package/src/daemon/handlers/skills.ts +232 -37
  212. package/src/daemon/host-bash-proxy.ts +48 -13
  213. package/src/daemon/host-browser-proxy.ts +191 -0
  214. package/src/daemon/host-cu-proxy.ts +36 -11
  215. package/src/daemon/host-file-proxy.ts +57 -9
  216. package/src/daemon/lifecycle.ts +65 -11
  217. package/src/daemon/message-protocol.ts +7 -0
  218. package/src/daemon/message-types/conversations.ts +55 -13
  219. package/src/daemon/message-types/host-browser.ts +100 -0
  220. package/src/daemon/message-types/messages.ts +5 -5
  221. package/src/daemon/message-types/skills.ts +10 -0
  222. package/src/daemon/message-types/subagents.ts +2 -0
  223. package/src/daemon/server.ts +92 -12
  224. package/src/daemon/tool-side-effects.ts +6 -0
  225. package/src/daemon/transport-hints.ts +5 -24
  226. package/src/inbound/platform-callback-registration.ts +18 -17
  227. package/src/mcp/client.ts +59 -24
  228. package/src/memory/app-store.ts +31 -1
  229. package/src/memory/conversation-crud.ts +23 -0
  230. package/src/memory/conversation-starters-cadence.ts +76 -0
  231. package/src/memory/conversation-title-service.ts +5 -2
  232. package/src/memory/db-init.ts +12 -0
  233. package/src/memory/embedding-backend.test.ts +75 -0
  234. package/src/memory/embedding-backend.ts +131 -5
  235. package/src/memory/embedding-gemini.test.ts +54 -0
  236. package/src/memory/embedding-gemini.ts +20 -9
  237. package/src/memory/embedding-local.ts +176 -17
  238. package/src/memory/graph/consolidation.ts +10 -23
  239. package/src/memory/graph/extraction-job.ts +15 -0
  240. package/src/memory/graph/retriever.ts +40 -22
  241. package/src/memory/graph/store.test.ts +7 -3
  242. package/src/memory/graph/store.ts +47 -12
  243. package/src/memory/llm-usage-store.ts +45 -4
  244. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  245. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  246. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  247. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  248. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  249. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  250. package/src/memory/migrations/index.ts +6 -0
  251. package/src/memory/migrations/registry.ts +8 -0
  252. package/src/memory/schema/conversations.ts +1 -0
  253. package/src/memory/schema/oauth.ts +18 -13
  254. package/src/oauth/AGENTS.md +76 -0
  255. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  256. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  257. package/src/oauth/byo-connection.test.ts +8 -8
  258. package/src/oauth/byo-connection.ts +7 -7
  259. package/src/oauth/connect-orchestrator.ts +23 -21
  260. package/src/oauth/connect-types.ts +3 -3
  261. package/src/oauth/connection-resolver.test.ts +17 -4
  262. package/src/oauth/connection-resolver.ts +16 -16
  263. package/src/oauth/connection.ts +1 -1
  264. package/src/oauth/manual-token-connection.ts +13 -13
  265. package/src/oauth/oauth-store.ts +214 -100
  266. package/src/oauth/platform-connection.test.ts +3 -3
  267. package/src/oauth/platform-connection.ts +4 -4
  268. package/src/oauth/provider-serializer.ts +31 -5
  269. package/src/oauth/revoke.ts +76 -0
  270. package/src/oauth/seed-providers.ts +126 -87
  271. package/src/oauth/token-persistence.ts +1 -1
  272. package/src/permissions/permission-mode.ts +4 -11
  273. package/src/permissions/prompter.ts +13 -1
  274. package/src/permissions/v2-consent-policy.ts +87 -0
  275. package/src/prompts/system-prompt.ts +18 -21
  276. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  277. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  278. package/src/providers/anthropic/client.ts +1 -0
  279. package/src/providers/types.ts +1 -1
  280. package/src/runtime/AGENTS.md +23 -0
  281. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  282. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  283. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  284. package/src/runtime/assistant-event-hub.ts +2 -2
  285. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  286. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  287. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  288. package/src/runtime/auth/middleware.ts +98 -0
  289. package/src/runtime/auth/route-policy.ts +6 -7
  290. package/src/runtime/capability-tokens.ts +414 -0
  291. package/src/runtime/channel-approvals.ts +18 -5
  292. package/src/runtime/chrome-extension-registry.ts +332 -0
  293. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  294. package/src/runtime/guardian-decision-types.ts +7 -0
  295. package/src/runtime/http-server.ts +425 -70
  296. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  297. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  298. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  299. package/src/runtime/migrations/migration-transport.ts +6 -0
  300. package/src/runtime/migrations/migration-wizard.ts +22 -2
  301. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  302. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  303. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  304. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  305. package/src/runtime/pending-interactions.ts +29 -13
  306. package/src/runtime/routes/approval-routes.ts +90 -16
  307. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  308. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  309. package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
  310. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  311. package/src/runtime/routes/conversation-routes.ts +301 -27
  312. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  313. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  314. package/src/runtime/routes/host-browser-routes.ts +279 -0
  315. package/src/runtime/routes/host-file-routes.ts +9 -1
  316. package/src/runtime/routes/identity-routes.ts +259 -16
  317. package/src/runtime/routes/log-export-routes.ts +42 -22
  318. package/src/runtime/routes/memory-item-routes.ts +1 -7
  319. package/src/runtime/routes/migration-routes.ts +87 -2
  320. package/src/runtime/routes/oauth-apps.ts +15 -17
  321. package/src/runtime/routes/oauth-providers.ts +4 -0
  322. package/src/runtime/routes/schedule-routes.ts +24 -11
  323. package/src/runtime/routes/settings-routes.ts +9 -97
  324. package/src/runtime/routes/skills-routes.ts +52 -2
  325. package/src/runtime/routes/subagents-routes.ts +14 -10
  326. package/src/runtime/routes/usage-routes.ts +8 -7
  327. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  328. package/src/runtime/routes/workspace-routes.ts +8 -1
  329. package/src/runtime/routes/workspace-utils.ts +2 -0
  330. package/src/schedule/scheduler.ts +7 -5
  331. package/src/security/ces-credential-client.ts +20 -0
  332. package/src/security/ces-rpc-credential-backend.ts +17 -0
  333. package/src/security/credential-backend.ts +5 -0
  334. package/src/security/oauth2.ts +42 -25
  335. package/src/security/secure-keys.ts +118 -25
  336. package/src/security/token-manager.ts +23 -10
  337. package/src/skills/catalog-files.ts +492 -0
  338. package/src/subagent/manager.ts +131 -26
  339. package/src/subagent/types.ts +19 -0
  340. package/src/tools/apps/executors.ts +11 -2
  341. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  342. package/src/tools/browser/auth-detector.ts +43 -12
  343. package/src/tools/browser/browser-execution.ts +645 -340
  344. package/src/tools/browser/browser-manager.ts +36 -12
  345. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  346. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  347. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  348. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  349. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  350. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  351. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  352. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  353. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  354. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  355. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  356. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  357. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  358. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  359. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  360. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  361. package/src/tools/browser/cdp-client/errors.ts +34 -0
  362. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  363. package/src/tools/browser/cdp-client/factory.ts +204 -0
  364. package/src/tools/browser/cdp-client/index.ts +14 -0
  365. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  366. package/src/tools/browser/cdp-client/types.ts +52 -0
  367. package/src/tools/filesystem/edit.ts +1 -1
  368. package/src/tools/filesystem/list.ts +1 -1
  369. package/src/tools/filesystem/read.ts +1 -1
  370. package/src/tools/filesystem/write.ts +2 -1
  371. package/src/tools/host-filesystem/edit.ts +1 -1
  372. package/src/tools/host-filesystem/read.ts +12 -15
  373. package/src/tools/host-filesystem/write.ts +1 -1
  374. package/src/tools/host-terminal/host-shell.ts +21 -16
  375. package/src/tools/permission-checker.ts +77 -82
  376. package/src/tools/registry.ts +0 -2
  377. package/src/tools/secret-detection-handler.ts +34 -0
  378. package/src/tools/shared/filesystem/image-read.ts +61 -40
  379. package/src/tools/subagent/spawn.ts +47 -3
  380. package/src/tools/subagent/status.ts +2 -0
  381. package/src/tools/system/register.ts +2 -16
  382. package/src/tools/terminal/safe-env.ts +7 -0
  383. package/src/tools/terminal/shell.ts +21 -16
  384. package/src/tools/tool-approval-handler.ts +48 -2
  385. package/src/tools/types.ts +2 -0
  386. package/src/util/platform.ts +14 -19
  387. package/src/workspace/top-level-renderer.ts +19 -1
  388. package/src/__tests__/chrome-cdp.test.ts +0 -419
  389. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  390. package/src/__tests__/permission-mode-store.test.ts +0 -277
  391. package/src/browser-extension-relay/protocol.ts +0 -63
  392. package/src/browser-extension-relay/server.ts +0 -203
  393. package/src/config/schemas/sandbox.ts +0 -14
  394. package/src/permissions/permission-mode-store.ts +0 -180
  395. package/src/tools/browser/chrome-cdp.ts +0 -239
  396. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -2,10 +2,11 @@
2
2
  // Memory Graph — Data access layer
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
- import { and, desc, eq, inArray, sql } from "drizzle-orm";
5
+ import { and, desc, eq, inArray, or, sql } from "drizzle-orm";
6
6
  import { v4 as uuid } from "uuid";
7
7
 
8
8
  import { getDb } from "../db.js";
9
+ import { enqueueMemoryJob } from "../jobs-store.js";
9
10
  import {
10
11
  memoryGraphEdges,
11
12
  memoryGraphNodeEdits,
@@ -270,7 +271,14 @@ export function updateNode(
270
271
 
271
272
  export function deleteNode(id: string): void {
272
273
  const db = getDb();
273
- db.delete(memoryGraphNodes).where(eq(memoryGraphNodes.id, id)).run();
274
+ db.update(memoryGraphNodes)
275
+ .set({ fidelity: "gone", lastAccessed: Date.now() })
276
+ .where(eq(memoryGraphNodes.id, id))
277
+ .run();
278
+ enqueueMemoryJob("delete_qdrant_vectors", {
279
+ targetType: "graph_node",
280
+ targetId: id,
281
+ });
274
282
  }
275
283
 
276
284
  // ---------------------------------------------------------------------------
@@ -343,7 +351,7 @@ export function queryNodes(filters: NodeQueryFilters): MemoryNode[] {
343
351
  .where(conditions.length > 0 ? and(...conditions) : undefined)
344
352
  .orderBy(sql`${memoryGraphNodes.significance} DESC`);
345
353
 
346
- if (filters.limit) {
354
+ if (filters.limit != null) {
347
355
  query = query.limit(filters.limit) as typeof query;
348
356
  }
349
357
 
@@ -396,12 +404,19 @@ export function getEdgesForNode(
396
404
  direction?: "incoming" | "outgoing",
397
405
  ): MemoryEdge[] {
398
406
  const db = getDb();
399
- const condition =
407
+ const dirCondition =
400
408
  direction === "outgoing"
401
409
  ? eq(memoryGraphEdges.sourceNodeId, nodeId)
402
410
  : direction === "incoming"
403
411
  ? eq(memoryGraphEdges.targetNodeId, nodeId)
404
- : sql`${memoryGraphEdges.sourceNodeId} = ${nodeId} OR ${memoryGraphEdges.targetNodeId} = ${nodeId}`;
412
+ : or(eq(memoryGraphEdges.sourceNodeId, nodeId), eq(memoryGraphEdges.targetNodeId, nodeId));
413
+
414
+ // Exclude edges where either endpoint has fidelity='gone' (soft-deleted)
415
+ const condition = and(
416
+ dirCondition,
417
+ sql`NOT EXISTS (SELECT 1 FROM ${memoryGraphNodes} WHERE ${memoryGraphNodes.id} = ${memoryGraphEdges.sourceNodeId} AND ${memoryGraphNodes.fidelity} = 'gone')`,
418
+ sql`NOT EXISTS (SELECT 1 FROM ${memoryGraphNodes} WHERE ${memoryGraphNodes.id} = ${memoryGraphEdges.targetNodeId} AND ${memoryGraphNodes.fidelity} = 'gone')`,
419
+ );
405
420
 
406
421
  return db
407
422
  .select()
@@ -512,12 +527,23 @@ export function getActiveTriggersByType(
512
527
  return rows.map((r) => rowToTrigger(r.trigger));
513
528
  }
514
529
 
515
- return db
516
- .select()
530
+ const rows = db
531
+ .select({
532
+ trigger: memoryGraphTriggers,
533
+ })
517
534
  .from(memoryGraphTriggers)
518
- .where(and(...conditions))
519
- .all()
520
- .map(rowToTrigger);
535
+ .innerJoin(
536
+ memoryGraphNodes,
537
+ eq(memoryGraphTriggers.nodeId, memoryGraphNodes.id),
538
+ )
539
+ .where(
540
+ and(
541
+ ...conditions,
542
+ sql`${memoryGraphNodes.fidelity} != 'gone'`,
543
+ ),
544
+ )
545
+ .all();
546
+ return rows.map((r) => rowToTrigger(r.trigger));
521
547
  }
522
548
 
523
549
  // ---------------------------------------------------------------------------
@@ -614,9 +640,18 @@ export function applyDiff(
614
640
  };
615
641
 
616
642
  db.transaction((tx) => {
617
- // Delete nodes first (cascades edges + triggers)
643
+ // Soft-delete nodes (set fidelity='gone' and enqueue Qdrant cleanup)
618
644
  for (const id of diff.deleteNodeIds) {
619
- tx.delete(memoryGraphNodes).where(eq(memoryGraphNodes.id, id)).run();
645
+ tx.update(memoryGraphNodes)
646
+ .set({ fidelity: "gone", lastAccessed: Date.now() })
647
+ .where(eq(memoryGraphNodes.id, id))
648
+ .run();
649
+ enqueueMemoryJob(
650
+ "delete_qdrant_vectors",
651
+ { targetType: "graph_node", targetId: id },
652
+ Date.now(),
653
+ tx,
654
+ );
620
655
  result.nodesDeleted++;
621
656
  }
622
657
 
@@ -301,7 +301,7 @@ export function getUsageHourBuckets(range: UsageTimeRange): UsageDayBucket[] {
301
301
  }));
302
302
  }
303
303
 
304
- type GroupByDimension = "actor" | "provider" | "model";
304
+ type GroupByDimension = "actor" | "provider" | "model" | "conversation";
305
305
 
306
306
  /**
307
307
  * Return grouped breakdowns across the given time range, ordered by total
@@ -312,10 +312,51 @@ export function getUsageGroupBreakdown(
312
312
  groupBy: GroupByDimension,
313
313
  ): UsageGroupBreakdown[] {
314
314
  // Runtime allowlist — defense-in-depth against SQL injection via type assertions.
315
- const ALLOWED_COLUMNS = new Set<string>(["actor", "provider", "model"]);
316
- if (!ALLOWED_COLUMNS.has(groupBy)) {
317
- throw new Error(`Invalid groupBy column: ${groupBy}`);
315
+ const ALLOWED_DIMENSIONS = new Set<string>([
316
+ "actor",
317
+ "provider",
318
+ "model",
319
+ "conversation",
320
+ ]);
321
+ if (!ALLOWED_DIMENSIONS.has(groupBy)) {
322
+ throw new Error(`Invalid groupBy dimension: ${groupBy}`);
318
323
  }
324
+
325
+ // Conversation grouping requires a JOIN with conversations to resolve titles.
326
+ if (groupBy === "conversation") {
327
+ const rows = rawAll<GroupRow>(
328
+ /*sql*/ `
329
+ SELECT
330
+ CASE WHEN e.conversation_id IS NULL THEN 'Other'
331
+ ELSE COALESCE(c.title, 'Untitled')
332
+ END AS group_key,
333
+ COALESCE(SUM(e.input_tokens), 0) AS total_input_tokens,
334
+ COALESCE(SUM(e.output_tokens), 0) AS total_output_tokens,
335
+ COALESCE(SUM(e.cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
336
+ COALESCE(SUM(e.cache_read_input_tokens), 0) AS total_cache_read_tokens,
337
+ COALESCE(SUM(e.estimated_cost_usd), 0) AS total_estimated_cost_usd,
338
+ COALESCE(SUM(COALESCE(e.llm_call_count, 1)), 0) AS event_count
339
+ FROM llm_usage_events e
340
+ LEFT JOIN conversations c ON e.conversation_id = c.id
341
+ WHERE e.created_at >= ?1 AND e.created_at <= ?2
342
+ GROUP BY e.conversation_id
343
+ ORDER BY total_estimated_cost_usd DESC
344
+ LIMIT 50
345
+ `,
346
+ range.from,
347
+ range.to,
348
+ );
349
+ return rows.map((r) => ({
350
+ group: r.group_key,
351
+ totalInputTokens: r.total_input_tokens,
352
+ totalOutputTokens: r.total_output_tokens,
353
+ totalCacheCreationTokens: r.total_cache_creation_tokens,
354
+ totalCacheReadTokens: r.total_cache_read_tokens,
355
+ totalEstimatedCostUsd: r.total_estimated_cost_usd ?? 0,
356
+ eventCount: r.event_count,
357
+ }));
358
+ }
359
+
319
360
  const column = groupBy;
320
361
  const rows = rawAll<GroupRow>(
321
362
  /*sql*/ `
@@ -0,0 +1,13 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateOAuthProvidersScopeSeparator(database: DrizzleDb): void {
5
+ const raw = getSqliteFrom(database);
6
+ try {
7
+ raw.exec(
8
+ `ALTER TABLE oauth_providers ADD COLUMN scope_separator TEXT NOT NULL DEFAULT ' '`,
9
+ );
10
+ } catch {
11
+ // Column already exists — nothing to do.
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateOAuthProvidersRefreshUrl(database: DrizzleDb): void {
5
+ const raw = getSqliteFrom(database);
6
+ try {
7
+ raw.exec(`ALTER TABLE oauth_providers ADD COLUMN refresh_url TEXT`);
8
+ } catch {
9
+ // Column already exists — nothing to do.
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateOAuthProvidersRevoke(database: DrizzleDb): void {
5
+ const raw = getSqliteFrom(database);
6
+ const columns = ["revoke_url TEXT", "revoke_body_template TEXT"];
7
+ for (const col of columns) {
8
+ try {
9
+ raw.exec(`ALTER TABLE oauth_providers ADD COLUMN ${col}`);
10
+ } catch {
11
+ // Column already exists — nothing to do.
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,30 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Backfill `oauth_providers.token_endpoint_auth_method` for any rows where
6
+ * the value is NULL or empty string, setting them to the new default
7
+ * "client_secret_post". This brings existing rows in line with the
8
+ * Drizzle schema's new `.notNull().default("client_secret_post")`
9
+ * constraint, which is enforced at write time via the TypeScript layer.
10
+ *
11
+ * SQLite cannot retroactively add a NOT NULL constraint to an existing
12
+ * column without a full table rebuild, so the underlying column remains
13
+ * nullable at the SQLite level. All writes go through Drizzle, which
14
+ * applies the default for any insert that omits the field.
15
+ *
16
+ * The UPDATE is inherently idempotent and safe to re-run. Errors are
17
+ * allowed to propagate to the migration runner in `db-init.ts`, which
18
+ * records the failure, logs it, and continues to the next migration.
19
+ */
20
+ export function migrateOAuthProvidersTokenAuthMethodDefault(
21
+ database: DrizzleDb,
22
+ ): void {
23
+ const raw = getSqliteFrom(database);
24
+ raw.exec(
25
+ `UPDATE oauth_providers
26
+ SET token_endpoint_auth_method = 'client_secret_post'
27
+ WHERE token_endpoint_auth_method IS NULL
28
+ OR token_endpoint_auth_method = ''`,
29
+ );
30
+ }
@@ -0,0 +1,40 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+ import { withCrashRecovery } from "./validate-migration-state.js";
4
+
5
+ const CHECKPOINT_KEY = "migration_conversation_host_access_v1";
6
+
7
+ /**
8
+ * Add conversation-scoped host access state with a safe default of disabled.
9
+ *
10
+ * Idempotent: ALTER TABLE is guarded and the backfill only touches NULL rows.
11
+ */
12
+ export function migrateConversationHostAccess(database: DrizzleDb): void {
13
+ withCrashRecovery(database, CHECKPOINT_KEY, () => {
14
+ const raw = getSqliteFrom(database);
15
+
16
+ try {
17
+ raw.exec(
18
+ `ALTER TABLE conversations ADD COLUMN host_access INTEGER NOT NULL DEFAULT 0`,
19
+ );
20
+ } catch {
21
+ // Column already exists.
22
+ }
23
+
24
+ raw.exec(`
25
+ UPDATE conversations
26
+ SET host_access = 0
27
+ WHERE host_access IS NULL
28
+ `);
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Reverse: no-op.
34
+ *
35
+ * The forward migration is additive and SQLite cannot drop one column without
36
+ * rebuilding the table.
37
+ */
38
+ export function downConversationHostAccess(_database: DrizzleDb): void {
39
+ // Intentionally empty.
40
+ }
@@ -0,0 +1,11 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateOAuthProvidersLogoUrl(database: DrizzleDb): void {
5
+ const raw = getSqliteFrom(database);
6
+ try {
7
+ raw.exec(`ALTER TABLE oauth_providers ADD COLUMN logo_url TEXT`);
8
+ } catch {
9
+ // Column already exists — nothing to do.
10
+ }
11
+ }
@@ -154,6 +154,12 @@ export { migrateStripThinkingFromConsolidated } from "./209-strip-thinking-from-
154
154
  export { migrateScheduleReuseConversation } from "./210-schedule-reuse-conversation.js";
155
155
  export { migrateMemoryRecallLogsQueryContext } from "./211-memory-recall-logs-query-context.js";
156
156
  export { migrateLlmRequestLogsCreatedAtIndex } from "./212-llm-request-logs-created-at-index.js";
157
+ export { migrateOAuthProvidersScopeSeparator } from "./213-oauth-providers-scope-separator.js";
158
+ export { migrateOAuthProvidersRefreshUrl } from "./214-oauth-providers-refresh-url.js";
159
+ export { migrateOAuthProvidersRevoke } from "./215-oauth-providers-revoke.js";
160
+ export { migrateOAuthProvidersTokenAuthMethodDefault } from "./216-oauth-providers-token-auth-method.js";
161
+ export { migrateConversationHostAccess } from "./217-conversation-host-access.js";
162
+ export { migrateOAuthProvidersLogoUrl } from "./218-oauth-providers-logo-url.js";
157
163
  export {
158
164
  MIGRATION_REGISTRY,
159
165
  type MigrationRegistryEntry,
@@ -41,6 +41,7 @@ import { migrateAddSourceTypeColumnsDown } from "./193-add-source-type-columns.j
41
41
  import { migrateStripIntegrationPrefixFromProviderKeysDown } from "./196-strip-integration-prefix-from-provider-keys.js";
42
42
  import { migrateRenameMemoryGraphTypeValuesDown } from "./204-rename-memory-graph-type-values.js";
43
43
  import { migrateScrubCorruptedImageAttachmentsDown } from "./206-scrub-corrupted-image-attachments.js";
44
+ import { downConversationHostAccess } from "./217-conversation-host-access.js";
44
45
 
45
46
  export interface MigrationRegistryEntry {
46
47
  /** The checkpoint key written to memory_checkpoints on completion. */
@@ -357,6 +358,13 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
357
358
  "Remove image attachments containing HTML error pages instead of image data",
358
359
  down: migrateScrubCorruptedImageAttachmentsDown,
359
360
  },
361
+ {
362
+ key: "migration_conversation_host_access_v1",
363
+ version: 41,
364
+ description:
365
+ "Add a host_access column to conversations so computer access is persisted per conversation with a safe default of disabled",
366
+ down: downConversationHostAccess,
367
+ },
360
368
  ];
361
369
 
362
370
  export function getMaxMigrationVersion(): number {
@@ -28,6 +28,7 @@ export const conversations = sqliteTable(
28
28
  originInterface: text("origin_interface"),
29
29
  forkParentConversationId: text("fork_parent_conversation_id"),
30
30
  forkParentMessageId: text("fork_parent_message_id"),
31
+ hostAccess: integer("host_access").notNull().default(0),
31
32
  isAutoTitle: integer("is_auto_title").notNull().default(1),
32
33
  scheduleJobId: text("schedule_job_id"),
33
34
  lastMessageAt: integer("last_message_at"),
@@ -7,24 +7,31 @@ import {
7
7
  } from "drizzle-orm/sqlite-core";
8
8
 
9
9
  export const oauthProviders = sqliteTable("oauth_providers", {
10
- providerKey: text("provider_key").primaryKey(),
11
- authUrl: text("auth_url").notNull(),
12
- tokenUrl: text("token_url").notNull(),
13
- tokenEndpointAuthMethod: text("token_endpoint_auth_method"),
10
+ provider: text("provider_key").primaryKey(),
11
+ authorizeUrl: text("auth_url").notNull(),
12
+ tokenExchangeUrl: text("token_url").notNull(),
13
+ refreshUrl: text("refresh_url"),
14
+ tokenEndpointAuthMethod: text("token_endpoint_auth_method")
15
+ .notNull()
16
+ .default("client_secret_post"),
14
17
  userinfoUrl: text("userinfo_url"),
15
18
  baseUrl: text("base_url"),
16
19
  defaultScopes: text("default_scopes").notNull().default("[]"),
17
20
  scopePolicy: text("scope_policy").notNull().default("{}"),
18
- extraParams: text("extra_params"),
21
+ scopeSeparator: text("scope_separator").notNull().default(" "),
22
+ authorizeParams: text("extra_params"),
19
23
  pingUrl: text("ping_url"),
20
24
  pingMethod: text("ping_method"),
21
25
  pingHeaders: text("ping_headers"),
22
26
  pingBody: text("ping_body"),
27
+ revokeUrl: text("revoke_url"),
28
+ revokeBodyTemplate: text("revoke_body_template"),
23
29
  managedServiceConfigKey: text("managed_service_config_key"),
24
- displayName: text("display_name"),
30
+ displayLabel: text("display_name"),
25
31
  description: text("description"),
26
32
  dashboardUrl: text("dashboard_url"),
27
33
  clientIdPlaceholder: text("client_id_placeholder"),
34
+ logoUrl: text("logo_url"),
28
35
  requiresClientSecret: integer("requires_client_secret").notNull().default(1),
29
36
  loopbackPort: integer("loopback_port"),
30
37
  injectionTemplates: text("injection_templates"),
@@ -46,9 +53,9 @@ export const oauthApps = sqliteTable(
46
53
  "oauth_apps",
47
54
  {
48
55
  id: text("id").primaryKey(),
49
- providerKey: text("provider_key")
56
+ provider: text("provider_key")
50
57
  .notNull()
51
- .references(() => oauthProviders.providerKey),
58
+ .references(() => oauthProviders.provider),
52
59
  clientId: text("client_id").notNull(),
53
60
  clientSecretCredentialPath: text("client_secret_credential_path").notNull(),
54
61
  createdAt: integer("created_at").notNull(),
@@ -56,7 +63,7 @@ export const oauthApps = sqliteTable(
56
63
  },
57
64
  (table) => [
58
65
  uniqueIndex("idx_oauth_apps_provider_client").on(
59
- table.providerKey,
66
+ table.provider,
60
67
  table.clientId,
61
68
  ),
62
69
  ],
@@ -69,7 +76,7 @@ export const oauthConnections = sqliteTable(
69
76
  oauthAppId: text("oauth_app_id")
70
77
  .notNull()
71
78
  .references(() => oauthApps.id),
72
- providerKey: text("provider_key").notNull(),
79
+ provider: text("provider_key").notNull(),
73
80
  accountInfo: text("account_info"),
74
81
  grantedScopes: text("granted_scopes").notNull().default("[]"),
75
82
  expiresAt: integer("expires_at"),
@@ -80,7 +87,5 @@ export const oauthConnections = sqliteTable(
80
87
  createdAt: integer("created_at").notNull(),
81
88
  updatedAt: integer("updated_at").notNull(),
82
89
  },
83
- (table) => [
84
- index("idx_oauth_connections_provider_key").on(table.providerKey),
85
- ],
90
+ (table) => [index("idx_oauth_connections_provider_key").on(table.provider)],
86
91
  );
@@ -0,0 +1,76 @@
1
+ # OAuth — Agent Instructions
2
+
3
+ ## Adding a New First-Class Provider
4
+
5
+ When introducing a new built-in OAuth integration (one that appears in `seed-providers.ts`), touch each of the following areas. Items marked _(managed only)_ apply only when the provider supports platform-provided credentials.
6
+
7
+ ### 1. Seed the provider — `seed-providers.ts`
8
+
9
+ Add an entry to `PROVIDER_SEED_DATA`. Required fields: `provider`, `authorizeUrl`, `tokenExchangeUrl`, `defaultScopes`, `scopePolicy`, `displayLabel`, `description`, `dashboardUrl`, `clientIdPlaceholder`, `logoUrl`, and `injectionTemplates`. See existing entries for the full shape. The `provider` key must be snake_case and is used as the canonical identifier everywhere else.
10
+
11
+ If the provider will support managed mode, set `managedServiceConfigKey` to a slug matching the key you will add to `ServicesSchema` (e.g. `"acme-oauth"`).
12
+
13
+ ### 2. _(managed only)_ Add a service schema — `../config/schemas/services.ts`
14
+
15
+ Create a schema and export its type:
16
+
17
+ ```ts
18
+ export const AcmeOAuthServiceSchema = BaseServiceSchema.extend({
19
+ mode: ServiceModeSchema.default("your-own"),
20
+ });
21
+ export type AcmeOAuthService = z.infer<typeof AcmeOAuthServiceSchema>;
22
+ ```
23
+
24
+ Then add the key to `ServicesSchema`:
25
+
26
+ ```ts
27
+ "acme-oauth": AcmeOAuthServiceSchema.default(AcmeOAuthServiceSchema.parse({})),
28
+ ```
29
+
30
+ The key here **must** match the `managedServiceConfigKey` in `seed-providers.ts`. The cross-repo invariant test in `__tests__/seed-providers-managed.test.ts` will fail if they drift.
31
+
32
+ ### 3. _(managed only)_ Enable by default during onboarding — `clients/macos/.../HatchingStepView.swift`
33
+
34
+ In `buildOnboardingConfigValues()`, add a line so managed-sign-in users get the integration pre-enabled:
35
+
36
+ ```swift
37
+ configValues["services.acme-oauth.mode"] = "managed"
38
+ ```
39
+
40
+ ### 4. Add a cached logo — `clients/shared/Resources/IntegrationLogos/`
41
+
42
+ Drop a **vector PDF** named `{provider_key}.pdf` (e.g. `acme.pdf`) into the `IntegrationLogos/` directory. The file is automatically bundled via `.copy()` in `clients/Package.swift` and looked up at runtime by `IntegrationLogoBundle` using the provider key — no code changes needed.
43
+
44
+ Most existing logos come from [Simple Icons](https://simpleicons.org) (CC0-licensed). To get a PDF from Simple Icons:
45
+
46
+ 1. Find the icon slug on https://simpleicons.org (e.g. `slack`, `linear`).
47
+ 2. Download the SVG: `curl -o acme.svg https://raw.githubusercontent.com/simple-icons/simple-icons/develop/icons/acme.svg`
48
+ 3. Convert to PDF using `rsvg-convert` (same tool used by `clients/scripts/sync-lucide-icons.sh`):
49
+ ```bash
50
+ # Install if needed: brew install librsvg
51
+ rsvg-convert -f pdf -o clients/shared/Resources/IntegrationLogos/acme.pdf acme.svg
52
+ ```
53
+ 4. Add the provider key to `clients/shared/Resources/integration-logos-manifest.json`.
54
+
55
+ If the service is not on Simple Icons, source or create an SVG and convert it the same way. The result must be a true vector PDF (not a rasterized image wrapped in PDF) so it scales cleanly.
56
+
57
+ The `logoUrl` field in `seed-providers.ts` still serves as the remote fallback (typically a Simple Icons CDN URL like `https://cdn.simpleicons.org/acme`). The client renders the local PDF first, then falls back to `logoUrl`, then to an initials avatar.
58
+
59
+ ### 5. Secret patterns (if applicable) — `../security/secret-patterns.ts`
60
+
61
+ If the provider issues API keys with a recognizable prefix (e.g. `acme_sk_`), add a `PREFIX_PATTERNS` entry. OAuth-only services with opaque access tokens do not need one. See `../security/AGENTS.md` for details.
62
+
63
+ ### 6. Feature-flag gating (optional) — `seed-providers.ts`
64
+
65
+ Set `featureFlag: "acme-oauth"` in the seed entry and register the flag in `meta/feature-flags/feature-flag-registry.json` to hide the provider until the flag is enabled. Omit `featureFlag` to make the provider visible immediately.
66
+
67
+ ### What you do NOT need to change
68
+
69
+ The following are wired automatically once `PROVIDER_SEED_DATA` has an entry:
70
+
71
+ - **Connection resolver** (`connection-resolver.ts`) — routes managed vs. BYO based on config.
72
+ - **CLI commands** (`../cli/commands/oauth/`) — `providers list`, `providers get`, `connect`, `disconnect`, etc.
73
+ - **Runtime API** (`../runtime/routes/oauth-providers.ts`) — `GET /v1/oauth/providers` and related endpoints.
74
+ - **Gateway proxy** (`gateway/src/http/routes/oauth-providers-proxy.ts`) — forwards to the runtime.
75
+ - **OAuth store** (`oauth-store.ts`) — seeding uses upsert; schema already supports arbitrary providers.
76
+ - **Provider serialization** (`provider-serializer.ts`) — generic over all providers.
@@ -24,29 +24,34 @@ afterEach(() => {
24
24
  // ---------------------------------------------------------------------------
25
25
 
26
26
  function makeProviderRow(
27
- overrides: Partial<OAuthProviderRow> & { providerKey: string },
27
+ overrides: Partial<OAuthProviderRow> & { provider: string },
28
28
  ): OAuthProviderRow {
29
29
  const now = Date.now();
30
- const { providerKey, ...rest } = overrides;
30
+ const { provider, ...rest } = overrides;
31
31
  return {
32
- providerKey,
33
- authUrl: "https://example.com/auth",
34
- tokenUrl: "https://example.com/token",
35
- tokenEndpointAuthMethod: null,
32
+ provider,
33
+ authorizeUrl: "https://example.com/auth",
34
+ tokenExchangeUrl: "https://example.com/token",
35
+ refreshUrl: null,
36
+ tokenEndpointAuthMethod: "client_secret_post",
36
37
  userinfoUrl: null,
37
38
  baseUrl: null,
38
39
  defaultScopes: "[]",
39
40
  scopePolicy: "{}",
40
- extraParams: null,
41
+ scopeSeparator: " ",
42
+ authorizeParams: null,
41
43
  pingUrl: null,
42
44
  pingMethod: null,
43
45
  pingHeaders: null,
44
46
  pingBody: null,
47
+ revokeUrl: null,
48
+ revokeBodyTemplate: null,
45
49
  managedServiceConfigKey: null,
46
- displayName: null,
50
+ displayLabel: null,
47
51
  description: null,
48
52
  dashboardUrl: null,
49
53
  clientIdPlaceholder: null,
54
+ logoUrl: null,
50
55
  requiresClientSecret: 1,
51
56
  loopbackPort: null,
52
57
  injectionTemplates: null,
@@ -82,7 +87,7 @@ describe("verifyIdentity", () => {
82
87
  // Missing identity URL
83
88
  // -----------------------------------------------------------------------
84
89
  test("returns undefined when identityUrl is null", async () => {
85
- const row = makeProviderRow({ providerKey: "custom" });
90
+ const row = makeProviderRow({ provider: "custom" });
86
91
  const result = await verifyIdentity(row, "token-abc");
87
92
  expect(result).toBeUndefined();
88
93
  expect(mockFetch).not.toHaveBeenCalled();
@@ -93,7 +98,7 @@ describe("verifyIdentity", () => {
93
98
  // -----------------------------------------------------------------------
94
99
  describe("Google pattern", () => {
95
100
  const googleRow = makeProviderRow({
96
- providerKey: "google",
101
+ provider: "google",
97
102
  identityUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
98
103
  identityResponsePaths: JSON.stringify(["email"]),
99
104
  });
@@ -127,7 +132,7 @@ describe("verifyIdentity", () => {
127
132
  // -----------------------------------------------------------------------
128
133
  describe("Slack pattern", () => {
129
134
  const slackRow = makeProviderRow({
130
- providerKey: "slack",
135
+ provider: "slack",
131
136
  identityUrl: "https://slack.com/api/auth.test",
132
137
  identityOkField: "ok",
133
138
  identityResponsePaths: JSON.stringify(["user", "team"]),
@@ -176,7 +181,7 @@ describe("verifyIdentity", () => {
176
181
  // -----------------------------------------------------------------------
177
182
  describe("HubSpot pattern", () => {
178
183
  const hubspotRow = makeProviderRow({
179
- providerKey: "hubspot",
184
+ provider: "hubspot",
180
185
  identityUrl:
181
186
  "https://api.hubapi.com/oauth/v1/access-tokens/${accessToken}",
182
187
  identityResponsePaths: JSON.stringify(["user", "hub_domain"]),
@@ -219,7 +224,7 @@ describe("verifyIdentity", () => {
219
224
  // -----------------------------------------------------------------------
220
225
  describe("Linear pattern", () => {
221
226
  const linearRow = makeProviderRow({
222
- providerKey: "linear",
227
+ provider: "linear",
223
228
  identityUrl: "https://api.linear.app/graphql",
224
229
  identityMethod: "POST",
225
230
  identityHeaders: JSON.stringify({ "Content-Type": "application/json" }),
@@ -272,7 +277,7 @@ describe("verifyIdentity", () => {
272
277
  // -----------------------------------------------------------------------
273
278
  describe("Todoist pattern", () => {
274
279
  const todoistRow = makeProviderRow({
275
- providerKey: "todoist",
280
+ provider: "todoist",
276
281
  identityUrl: "https://api.todoist.com/sync/v9/sync",
277
282
  identityMethod: "POST",
278
283
  identityHeaders: JSON.stringify({
@@ -317,7 +322,7 @@ describe("verifyIdentity", () => {
317
322
  // -----------------------------------------------------------------------
318
323
  describe("Twitter pattern", () => {
319
324
  const twitterRow = makeProviderRow({
320
- providerKey: "twitter",
325
+ provider: "twitter",
321
326
  identityUrl: "https://api.x.com/2/users/me",
322
327
  identityResponsePaths: JSON.stringify(["data.username"]),
323
328
  identityFormat: "@${data.username}",
@@ -338,7 +343,7 @@ describe("verifyIdentity", () => {
338
343
  // -----------------------------------------------------------------------
339
344
  describe("GitHub pattern", () => {
340
345
  const githubRow = makeProviderRow({
341
- providerKey: "github",
346
+ provider: "github",
342
347
  identityUrl: "https://api.github.com/user",
343
348
  identityResponsePaths: JSON.stringify(["login"]),
344
349
  identityFormat: "@${login}",
@@ -357,7 +362,7 @@ describe("verifyIdentity", () => {
357
362
  // -----------------------------------------------------------------------
358
363
  describe("Notion pattern", () => {
359
364
  const notionRow = makeProviderRow({
360
- providerKey: "notion",
365
+ provider: "notion",
361
366
  identityUrl: "https://api.notion.com/v1/users/me",
362
367
  identityHeaders: JSON.stringify({ "Notion-Version": "2022-06-28" }),
363
368
  identityResponsePaths: JSON.stringify(["name", "person.email"]),
@@ -392,7 +397,7 @@ describe("verifyIdentity", () => {
392
397
  // -----------------------------------------------------------------------
393
398
  describe("error handling", () => {
394
399
  const googleRow = makeProviderRow({
395
- providerKey: "google",
400
+ provider: "google",
396
401
  identityUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
397
402
  identityResponsePaths: JSON.stringify(["email"]),
398
403
  });
@@ -431,7 +436,7 @@ describe("verifyIdentity", () => {
431
436
  // -----------------------------------------------------------------------
432
437
  describe("Dropbox pattern", () => {
433
438
  const dropboxRow = makeProviderRow({
434
- providerKey: "dropbox",
439
+ provider: "dropbox",
435
440
  identityUrl: "https://api.dropboxapi.com/2/users/get_current_account",
436
441
  identityMethod: "POST",
437
442
  identityResponsePaths: JSON.stringify(["name.display_name", "email"]),