@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
@@ -26,6 +26,11 @@ const disconnectOAuthProviderCalls: string[] = [];
26
26
  const disconnectOAuthProviderResult: "disconnected" | "not-found" | "error" =
27
27
  "not-found";
28
28
 
29
+ // In-memory provider store used by registerProvider/updateProvider/getProvider
30
+ // mocks below. Tests that exercise the providers register/update/get commands
31
+ // can read and write through this map directly.
32
+ const mockProviderStore = new Map<string, Record<string, unknown>>();
33
+
29
34
  // App upsert mock state
30
35
  let mockUpsertAppCalls: Array<{
31
36
  provider: string;
@@ -37,7 +42,7 @@ let mockUpsertAppCalls: Array<{
37
42
  }> = [];
38
43
  let mockUpsertAppResult: Record<string, unknown> = {
39
44
  id: "app-upsert-1",
40
- providerKey: "test",
45
+ provider: "test",
41
46
  clientId: "test-client-id",
42
47
  createdAt: 1700000000000,
43
48
  updatedAt: 1700000000000,
@@ -58,18 +63,18 @@ let mockOrchestrateOAuthConnect: (
58
63
  opts: Record<string, unknown>,
59
64
  ) => Promise<Record<string, unknown>>;
60
65
  let mockGetAppByProviderAndClientId: (
61
- providerKey: string,
66
+ provider: string,
62
67
  clientId: string,
63
68
  ) => Record<string, unknown> | undefined = () => undefined;
64
69
  let mockGetMostRecentAppByProvider: (
65
- providerKey: string,
70
+ provider: string,
66
71
  ) => Record<string, unknown> | undefined = () => undefined;
67
72
  let mockGetProvider: (
68
- providerKey: string,
73
+ provider: string,
69
74
  ) => Record<string, unknown> | undefined = () => undefined;
70
75
  let mockGetSecureKey: (account: string) => string | undefined = () => undefined;
71
76
  let mockResolveOAuthConnection: (
72
- providerKey: string,
77
+ provider: string,
73
78
  options?: Record<string, unknown>,
74
79
  ) => Promise<{
75
80
  request: (req: Record<string, unknown>) => Promise<{
@@ -79,7 +84,7 @@ let mockResolveOAuthConnection: (
79
84
  }>;
80
85
  withToken: <T>(fn: (token: string) => Promise<T>) => Promise<T>;
81
86
  id: string;
82
- providerKey: string;
87
+ provider: string;
83
88
  accountInfo: string | null;
84
89
  }> = async () => {
85
90
  throw new Error("resolveOAuthConnection not configured in test");
@@ -120,9 +125,9 @@ mock.module("../security/token-manager.js", () => ({
120
125
 
121
126
  mock.module("../oauth/oauth-store.js", () => ({
122
127
  disconnectOAuthProvider: async (
123
- providerKey: string,
128
+ provider: string,
124
129
  ): Promise<"disconnected" | "not-found" | "error"> => {
125
- disconnectOAuthProviderCalls.push(providerKey);
130
+ disconnectOAuthProviderCalls.push(provider);
126
131
  return disconnectOAuthProviderResult;
127
132
  },
128
133
  getConnection: () => undefined,
@@ -145,16 +150,120 @@ mock.module("../oauth/oauth-store.js", () => ({
145
150
  return mockUpsertAppResult;
146
151
  },
147
152
  getApp: () => undefined,
148
- getAppByProviderAndClientId: (providerKey: string, clientId: string) =>
149
- mockGetAppByProviderAndClientId(providerKey, clientId),
150
- getMostRecentAppByProvider: (providerKey: string) =>
151
- mockGetMostRecentAppByProvider(providerKey),
153
+ getAppByProviderAndClientId: (provider: string, clientId: string) =>
154
+ mockGetAppByProviderAndClientId(provider, clientId),
155
+ getMostRecentAppByProvider: (provider: string) =>
156
+ mockGetMostRecentAppByProvider(provider),
152
157
  listApps: () => [],
153
158
  deleteApp: async () => false,
154
- getProvider: (providerKey: string) => mockGetProvider(providerKey),
159
+ getProvider: (provider: string) => {
160
+ // If the test has plugged in a custom mockGetProvider, prefer that.
161
+ const custom = mockGetProvider(provider);
162
+ if (custom !== undefined) return custom;
163
+ return mockProviderStore.get(provider);
164
+ },
155
165
  listProviders: () => mockListProviders(),
156
- registerProvider: () => ({}),
157
- updateProvider: () => undefined,
166
+ registerProvider: (params: Record<string, unknown>) => {
167
+ const now = Date.now();
168
+ const row: Record<string, unknown> = {
169
+ provider: params.provider,
170
+ authorizeUrl: params.authorizeUrl,
171
+ tokenExchangeUrl: params.tokenExchangeUrl,
172
+ refreshUrl: (params.refreshUrl as string | undefined) ?? null,
173
+ tokenEndpointAuthMethod:
174
+ (params.tokenEndpointAuthMethod as string | undefined) ||
175
+ "client_secret_post",
176
+ userinfoUrl: params.userinfoUrl ?? null,
177
+ baseUrl: params.baseUrl ?? null,
178
+ defaultScopes: JSON.stringify(params.defaultScopes ?? []),
179
+ scopePolicy: JSON.stringify(params.scopePolicy ?? {}),
180
+ scopeSeparator: (params.scopeSeparator as string | undefined) ?? " ",
181
+ authorizeParams: params.authorizeParams
182
+ ? JSON.stringify(params.authorizeParams)
183
+ : null,
184
+ pingUrl: params.pingUrl ?? null,
185
+ pingMethod: params.pingMethod ?? null,
186
+ pingHeaders: params.pingHeaders
187
+ ? JSON.stringify(params.pingHeaders)
188
+ : null,
189
+ pingBody:
190
+ params.pingBody !== undefined ? JSON.stringify(params.pingBody) : null,
191
+ revokeUrl: (params.revokeUrl as string | undefined) ?? null,
192
+ revokeBodyTemplate: params.revokeBodyTemplate
193
+ ? JSON.stringify(params.revokeBodyTemplate)
194
+ : null,
195
+ managedServiceConfigKey: params.managedServiceConfigKey ?? null,
196
+ displayLabel: params.displayLabel ?? null,
197
+ description: params.description ?? null,
198
+ dashboardUrl: params.dashboardUrl ?? null,
199
+ logoUrl: params.logoUrl ?? null,
200
+ clientIdPlaceholder: params.clientIdPlaceholder ?? null,
201
+ requiresClientSecret: params.requiresClientSecret ?? 1,
202
+ loopbackPort: params.loopbackPort ?? null,
203
+ injectionTemplates: params.injectionTemplates
204
+ ? JSON.stringify(params.injectionTemplates)
205
+ : null,
206
+ appType: params.appType ?? null,
207
+ setupNotes: params.setupNotes ? JSON.stringify(params.setupNotes) : null,
208
+ identityUrl: params.identityUrl ?? null,
209
+ identityMethod: params.identityMethod ?? null,
210
+ identityHeaders: params.identityHeaders
211
+ ? JSON.stringify(params.identityHeaders)
212
+ : null,
213
+ identityBody:
214
+ params.identityBody !== undefined
215
+ ? JSON.stringify(params.identityBody)
216
+ : null,
217
+ identityResponsePaths: params.identityResponsePaths
218
+ ? JSON.stringify(params.identityResponsePaths)
219
+ : null,
220
+ identityFormat: params.identityFormat ?? null,
221
+ identityOkField: params.identityOkField ?? null,
222
+ featureFlag: params.featureFlag ?? null,
223
+ createdAt: now,
224
+ updatedAt: now,
225
+ };
226
+ mockProviderStore.set(params.provider as string, row);
227
+ return row;
228
+ },
229
+ updateProvider: (provider: string, params: Record<string, unknown>) => {
230
+ const existing = mockProviderStore.get(provider);
231
+ if (!existing) return undefined;
232
+ const updated: Record<string, unknown> = { ...existing };
233
+ if (params.scopeSeparator !== undefined) {
234
+ updated.scopeSeparator = params.scopeSeparator;
235
+ }
236
+ if (params.authorizeUrl !== undefined) {
237
+ updated.authorizeUrl = params.authorizeUrl;
238
+ }
239
+ if (params.tokenExchangeUrl !== undefined) {
240
+ updated.tokenExchangeUrl = params.tokenExchangeUrl;
241
+ }
242
+ if (params.refreshUrl !== undefined) {
243
+ updated.refreshUrl = params.refreshUrl;
244
+ }
245
+ if (params.revokeUrl !== undefined) {
246
+ updated.revokeUrl = params.revokeUrl;
247
+ }
248
+ if (params.revokeBodyTemplate !== undefined) {
249
+ updated.revokeBodyTemplate =
250
+ params.revokeBodyTemplate === null
251
+ ? null
252
+ : JSON.stringify(params.revokeBodyTemplate);
253
+ }
254
+ if (params.defaultScopes !== undefined) {
255
+ updated.defaultScopes = JSON.stringify(params.defaultScopes);
256
+ }
257
+ if (params.displayLabel !== undefined) {
258
+ updated.displayLabel = params.displayLabel;
259
+ }
260
+ if (params.logoUrl !== undefined) {
261
+ updated.logoUrl = params.logoUrl;
262
+ }
263
+ updated.updatedAt = Date.now();
264
+ mockProviderStore.set(provider, updated);
265
+ return updated;
266
+ },
158
267
  deleteProvider: () => false,
159
268
  seedProviders: () => {},
160
269
  getActiveConnection: () => undefined,
@@ -229,9 +338,9 @@ mock.module("../oauth/seed-providers.js", () => ({
229
338
 
230
339
  mock.module("../oauth/connection-resolver.js", () => ({
231
340
  resolveOAuthConnection: (
232
- providerKey: string,
341
+ provider: string,
233
342
  options?: Record<string, unknown>,
234
- ) => mockResolveOAuthConnection(providerKey, options),
343
+ ) => mockResolveOAuthConnection(provider, options),
235
344
  }));
236
345
 
237
346
  // ---------------------------------------------------------------------------
@@ -439,45 +548,45 @@ describe("assistant oauth token <provider-key>", () => {
439
548
  describe("assistant oauth providers list", () => {
440
549
  const fakeProviders = [
441
550
  {
442
- providerKey: "google",
443
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
444
- tokenUrl: "https://oauth2.googleapis.com/token",
551
+ provider: "google",
552
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
553
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
445
554
  defaultScopes: "[]",
446
555
  scopePolicy: "{}",
447
- extraParams: null,
556
+ authorizeParams: null,
448
557
  managedServiceConfigKey: "google-oauth",
449
558
  createdAt: "2025-01-01T00:00:00.000Z",
450
559
  updatedAt: "2025-01-01T00:00:00.000Z",
451
560
  },
452
561
  {
453
- providerKey: "google-calendar",
454
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
455
- tokenUrl: "https://oauth2.googleapis.com/token",
562
+ provider: "google-calendar",
563
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
564
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
456
565
  defaultScopes: "[]",
457
566
  scopePolicy: "{}",
458
- extraParams: null,
567
+ authorizeParams: null,
459
568
  managedServiceConfigKey: "google-calendar-oauth",
460
569
  createdAt: "2025-01-01T00:00:00.000Z",
461
570
  updatedAt: "2025-01-01T00:00:00.000Z",
462
571
  },
463
572
  {
464
- providerKey: "slack",
465
- authUrl: "https://slack.com/oauth/v2/authorize",
466
- tokenUrl: "https://slack.com/api/oauth.v2.access",
573
+ provider: "slack",
574
+ authorizeUrl: "https://slack.com/oauth/v2/authorize",
575
+ tokenExchangeUrl: "https://slack.com/api/oauth.v2.access",
467
576
  defaultScopes: "[]",
468
577
  scopePolicy: "{}",
469
- extraParams: null,
578
+ authorizeParams: null,
470
579
  managedServiceConfigKey: null,
471
580
  createdAt: "2025-01-01T00:00:00.000Z",
472
581
  updatedAt: "2025-01-01T00:00:00.000Z",
473
582
  },
474
583
  {
475
- providerKey: "twitter",
476
- authUrl: "https://twitter.com/i/oauth2/authorize",
477
- tokenUrl: "https://api.twitter.com/2/oauth2/token",
584
+ provider: "twitter",
585
+ authorizeUrl: "https://twitter.com/i/oauth2/authorize",
586
+ tokenExchangeUrl: "https://api.twitter.com/2/oauth2/token",
478
587
  defaultScopes: "[]",
479
588
  scopePolicy: "{}",
480
- extraParams: null,
589
+ authorizeParams: null,
481
590
  managedServiceConfigKey: null,
482
591
  createdAt: "2025-01-01T00:00:00.000Z",
483
592
  updatedAt: "2025-01-01T00:00:00.000Z",
@@ -638,7 +747,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
638
747
  mockUpsertAppCalls = [];
639
748
  mockUpsertAppResult = {
640
749
  id: "app-upsert-1",
641
- providerKey: "google",
750
+ provider: "google",
642
751
  clientId: "abc123",
643
752
  createdAt: 1700000000000,
644
753
  updatedAt: 1700000000000,
@@ -890,19 +999,19 @@ describe("assistant oauth ping <provider-key>", () => {
890
999
 
891
1000
  test("returns ok when ping endpoint returns 200", async () => {
892
1001
  mockGetProvider = () => ({
893
- providerKey: "google",
1002
+ provider: "google",
894
1003
  pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
895
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
896
- tokenUrl: "https://oauth2.googleapis.com/token",
1004
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1005
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
897
1006
  defaultScopes: "[]",
898
1007
  scopePolicy: "{}",
899
- extraParams: null,
1008
+ authorizeParams: null,
900
1009
  createdAt: Date.now(),
901
1010
  updatedAt: Date.now(),
902
1011
  });
903
1012
  mockResolveOAuthConnection = async () => ({
904
1013
  id: "conn-1",
905
- providerKey: "google",
1014
+ provider: "google",
906
1015
  accountInfo: null,
907
1016
  request: async () => ({ status: 200, headers: {}, body: {} }),
908
1017
  withToken: async (fn) => fn("mock-access-token-xyz"),
@@ -925,13 +1034,13 @@ describe("assistant oauth ping <provider-key>", () => {
925
1034
 
926
1035
  test("exits 1 when no ping URL configured", async () => {
927
1036
  mockGetProvider = () => ({
928
- providerKey: "telegram",
1037
+ provider: "telegram",
929
1038
  pingUrl: null,
930
- authUrl: "urn:manual-token",
931
- tokenUrl: "urn:manual-token",
1039
+ authorizeUrl: "urn:manual-token",
1040
+ tokenExchangeUrl: "urn:manual-token",
932
1041
  defaultScopes: "[]",
933
1042
  scopePolicy: "{}",
934
- extraParams: null,
1043
+ authorizeParams: null,
935
1044
  createdAt: Date.now(),
936
1045
  updatedAt: Date.now(),
937
1046
  });
@@ -944,19 +1053,19 @@ describe("assistant oauth ping <provider-key>", () => {
944
1053
 
945
1054
  test("exits 1 when ping endpoint returns non-2xx", async () => {
946
1055
  mockGetProvider = () => ({
947
- providerKey: "google",
1056
+ provider: "google",
948
1057
  pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
949
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
950
- tokenUrl: "https://oauth2.googleapis.com/token",
1058
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1059
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
951
1060
  defaultScopes: "[]",
952
1061
  scopePolicy: "{}",
953
- extraParams: null,
1062
+ authorizeParams: null,
954
1063
  createdAt: Date.now(),
955
1064
  updatedAt: Date.now(),
956
1065
  });
957
1066
  mockResolveOAuthConnection = async () => ({
958
1067
  id: "conn-1",
959
- providerKey: "google",
1068
+ provider: "google",
960
1069
  accountInfo: null,
961
1070
  request: async () => ({ status: 403, headers: {}, body: "Forbidden" }),
962
1071
  withToken: async (fn) => fn("mock-access-token-xyz"),
@@ -970,13 +1079,13 @@ describe("assistant oauth ping <provider-key>", () => {
970
1079
 
971
1080
  test("exits 1 when no connection can be resolved", async () => {
972
1081
  mockGetProvider = () => ({
973
- providerKey: "google",
1082
+ provider: "google",
974
1083
  pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
975
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
976
- tokenUrl: "https://oauth2.googleapis.com/token",
1084
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1085
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
977
1086
  defaultScopes: "[]",
978
1087
  scopePolicy: "{}",
979
- extraParams: null,
1088
+ authorizeParams: null,
980
1089
  createdAt: Date.now(),
981
1090
  updatedAt: Date.now(),
982
1091
  });
@@ -1023,12 +1132,12 @@ describe("assistant oauth connect managed mode — platform 401/403 errors", ()
1023
1132
  // Set up managed mode: provider has managedServiceConfigKey, config
1024
1133
  // returns the matching service with mode "managed".
1025
1134
  mockGetProvider = () => ({
1026
- providerKey: "google",
1027
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1028
- tokenUrl: "https://oauth2.googleapis.com/token",
1135
+ provider: "google",
1136
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1137
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
1029
1138
  defaultScopes: "[]",
1030
1139
  scopePolicy: "{}",
1031
- extraParams: null,
1140
+ authorizeParams: null,
1032
1141
  managedServiceConfigKey: "google-oauth",
1033
1142
  createdAt: Date.now(),
1034
1143
  updatedAt: Date.now(),
@@ -1249,12 +1358,12 @@ describe("assistant oauth mode", () => {
1249
1358
  beforeEach(() => {
1250
1359
  mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1251
1360
  mockGetProvider = () => ({
1252
- providerKey: "google",
1253
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1254
- tokenUrl: "https://oauth2.googleapis.com/token",
1361
+ provider: "google",
1362
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1363
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
1255
1364
  defaultScopes: "[]",
1256
1365
  scopePolicy: "{}",
1257
- extraParams: null,
1366
+ authorizeParams: null,
1258
1367
  managedServiceConfigKey: "google-oauth",
1259
1368
  createdAt: Date.now(),
1260
1369
  updatedAt: Date.now(),
@@ -1313,3 +1422,541 @@ describe("assistant oauth mode", () => {
1313
1422
  expect(parsed.mode).toBe("your-own");
1314
1423
  });
1315
1424
  });
1425
+
1426
+ // ---------------------------------------------------------------------------
1427
+ // providers register / update / get — --scope-separator wiring
1428
+ // ---------------------------------------------------------------------------
1429
+
1430
+ describe("assistant oauth providers --scope-separator", () => {
1431
+ beforeEach(() => {
1432
+ mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1433
+ mockProviderStore.clear();
1434
+ // Default getProvider falls through to mockProviderStore via the
1435
+ // oauth-store mock module. Tests in this describe block don't need
1436
+ // a per-test mockGetProvider override.
1437
+ mockGetProvider = () => undefined;
1438
+ mockGetConfig = () => ({ services: {} });
1439
+ });
1440
+
1441
+ afterEach(() => {
1442
+ mockProviderStore.clear();
1443
+ mockGetProvider = () => undefined;
1444
+ });
1445
+
1446
+ test("providers register --scope-separator , stores ',' on the provider row", async () => {
1447
+ const { exitCode } = await runCli([
1448
+ "providers",
1449
+ "register",
1450
+ "--provider-key",
1451
+ "custom-linear",
1452
+ "--auth-url",
1453
+ "https://linear.app/oauth/authorize",
1454
+ "--token-url",
1455
+ "https://api.linear.app/oauth/token",
1456
+ "--scopes",
1457
+ "read,write",
1458
+ "--scope-separator",
1459
+ ",",
1460
+ "--json",
1461
+ ]);
1462
+ expect(exitCode).toBe(0);
1463
+ const stored = mockProviderStore.get("custom-linear");
1464
+ expect(stored).toBeDefined();
1465
+ expect(stored?.scopeSeparator).toBe(",");
1466
+ });
1467
+
1468
+ test("providers register without --scope-separator stores the default ' '", async () => {
1469
+ const { exitCode } = await runCli([
1470
+ "providers",
1471
+ "register",
1472
+ "--provider-key",
1473
+ "custom-default-sep",
1474
+ "--auth-url",
1475
+ "https://example.com/oauth/authorize",
1476
+ "--token-url",
1477
+ "https://example.com/oauth/token",
1478
+ "--scopes",
1479
+ "read,write",
1480
+ "--json",
1481
+ ]);
1482
+ expect(exitCode).toBe(0);
1483
+ const stored = mockProviderStore.get("custom-default-sep");
1484
+ expect(stored).toBeDefined();
1485
+ expect(stored?.scopeSeparator).toBe(" ");
1486
+ });
1487
+
1488
+ test("providers update --scope-separator , updates an existing custom provider", async () => {
1489
+ // Seed the store with an existing custom provider that uses the default
1490
+ // " " separator.
1491
+ await runCli([
1492
+ "providers",
1493
+ "register",
1494
+ "--provider-key",
1495
+ "custom-update-target",
1496
+ "--auth-url",
1497
+ "https://example.com/oauth/authorize",
1498
+ "--token-url",
1499
+ "https://example.com/oauth/token",
1500
+ "--scopes",
1501
+ "read",
1502
+ "--json",
1503
+ ]);
1504
+ expect(mockProviderStore.get("custom-update-target")?.scopeSeparator).toBe(
1505
+ " ",
1506
+ );
1507
+
1508
+ const { exitCode } = await runCli([
1509
+ "providers",
1510
+ "update",
1511
+ "custom-update-target",
1512
+ "--scope-separator",
1513
+ ",",
1514
+ "--json",
1515
+ ]);
1516
+ expect(exitCode).toBe(0);
1517
+ expect(mockProviderStore.get("custom-update-target")?.scopeSeparator).toBe(
1518
+ ",",
1519
+ );
1520
+ });
1521
+
1522
+ test("providers get <key> --json includes scopeSeparator from the serialized output", async () => {
1523
+ // Seed the store with a custom provider that uses ',' as the separator.
1524
+ await runCli([
1525
+ "providers",
1526
+ "register",
1527
+ "--provider-key",
1528
+ "custom-get-target",
1529
+ "--auth-url",
1530
+ "https://example.com/oauth/authorize",
1531
+ "--token-url",
1532
+ "https://example.com/oauth/token",
1533
+ "--scopes",
1534
+ "read,write",
1535
+ "--scope-separator",
1536
+ ",",
1537
+ "--json",
1538
+ ]);
1539
+
1540
+ const { exitCode, stdout } = await runCli([
1541
+ "providers",
1542
+ "get",
1543
+ "custom-get-target",
1544
+ "--json",
1545
+ ]);
1546
+ expect(exitCode).toBe(0);
1547
+ const parsed = JSON.parse(stdout);
1548
+ expect(parsed.scopeSeparator).toBe(",");
1549
+ });
1550
+ });
1551
+
1552
+ // ---------------------------------------------------------------------------
1553
+ // providers register / update / get — --refresh-url wiring
1554
+ // ---------------------------------------------------------------------------
1555
+
1556
+ describe("assistant oauth providers --refresh-url", () => {
1557
+ beforeEach(() => {
1558
+ mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1559
+ mockProviderStore.clear();
1560
+ // Default getProvider falls through to mockProviderStore via the
1561
+ // oauth-store mock module.
1562
+ mockGetProvider = () => undefined;
1563
+ mockGetConfig = () => ({ services: {} });
1564
+ });
1565
+
1566
+ afterEach(() => {
1567
+ mockProviderStore.clear();
1568
+ mockGetProvider = () => undefined;
1569
+ });
1570
+
1571
+ test("providers register --refresh-url stores the URL on the provider row", async () => {
1572
+ const { exitCode } = await runCli([
1573
+ "providers",
1574
+ "register",
1575
+ "--provider-key",
1576
+ "custom-refresh-url",
1577
+ "--auth-url",
1578
+ "https://example.com/oauth/authorize",
1579
+ "--token-url",
1580
+ "https://example.com/oauth/token",
1581
+ "--refresh-url",
1582
+ "https://refresh.example.com/token",
1583
+ "--json",
1584
+ ]);
1585
+ expect(exitCode).toBe(0);
1586
+ const stored = mockProviderStore.get("custom-refresh-url");
1587
+ expect(stored).toBeDefined();
1588
+ expect(stored?.refreshUrl).toBe("https://refresh.example.com/token");
1589
+ });
1590
+
1591
+ test("providers register without --refresh-url stores null", async () => {
1592
+ const { exitCode } = await runCli([
1593
+ "providers",
1594
+ "register",
1595
+ "--provider-key",
1596
+ "custom-no-refresh-url",
1597
+ "--auth-url",
1598
+ "https://example.com/oauth/authorize",
1599
+ "--token-url",
1600
+ "https://example.com/oauth/token",
1601
+ "--json",
1602
+ ]);
1603
+ expect(exitCode).toBe(0);
1604
+ const stored = mockProviderStore.get("custom-no-refresh-url");
1605
+ expect(stored).toBeDefined();
1606
+ expect(stored?.refreshUrl).toBeNull();
1607
+ });
1608
+
1609
+ test("providers update --refresh-url updates an existing custom provider", async () => {
1610
+ // Seed the store with an existing custom provider that has no refresh URL.
1611
+ await runCli([
1612
+ "providers",
1613
+ "register",
1614
+ "--provider-key",
1615
+ "custom-update-refresh",
1616
+ "--auth-url",
1617
+ "https://example.com/oauth/authorize",
1618
+ "--token-url",
1619
+ "https://example.com/oauth/token",
1620
+ "--json",
1621
+ ]);
1622
+ expect(
1623
+ mockProviderStore.get("custom-update-refresh")?.refreshUrl,
1624
+ ).toBeNull();
1625
+
1626
+ const { exitCode } = await runCli([
1627
+ "providers",
1628
+ "update",
1629
+ "custom-update-refresh",
1630
+ "--refresh-url",
1631
+ "https://new-refresh.example.com/token",
1632
+ "--json",
1633
+ ]);
1634
+ expect(exitCode).toBe(0);
1635
+ expect(mockProviderStore.get("custom-update-refresh")?.refreshUrl).toBe(
1636
+ "https://new-refresh.example.com/token",
1637
+ );
1638
+ });
1639
+
1640
+ test("providers get <key> --json includes refreshUrl from the serialized output", async () => {
1641
+ // Seed the store with a custom provider that has a refresh URL set.
1642
+ await runCli([
1643
+ "providers",
1644
+ "register",
1645
+ "--provider-key",
1646
+ "custom-get-refresh",
1647
+ "--auth-url",
1648
+ "https://example.com/oauth/authorize",
1649
+ "--token-url",
1650
+ "https://example.com/oauth/token",
1651
+ "--refresh-url",
1652
+ "https://refresh.example.com/token",
1653
+ "--json",
1654
+ ]);
1655
+
1656
+ const { exitCode, stdout } = await runCli([
1657
+ "providers",
1658
+ "get",
1659
+ "custom-get-refresh",
1660
+ "--json",
1661
+ ]);
1662
+ expect(exitCode).toBe(0);
1663
+ const parsed = JSON.parse(stdout);
1664
+ expect(parsed.refreshUrl).toBe("https://refresh.example.com/token");
1665
+ });
1666
+ });
1667
+
1668
+ // ---------------------------------------------------------------------------
1669
+ // providers register / update / get — --revoke-url and --revoke-body-template wiring
1670
+ // ---------------------------------------------------------------------------
1671
+
1672
+ describe("assistant oauth providers --revoke-url and --revoke-body-template", () => {
1673
+ beforeEach(() => {
1674
+ mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
1675
+ mockProviderStore.clear();
1676
+ mockGetProvider = () => undefined;
1677
+ mockGetConfig = () => ({ services: {} });
1678
+ });
1679
+
1680
+ afterEach(() => {
1681
+ mockProviderStore.clear();
1682
+ mockGetProvider = () => undefined;
1683
+ });
1684
+
1685
+ test("providers register --revoke-url and --revoke-body-template stores both fields", async () => {
1686
+ const { exitCode } = await runCli([
1687
+ "providers",
1688
+ "register",
1689
+ "--provider-key",
1690
+ "custom-revoke-roundtrip",
1691
+ "--auth-url",
1692
+ "https://example.com/oauth/authorize",
1693
+ "--token-url",
1694
+ "https://example.com/oauth/token",
1695
+ "--revoke-url",
1696
+ "https://revoke.example.com",
1697
+ "--revoke-body-template",
1698
+ '{"token":"{access_token}"}',
1699
+ "--json",
1700
+ ]);
1701
+ expect(exitCode).toBe(0);
1702
+ const stored = mockProviderStore.get("custom-revoke-roundtrip");
1703
+ expect(stored).toBeDefined();
1704
+ expect(stored?.revokeUrl).toBe("https://revoke.example.com");
1705
+ // revokeBodyTemplate is JSON-stringified at the storage layer.
1706
+ expect(JSON.parse(stored?.revokeBodyTemplate as string)).toEqual({
1707
+ token: "{access_token}",
1708
+ });
1709
+ });
1710
+
1711
+ test("providers register without --revoke-url or --revoke-body-template stores both as null", async () => {
1712
+ const { exitCode } = await runCli([
1713
+ "providers",
1714
+ "register",
1715
+ "--provider-key",
1716
+ "custom-no-revoke",
1717
+ "--auth-url",
1718
+ "https://example.com/oauth/authorize",
1719
+ "--token-url",
1720
+ "https://example.com/oauth/token",
1721
+ "--json",
1722
+ ]);
1723
+ expect(exitCode).toBe(0);
1724
+ const stored = mockProviderStore.get("custom-no-revoke");
1725
+ expect(stored).toBeDefined();
1726
+ expect(stored?.revokeUrl).toBeNull();
1727
+ expect(stored?.revokeBodyTemplate).toBeNull();
1728
+ });
1729
+
1730
+ test("providers register with malformed --revoke-body-template fails with a JSON parse error", async () => {
1731
+ const { exitCode, stdout } = await runCli([
1732
+ "providers",
1733
+ "register",
1734
+ "--provider-key",
1735
+ "custom-bad-revoke",
1736
+ "--auth-url",
1737
+ "https://example.com/oauth/authorize",
1738
+ "--token-url",
1739
+ "https://example.com/oauth/token",
1740
+ "--revoke-body-template",
1741
+ "not-json{",
1742
+ "--json",
1743
+ ]);
1744
+ expect(exitCode).toBe(1);
1745
+ const parsed = JSON.parse(stdout);
1746
+ expect(parsed.ok).toBe(false);
1747
+ // The error message comes from JSON.parse — match permissively.
1748
+ expect(parsed.error).toMatch(/JSON|Unexpected|parse/i);
1749
+ // Provider should not have been written.
1750
+ expect(mockProviderStore.get("custom-bad-revoke")).toBeUndefined();
1751
+ });
1752
+
1753
+ test("providers update --revoke-url updates only the URL", async () => {
1754
+ // Seed an existing custom provider.
1755
+ await runCli([
1756
+ "providers",
1757
+ "register",
1758
+ "--provider-key",
1759
+ "custom-update-revoke-url",
1760
+ "--auth-url",
1761
+ "https://example.com/oauth/authorize",
1762
+ "--token-url",
1763
+ "https://example.com/oauth/token",
1764
+ "--json",
1765
+ ]);
1766
+ expect(
1767
+ mockProviderStore.get("custom-update-revoke-url")?.revokeUrl,
1768
+ ).toBeNull();
1769
+
1770
+ const { exitCode } = await runCli([
1771
+ "providers",
1772
+ "update",
1773
+ "custom-update-revoke-url",
1774
+ "--revoke-url",
1775
+ "https://new-revoke.example.com",
1776
+ "--json",
1777
+ ]);
1778
+ expect(exitCode).toBe(0);
1779
+ expect(mockProviderStore.get("custom-update-revoke-url")?.revokeUrl).toBe(
1780
+ "https://new-revoke.example.com",
1781
+ );
1782
+ // revokeBodyTemplate should still be null (unchanged).
1783
+ expect(
1784
+ mockProviderStore.get("custom-update-revoke-url")?.revokeBodyTemplate,
1785
+ ).toBeNull();
1786
+ });
1787
+
1788
+ test("providers update --revoke-url with empty string clears the stored URL to null", async () => {
1789
+ // Seed an existing custom provider that already has a non-null revokeUrl.
1790
+ await runCli([
1791
+ "providers",
1792
+ "register",
1793
+ "--provider-key",
1794
+ "custom-clear-revoke-url",
1795
+ "--auth-url",
1796
+ "https://example.com/oauth/authorize",
1797
+ "--token-url",
1798
+ "https://example.com/oauth/token",
1799
+ "--revoke-url",
1800
+ "https://revoke.example.com",
1801
+ "--json",
1802
+ ]);
1803
+ expect(mockProviderStore.get("custom-clear-revoke-url")?.revokeUrl).toBe(
1804
+ "https://revoke.example.com",
1805
+ );
1806
+
1807
+ // Pass an empty string to --revoke-url — the help text promises this
1808
+ // clears the field, so the stored value should end up as null (not "").
1809
+ const { exitCode, stdout } = await runCli([
1810
+ "providers",
1811
+ "update",
1812
+ "custom-clear-revoke-url",
1813
+ "--revoke-url",
1814
+ "",
1815
+ "--json",
1816
+ ]);
1817
+ expect(exitCode).toBe(0);
1818
+ expect(
1819
+ mockProviderStore.get("custom-clear-revoke-url")?.revokeUrl,
1820
+ ).toBeNull();
1821
+
1822
+ // The serialized --json output should also reflect null, not "".
1823
+ const parsed = JSON.parse(stdout);
1824
+ expect(parsed.revokeUrl).toBeNull();
1825
+ });
1826
+
1827
+ test("providers update --revoke-body-template updates only the template (JSON round-trip)", async () => {
1828
+ await runCli([
1829
+ "providers",
1830
+ "register",
1831
+ "--provider-key",
1832
+ "custom-update-revoke-body",
1833
+ "--auth-url",
1834
+ "https://example.com/oauth/authorize",
1835
+ "--token-url",
1836
+ "https://example.com/oauth/token",
1837
+ "--json",
1838
+ ]);
1839
+
1840
+ const { exitCode } = await runCli([
1841
+ "providers",
1842
+ "update",
1843
+ "custom-update-revoke-body",
1844
+ "--revoke-body-template",
1845
+ '{"token":"{access_token}","client_id":"{client_id}"}',
1846
+ "--json",
1847
+ ]);
1848
+ expect(exitCode).toBe(0);
1849
+ const stored = mockProviderStore.get("custom-update-revoke-body");
1850
+ expect(stored).toBeDefined();
1851
+ expect(JSON.parse(stored?.revokeBodyTemplate as string)).toEqual({
1852
+ token: "{access_token}",
1853
+ client_id: "{client_id}",
1854
+ });
1855
+ // revokeUrl should still be null (unchanged).
1856
+ expect(stored?.revokeUrl).toBeNull();
1857
+ });
1858
+
1859
+ test("providers update --revoke-body-template with empty string clears the stored template to null", async () => {
1860
+ // Seed an existing custom provider that already has a non-null
1861
+ // revokeBodyTemplate set via register.
1862
+ await runCli([
1863
+ "providers",
1864
+ "register",
1865
+ "--provider-key",
1866
+ "custom-clear-revoke-body",
1867
+ "--auth-url",
1868
+ "https://example.com/oauth/authorize",
1869
+ "--token-url",
1870
+ "https://example.com/oauth/token",
1871
+ "--revoke-url",
1872
+ "https://revoke.example.com",
1873
+ "--revoke-body-template",
1874
+ '{"token":"{access_token}"}',
1875
+ "--json",
1876
+ ]);
1877
+ const initial = mockProviderStore.get("custom-clear-revoke-body");
1878
+ expect(initial?.revokeBodyTemplate).toBeDefined();
1879
+ expect(JSON.parse(initial?.revokeBodyTemplate as string)).toEqual({
1880
+ token: "{access_token}",
1881
+ });
1882
+
1883
+ // Pass an empty string to --revoke-body-template — the help text promises
1884
+ // this clears the field, so the stored value should end up as null
1885
+ // (not the string "null" or a JSON.parse crash).
1886
+ const { exitCode, stdout } = await runCli([
1887
+ "providers",
1888
+ "update",
1889
+ "custom-clear-revoke-body",
1890
+ "--revoke-body-template",
1891
+ "",
1892
+ "--json",
1893
+ ]);
1894
+ expect(exitCode).toBe(0);
1895
+ expect(
1896
+ mockProviderStore.get("custom-clear-revoke-body")?.revokeBodyTemplate,
1897
+ ).toBeNull();
1898
+
1899
+ // The serialized --json output should also reflect null.
1900
+ const parsed = JSON.parse(stdout);
1901
+ expect(parsed.revokeBodyTemplate).toBeNull();
1902
+ });
1903
+
1904
+ test("providers get google --json includes both fields populated from PR 1's seed data", async () => {
1905
+ // Simulate the seeded "google" provider row by overriding mockGetProvider
1906
+ // to return a row matching what PR 1's seed inserts.
1907
+ const now = Date.now();
1908
+ mockGetProvider = (provider: string) => {
1909
+ if (provider !== "google") return undefined;
1910
+ return {
1911
+ provider: "google",
1912
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1913
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
1914
+ refreshUrl: null,
1915
+ tokenEndpointAuthMethod: "client_secret_post",
1916
+ userinfoUrl: null,
1917
+ baseUrl: null,
1918
+ defaultScopes: "[]",
1919
+ scopePolicy: "{}",
1920
+ scopeSeparator: " ",
1921
+ authorizeParams: null,
1922
+ pingUrl: null,
1923
+ pingMethod: null,
1924
+ pingHeaders: null,
1925
+ pingBody: null,
1926
+ revokeUrl: "https://oauth2.googleapis.com/revoke",
1927
+ revokeBodyTemplate: JSON.stringify({ token: "{access_token}" }),
1928
+ managedServiceConfigKey: null,
1929
+ displayLabel: "Google",
1930
+ description: null,
1931
+ dashboardUrl: null,
1932
+ clientIdPlaceholder: null,
1933
+ requiresClientSecret: 1,
1934
+ loopbackPort: null,
1935
+ injectionTemplates: null,
1936
+ appType: null,
1937
+ setupNotes: null,
1938
+ identityUrl: null,
1939
+ identityMethod: null,
1940
+ identityHeaders: null,
1941
+ identityBody: null,
1942
+ identityResponsePaths: null,
1943
+ identityFormat: null,
1944
+ identityOkField: null,
1945
+ featureFlag: null,
1946
+ createdAt: now,
1947
+ updatedAt: now,
1948
+ };
1949
+ };
1950
+
1951
+ const { exitCode, stdout } = await runCli([
1952
+ "providers",
1953
+ "get",
1954
+ "google",
1955
+ "--json",
1956
+ ]);
1957
+ expect(exitCode).toBe(0);
1958
+ const parsed = JSON.parse(stdout);
1959
+ expect(parsed.revokeUrl).toBe("https://oauth2.googleapis.com/revoke");
1960
+ expect(parsed.revokeBodyTemplate).toEqual({ token: "{access_token}" });
1961
+ });
1962
+ });