@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
@@ -190,6 +190,28 @@ describe("isTextMimeType", () => {
190
190
  // A binary plist has a specific MIME type — extension should not override it
191
191
  expect(isTextMimeType("application/x-plist", "Info.plist")).toBe(false);
192
192
  });
193
+
194
+ test("application/octet-stream with .jsonl filename is text", () => {
195
+ expect(isTextMimeType("application/octet-stream", "messages.jsonl")).toBe(
196
+ true,
197
+ );
198
+ });
199
+
200
+ test("application/octet-stream with .ndjson filename is text", () => {
201
+ expect(isTextMimeType("application/octet-stream", "events.ndjson")).toBe(
202
+ true,
203
+ );
204
+ });
205
+
206
+ test("application/octet-stream with .JSONL uppercase is text", () => {
207
+ expect(isTextMimeType("application/octet-stream", "DATA.JSONL")).toBe(true);
208
+ });
209
+
210
+ test("application/octet-stream with .NDJSON uppercase is text", () => {
211
+ expect(isTextMimeType("application/octet-stream", "DATA.NDJSON")).toBe(
212
+ true,
213
+ );
214
+ });
193
215
  });
194
216
 
195
217
  // ===========================================================================
@@ -120,7 +120,14 @@ function handleWorkspaceFile(ctx: RouteContext): Response {
120
120
  }
121
121
 
122
122
  const mimeType = Bun.file(resolved).type;
123
- const isText = isTextMimeType(mimeType, basename(resolved));
123
+ // Empty files with unknown MIME type default to text — there is no binary
124
+ // content to protect, and files created via the UI "New File" action are
125
+ // always 0 bytes. Without this override, extensionless files (e.g. "Test")
126
+ // would be classified as binary and rendered in a non-editable fallback view.
127
+ const isText =
128
+ stat.size === 0 && mimeType === "application/octet-stream"
129
+ ? true
130
+ : isTextMimeType(mimeType, basename(resolved));
124
131
  const isBinary = !isText;
125
132
 
126
133
  let content: string | undefined = undefined;
@@ -159,6 +159,8 @@ const TEXT_FILE_EXTENSIONS = new Set([
159
159
  "patch",
160
160
  "log",
161
161
  "lock",
162
+ "jsonl",
163
+ "ndjson",
162
164
  ]);
163
165
 
164
166
  export function isTextMimeType(mimeType: string, fileName?: string): boolean {
@@ -23,6 +23,7 @@ const log = getLogger("scheduler");
23
23
 
24
24
  export interface ScheduleMessageOptions {
25
25
  trustClass?: "guardian" | "trusted_contact" | "unknown";
26
+ taskRunId?: string;
26
27
  }
27
28
 
28
29
  export type ScheduleMessageProcessor = (
@@ -196,11 +197,12 @@ async function runScheduleOnce(
196
197
  source: "schedule",
197
198
  scheduleJobId: job.id,
198
199
  },
199
- processMessage as (
200
- conversationId: string,
201
- message: string,
202
- taskRunId: string,
203
- ) => Promise<void>,
200
+ async (conversationId, message, taskRunId) => {
201
+ await processMessage(conversationId, message, {
202
+ trustClass: "guardian",
203
+ taskRunId,
204
+ });
205
+ },
204
206
  );
205
207
 
206
208
  onScheduleConversationCreated?.({
@@ -190,6 +190,26 @@ export class CesCredentialBackend implements CredentialBackend {
190
190
  }
191
191
  }
192
192
 
193
+ async bulkSet(
194
+ credentials: Array<{ account: string; value: string }>,
195
+ ): Promise<Array<{ account: string; ok: boolean }>> {
196
+ try {
197
+ const res = await cesRequest("POST", "/v1/credentials/bulk", {
198
+ credentials,
199
+ });
200
+ if (!res?.ok) {
201
+ return credentials.map((c) => ({ account: c.account, ok: false }));
202
+ }
203
+ const data = (await res.json()) as {
204
+ results: Array<{ account: string; ok: boolean }>;
205
+ };
206
+ return data.results;
207
+ } catch (err) {
208
+ log.warn({ err }, "CES credential bulk set threw unexpectedly");
209
+ return credentials.map((c) => ({ account: c.account, ok: false }));
210
+ }
211
+ }
212
+
193
213
  async list(): Promise<CredentialListResult> {
194
214
  try {
195
215
  const res = await cesRequest("GET", "/v1/credentials");
@@ -86,4 +86,21 @@ export class CesRpcCredentialBackend implements CredentialBackend {
86
86
  return { accounts: [], unreachable: true };
87
87
  }
88
88
  }
89
+
90
+ async bulkSet(
91
+ credentials: Array<{ account: string; value: string }>,
92
+ ): Promise<Array<{ account: string; ok: boolean }>> {
93
+ if (!this.isAvailable()) {
94
+ return credentials.map((c) => ({ account: c.account, ok: false }));
95
+ }
96
+ try {
97
+ const result = await this.client.call(CesRpcMethod.BulkSetCredentials, {
98
+ credentials,
99
+ });
100
+ return result.results;
101
+ } catch (err) {
102
+ log.warn({ err }, "CES RPC bulk credential set failed");
103
+ return credentials.map((c) => ({ account: c.account, ok: false }));
104
+ }
105
+ }
89
106
  }
@@ -47,6 +47,11 @@ export interface CredentialBackend {
47
47
 
48
48
  /** List all account names. */
49
49
  list(): Promise<CredentialListResult>;
50
+
51
+ /** Bulk-set multiple credentials. Optional — backends without native bulk support omit this. */
52
+ bulkSet?(
53
+ credentials: Array<{ account: string; value: string }>,
54
+ ): Promise<Array<{ account: string; ok: boolean }>>;
50
55
  }
51
56
 
52
57
  // ---------------------------------------------------------------------------
@@ -33,13 +33,13 @@ export type TokenEndpointAuthMethod =
33
33
  | "client_secret_post";
34
34
 
35
35
  export interface OAuth2Config {
36
- authUrl: string;
37
- tokenUrl: string;
36
+ authorizeUrl: string;
37
+ tokenExchangeUrl: string;
38
38
  scopes: string[];
39
39
  clientId: string;
40
40
  /** Client secret for providers that require it (e.g. Slack). PKCE is always used regardless. */
41
41
  clientSecret?: string;
42
- extraParams?: Record<string, string>;
42
+ authorizeParams?: Record<string, string>;
43
43
  /** URL to fetch user identity info after OAuth. If omitted, account info is not fetched. */
44
44
  userinfoUrl?: string;
45
45
  /**
@@ -49,6 +49,13 @@ export interface OAuth2Config {
49
49
  * Defaults to `client_secret_post`.
50
50
  */
51
51
  tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
52
+ /**
53
+ * Separator used to join scopes in the authorize URL and split the
54
+ * granted-scope string returned by the token endpoint. Defaults to
55
+ * `" "` (space) per the OAuth 2.0 spec, but providers like Linear
56
+ * use `","` (comma).
57
+ */
58
+ scopeSeparator: string;
52
59
  }
53
60
 
54
61
  export interface OAuth2TokenResult {
@@ -130,7 +137,7 @@ async function exchangeCodeForTokens(
130
137
  }
131
138
  }
132
139
 
133
- const tokenResp = await fetch(config.tokenUrl, {
140
+ const tokenResp = await fetch(config.tokenExchangeUrl, {
134
141
  method: "POST",
135
142
  headers,
136
143
  body: new URLSearchParams(tokenBody),
@@ -187,9 +194,19 @@ async function exchangeCodeForTokens(
187
194
  (tokenData.token_type as string | undefined),
188
195
  };
189
196
 
197
+ // Defensive split: providers (e.g. GitHub, Slack) may return comma-separated
198
+ // scopes in token responses regardless of the scope_separator used to join
199
+ // outbound authorize URLs, so we tolerate both spaces and commas here. When
200
+ // a provider explicitly configures a non-default separator (e.g. Linear uses
201
+ // ","), we honor that to keep symmetric round-tripping of configured scopes.
202
+ const splitPattern =
203
+ config.scopeSeparator === " " ? /[ ,]/ : config.scopeSeparator;
190
204
  const grantedScopes =
191
205
  typeof tokens.scope === "string"
192
- ? tokens.scope.split(/[ ,]/).filter(Boolean)
206
+ ? tokens.scope
207
+ .split(splitPattern)
208
+ .map((s) => s.trim())
209
+ .filter(Boolean)
193
210
  : [...config.scopes];
194
211
 
195
212
  return { tokens, grantedScopes, rawTokenResponse: tokenData };
@@ -228,18 +245,18 @@ async function runGatewayFlow(
228
245
  });
229
246
 
230
247
  const authParams = new URLSearchParams({
231
- ...config.extraParams,
248
+ ...config.authorizeParams,
232
249
  client_id: config.clientId,
233
250
  redirect_uri: redirectUri,
234
251
  response_type: "code",
235
- scope: config.scopes.join(" "),
252
+ scope: config.scopes.join(config.scopeSeparator),
236
253
  state,
237
254
  code_challenge: codeChallenge,
238
255
  code_challenge_method: "S256",
239
256
  });
240
257
 
241
- const authUrl = `${config.authUrl}?${authParams}`;
242
- callbacks.openUrl(authUrl);
258
+ const authorizeUrl = `${config.authorizeUrl}?${authParams}`;
259
+ callbacks.openUrl(authorizeUrl);
243
260
 
244
261
  const code = await codePromise;
245
262
 
@@ -401,22 +418,22 @@ function startLoopbackServerAndWaitForCode(
401
418
  );
402
419
 
403
420
  const authParams = new URLSearchParams({
404
- ...config.extraParams,
421
+ ...config.authorizeParams,
405
422
  client_id: config.clientId,
406
423
  redirect_uri: boundRedirectUri,
407
424
  response_type: "code",
408
- scope: config.scopes.join(" "),
425
+ scope: config.scopes.join(config.scopeSeparator),
409
426
  state,
410
427
  code_challenge: codeChallenge,
411
428
  code_challenge_method: "S256",
412
429
  });
413
430
 
414
- const authUrl = `${config.authUrl}?${authParams}`;
431
+ const authorizeUrl = `${config.authorizeUrl}?${authParams}`;
415
432
  log.info(
416
- { authUrlLength: authUrl.length, state },
433
+ { authorizeUrlLength: authorizeUrl.length, state },
417
434
  "oauth2 loopback: built auth URL, calling openUrl callback",
418
435
  );
419
- callbacks.openUrl(authUrl);
436
+ callbacks.openUrl(authorizeUrl);
420
437
  log.info("oauth2 loopback: openUrl callback returned");
421
438
  });
422
439
 
@@ -439,7 +456,7 @@ function startLoopbackServerAndWaitForCode(
439
456
  // ---------------------------------------------------------------------------
440
457
 
441
458
  export interface OAuth2PreparedFlow {
442
- authUrl: string;
459
+ authorizeUrl: string;
443
460
  state: string;
444
461
  /** Resolves when the user completes authorization and tokens are exchanged. */
445
462
  completion: Promise<OAuth2FlowResult>;
@@ -493,17 +510,17 @@ export async function prepareOAuth2Flow(
493
510
  });
494
511
 
495
512
  const authParams = new URLSearchParams({
496
- ...config.extraParams,
513
+ ...config.authorizeParams,
497
514
  client_id: config.clientId,
498
515
  redirect_uri: redirectUri,
499
516
  response_type: "code",
500
- scope: config.scopes.join(" "),
517
+ scope: config.scopes.join(config.scopeSeparator),
501
518
  state,
502
519
  code_challenge: codeChallenge,
503
520
  code_challenge_method: "S256",
504
521
  });
505
522
 
506
- const authUrl = `${config.authUrl}?${authParams}`;
523
+ const authorizeUrl = `${config.authorizeUrl}?${authParams}`;
507
524
 
508
525
  const completion = codePromise.then(async (code) => {
509
526
  return await exchangeCodeForTokens(config, code, redirectUri, codeVerifier);
@@ -511,7 +528,7 @@ export async function prepareOAuth2Flow(
511
528
 
512
529
  log.debug({ transport: "gateway", state }, "Prepared deferred OAuth2 flow");
513
530
 
514
- return { authUrl, state, completion };
531
+ return { authorizeUrl, state, completion };
515
532
  }
516
533
 
517
534
  /**
@@ -533,17 +550,17 @@ async function prepareLoopbackFlow(
533
550
  );
534
551
 
535
552
  const authParams = new URLSearchParams({
536
- ...config.extraParams,
553
+ ...config.authorizeParams,
537
554
  client_id: config.clientId,
538
555
  redirect_uri: redirectUri,
539
556
  response_type: "code",
540
- scope: config.scopes.join(" "),
557
+ scope: config.scopes.join(config.scopeSeparator),
541
558
  state,
542
559
  code_challenge: codeChallenge,
543
560
  code_challenge_method: "S256",
544
561
  });
545
562
 
546
- const authUrl = `${config.authUrl}?${authParams}`;
563
+ const authorizeUrl = `${config.authorizeUrl}?${authParams}`;
547
564
 
548
565
  const completion = codePromise.then(async (code) => {
549
566
  return await exchangeCodeForTokens(config, code, redirectUri, codeVerifier);
@@ -554,7 +571,7 @@ async function prepareLoopbackFlow(
554
571
  "Prepared deferred OAuth2 flow (loopback)",
555
572
  );
556
573
 
557
- return { authUrl, state, completion };
574
+ return { authorizeUrl, state, completion };
558
575
  }
559
576
 
560
577
  /**
@@ -763,7 +780,7 @@ export async function startOAuth2Flow(
763
780
  * Supports both PKCE (no secret) and client_secret flows.
764
781
  */
765
782
  export async function refreshOAuth2Token(
766
- tokenUrl: string,
783
+ tokenExchangeUrl: string,
767
784
  clientId: string,
768
785
  refreshToken: string,
769
786
  clientSecret?: string,
@@ -792,7 +809,7 @@ export async function refreshOAuth2Token(
792
809
  }
793
810
  }
794
811
 
795
- const resp = await fetch(tokenUrl, {
812
+ const resp = await fetch(tokenExchangeUrl, {
796
813
  method: "POST",
797
814
  headers,
798
815
  body: new URLSearchParams(body),
@@ -86,6 +86,15 @@ let _cesHttpUnreachable = false;
86
86
  /** Minimum interval between CES reconnection attempts. */
87
87
  const RECONNECT_COOLDOWN_MS = 3_000;
88
88
 
89
+ /**
90
+ * Hard timeout for each public credential operation (resolve + backend call).
91
+ * Prevents indefinite blocking when CES reconnection or backend operations hang.
92
+ *
93
+ * Set to 45s to comfortably cover the CES HTTP set worst case (~34s:
94
+ * 3 fetch attempts × 10s REQUEST_TIMEOUT_MS + 2 × 2s SET_RETRY_DELAY_MS).
95
+ */
96
+ const CREDENTIAL_OP_TIMEOUT_MS = 45_000;
97
+
89
98
  /** Inject a CES RPC client for credential routing. Resets the resolved backend. */
90
99
  export function setCesClient(client: CesClient | undefined): void {
91
100
  _cesClient = client;
@@ -308,16 +317,58 @@ function updateCesHttpReachability(
308
317
  }
309
318
  }
310
319
 
320
+ // ---------------------------------------------------------------------------
321
+ // Timeout helper
322
+ // ---------------------------------------------------------------------------
323
+
324
+ const CREDENTIAL_TIMEOUT_MSG = "Credential operation timed out";
325
+
326
+ /**
327
+ * Race a credential operation against a hard deadline. If the operation
328
+ * does not settle within `CREDENTIAL_OP_TIMEOUT_MS`, return the supplied
329
+ * fallback value so callers degrade gracefully instead of hanging.
330
+ *
331
+ * Non-timeout errors from `op()` are propagated to callers rather than
332
+ * silently swallowed — only genuine timeouts return the fallback.
333
+ */
334
+ async function withCredentialTimeout<T>(
335
+ op: () => Promise<T>,
336
+ fallback: T,
337
+ ): Promise<T> {
338
+ return new Promise<T>((resolve, reject) => {
339
+ const timer = setTimeout(() => {
340
+ log.warn(CREDENTIAL_TIMEOUT_MSG + " — returning fallback");
341
+ resolve(fallback);
342
+ }, CREDENTIAL_OP_TIMEOUT_MS);
343
+
344
+ op().then(
345
+ (val) => {
346
+ clearTimeout(timer);
347
+ resolve(val);
348
+ },
349
+ (err) => {
350
+ clearTimeout(timer);
351
+ reject(err);
352
+ },
353
+ );
354
+ });
355
+ }
356
+
311
357
  /**
312
358
  * List all account names from the resolved backend (async).
313
359
  *
314
360
  * Queries exactly one backend — no cross-store merge.
315
361
  */
316
362
  export async function listSecureKeysAsync(): Promise<CredentialListResult> {
317
- const backend = await resolveBackendAsync();
318
- const result = await backend.list();
319
- updateCesHttpReachability(backend, result.unreachable);
320
- return result;
363
+ return withCredentialTimeout(
364
+ async () => {
365
+ const backend = await resolveBackendAsync();
366
+ const result = await backend.list();
367
+ updateCesHttpReachability(backend, result.unreachable);
368
+ return result;
369
+ },
370
+ { accounts: [], unreachable: true },
371
+ );
321
372
  }
322
373
 
323
374
  // ---------------------------------------------------------------------------
@@ -336,13 +387,18 @@ export async function listSecureKeysAsync(): Promise<CredentialListResult> {
336
387
  export async function getSecureKeyResultAsync(
337
388
  account: string,
338
389
  ): Promise<SecureKeyResult> {
339
- const backend = await resolveBackendAsync();
340
- const result = await backend.get(account);
341
- updateCesHttpReachability(backend, result.unreachable);
342
- if (result.value != null) {
343
- return { value: result.value, unreachable: false };
344
- }
345
- return { value: undefined, unreachable: result.unreachable };
390
+ return withCredentialTimeout(
391
+ async () => {
392
+ const backend = await resolveBackendAsync();
393
+ const result = await backend.get(account);
394
+ updateCesHttpReachability(backend, result.unreachable);
395
+ if (result.value != null) {
396
+ return { value: result.value, unreachable: false };
397
+ }
398
+ return { value: undefined, unreachable: result.unreachable };
399
+ },
400
+ { value: undefined, unreachable: true },
401
+ );
346
402
  }
347
403
 
348
404
  /**
@@ -364,16 +420,18 @@ export async function setSecureKeyAsync(
364
420
  account: string,
365
421
  value: string,
366
422
  ): Promise<boolean> {
367
- const backend = await resolveBackendAsync();
368
- const ok = await backend.set(account, value);
369
- if (!ok) {
370
- log.warn(
371
- { account, backend: backend.name },
372
- "Credential backend set failed",
373
- );
374
- }
375
- updateCesHttpReachability(backend, !ok);
376
- return ok;
423
+ return withCredentialTimeout(async () => {
424
+ const backend = await resolveBackendAsync();
425
+ const ok = await backend.set(account, value);
426
+ if (!ok) {
427
+ log.warn(
428
+ { account, backend: backend.name },
429
+ "Credential backend set failed",
430
+ );
431
+ }
432
+ updateCesHttpReachability(backend, !ok);
433
+ return ok;
434
+ }, false);
377
435
  }
378
436
 
379
437
  /**
@@ -384,10 +442,45 @@ export async function setSecureKeyAsync(
384
442
  export async function deleteSecureKeyAsync(
385
443
  account: string,
386
444
  ): Promise<DeleteResult> {
387
- const backend = await resolveBackendAsync();
388
- const result = await backend.delete(account);
389
- updateCesHttpReachability(backend, result === "error");
390
- return result;
445
+ return withCredentialTimeout(async () => {
446
+ const backend = await resolveBackendAsync();
447
+ const result = await backend.delete(account);
448
+ updateCesHttpReachability(backend, result === "error");
449
+ return result;
450
+ }, "error");
451
+ }
452
+
453
+ /**
454
+ * Bulk-set multiple credentials in a single operation.
455
+ *
456
+ * Uses the backend's native `bulkSet` when available (CES RPC / HTTP),
457
+ * otherwise falls back to individual `set` calls.
458
+ */
459
+ export async function bulkSetSecureKeysAsync(
460
+ credentials: Array<{ account: string; value: string }>,
461
+ ): Promise<Array<{ account: string; ok: boolean }>> {
462
+ return withCredentialTimeout(
463
+ async () => {
464
+ const backend = await resolveBackendAsync();
465
+ if (backend.bulkSet) {
466
+ const results = await backend.bulkSet(credentials);
467
+ const anyFailed = results.some((r) => !r.ok);
468
+ updateCesHttpReachability(backend, anyFailed);
469
+ return results;
470
+ }
471
+ // Fallback: loop individual sets
472
+ const results = [];
473
+ let anyFailed = false;
474
+ for (const { account, value } of credentials) {
475
+ const ok = await backend.set(account, value);
476
+ if (!ok) anyFailed = true;
477
+ results.push({ account, ok });
478
+ }
479
+ updateCesHttpReachability(backend, anyFailed);
480
+ return results;
481
+ },
482
+ credentials.map((c) => ({ account: c.account, ok: false })),
483
+ );
391
484
  }
392
485
 
393
486
  // ---------------------------------------------------------------------------
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Token manager for OAuth2 credentials.
3
3
  *
4
- * Reads refresh configuration (tokenUrl, clientId, authMethod) exclusively
4
+ * Reads refresh configuration (refreshUrl with fallback to tokenExchangeUrl, clientId, authMethod) exclusively
5
5
  * from the SQLite oauth-store (provider + app + connection rows). After a
6
6
  * successful refresh, writes tokens to new-format secure key paths and
7
7
  * updates the oauth_connection row.
@@ -90,7 +90,12 @@ const secureKeyBackend: SecureKeyBackend = {
90
90
 
91
91
  /** Shared shape for resolved refresh configuration. */
92
92
  interface RefreshConfig {
93
- tokenUrl: string;
93
+ /**
94
+ * Token endpoint used for the refresh grant. Resolved from
95
+ * `provider.refreshUrl` when set to a non-empty string, otherwise
96
+ * `provider.tokenExchangeUrl` (matching platform's Python `or` semantics).
97
+ */
98
+ tokenExchangeUrl: string;
94
99
  clientId: string;
95
100
  /** OAuth client secret (optional — PKCE flows may omit it). */
96
101
  secret?: string;
@@ -102,8 +107,9 @@ interface RefreshConfig {
102
107
  /**
103
108
  * Resolve refresh configuration from the SQLite oauth-store.
104
109
  *
105
- * Looks up connection -> app -> provider to read tokenUrl, clientId, and
106
- * authMethod. Throws `TokenExpiredError` if the connection is not found
110
+ * Looks up connection -> app -> provider to read the refresh endpoint (preferring
111
+ * `provider.refreshUrl`, falling back to `provider.tokenExchangeUrl`), clientId,
112
+ * and authMethod. Throws `TokenExpiredError` if the connection is not found
107
113
  * or incomplete.
108
114
  */
109
115
  async function resolveRefreshConfig(
@@ -126,7 +132,7 @@ async function resolveRefreshConfig(
126
132
  );
127
133
  }
128
134
 
129
- const provider = getProvider(conn.providerKey);
135
+ const provider = getProvider(conn.provider);
130
136
  if (!provider) {
131
137
  throw new TokenExpiredError(
132
138
  service,
@@ -134,9 +140,16 @@ async function resolveRefreshConfig(
134
140
  );
135
141
  }
136
142
 
137
- const tokenUrl = provider.tokenUrl;
143
+ // Prefer provider.refreshUrl when set; fall back to tokenExchangeUrl.
144
+ // This mirrors platform's `oauth_app.refresh_url or oauth_app.token_exchange_url`
145
+ // in `token_service.py:112`, so both repos resolve the refresh endpoint
146
+ // identically for managed and BYO flows. We use `||` (not `??`) so empty
147
+ // strings fall back to tokenExchangeUrl — matching Python's `or` semantics
148
+ // and preventing a malformed provider row with `refreshUrl: ""` from
149
+ // resolving to an empty endpoint.
150
+ const tokenExchangeUrl = provider.refreshUrl || provider.tokenExchangeUrl;
138
151
  const resolvedClientId = app.clientId;
139
- if (!tokenUrl || !resolvedClientId) {
152
+ if (!tokenExchangeUrl || !resolvedClientId) {
140
153
  throw new TokenExpiredError(
141
154
  service,
142
155
  `Missing OAuth2 refresh config for "${service}".${recoveryHint(service)}`,
@@ -155,7 +168,7 @@ async function resolveRefreshConfig(
155
168
 
156
169
  return {
157
170
  connId: conn.id,
158
- tokenUrl,
171
+ tokenExchangeUrl,
159
172
  clientId: resolvedClientId,
160
173
  secret,
161
174
  refreshToken,
@@ -175,7 +188,7 @@ async function resolveRefreshConfig(
175
188
  async function doRefresh(service: string, connId: string): Promise<string> {
176
189
  const refreshConfig = await resolveRefreshConfig(service, connId);
177
190
  const {
178
- tokenUrl,
191
+ tokenExchangeUrl,
179
192
  clientId: resolvedClientId,
180
193
  secret,
181
194
  authMethod,
@@ -204,7 +217,7 @@ async function doRefresh(service: string, connId: string): Promise<string> {
204
217
  let result;
205
218
  try {
206
219
  result = await refreshOAuth2Token(
207
- tokenUrl,
220
+ tokenExchangeUrl,
208
221
  resolvedClientId,
209
222
  refreshToken,
210
223
  secret,