@vellumai/assistant 0.12.1 → 0.12.2-staging.2

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 (577) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
  6. package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
  7. package/openapi.yaml +360 -51
  8. package/package.json +3 -1
  9. package/scripts/smoke-desktop-window-theme.ts +186 -0
  10. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  11. package/src/__tests__/anthropic-provider.test.ts +0 -63
  12. package/src/__tests__/app-compiler.test.ts +6 -0
  13. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  14. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  15. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  16. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  17. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  18. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  19. package/src/__tests__/computer-use-tools.test.ts +19 -7
  20. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  21. package/src/__tests__/config-schema.test.ts +28 -29
  22. package/src/__tests__/config-watcher.test.ts +23 -8
  23. package/src/__tests__/connection-model-compat.test.ts +2 -2
  24. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  25. package/src/__tests__/conversation-pairing.test.ts +26 -0
  26. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  27. package/src/__tests__/conversation-queue.test.ts +48 -207
  28. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  29. package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
  30. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  31. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  32. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  35. package/src/__tests__/conversation-title-service.test.ts +14 -10
  36. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  37. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  38. package/src/__tests__/credential-record-retire.test.ts +171 -1
  39. package/src/__tests__/credential-routes.test.ts +250 -3
  40. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  41. package/src/__tests__/cu-unified-flow.test.ts +14 -4
  42. package/src/__tests__/document-sync-tags.test.ts +25 -0
  43. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  44. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  45. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  46. package/src/__tests__/git-porcelain.test.ts +47 -0
  47. package/src/__tests__/heartbeat-service.test.ts +56 -40
  48. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  49. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  50. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  51. package/src/__tests__/host-cu-proxy.test.ts +596 -0
  52. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  53. package/src/__tests__/host-file-proxy.test.ts +11 -23
  54. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  55. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  56. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  57. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  58. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  59. package/src/__tests__/mcp-health-check.test.ts +8 -11
  60. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  61. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  62. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  63. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  64. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  65. package/src/__tests__/model-intents.test.ts +3 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  67. package/src/__tests__/oauth-commands-routes.test.ts +168 -0
  68. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  69. package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
  70. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  71. package/src/__tests__/openai-provider.test.ts +1 -0
  72. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  73. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  74. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  75. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  76. package/src/__tests__/qdrant-manager.test.ts +132 -0
  77. package/src/__tests__/resolve-app.test.ts +135 -0
  78. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  79. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  80. package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
  81. package/src/__tests__/service-account-token.test.ts +247 -0
  82. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  83. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +9 -4
  85. package/src/__tests__/source-fingerprint.test.ts +184 -0
  86. package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
  87. package/src/__tests__/subagent-tools.test.ts +3 -2
  88. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  89. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  90. package/src/__tests__/ui-channel-variants.test.ts +1 -56
  91. package/src/__tests__/unicode.test.ts +13 -0
  92. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  93. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  94. package/src/__tests__/workspace-git-service.test.ts +26 -0
  95. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
  97. package/src/acp/__tests__/acp-credentials.test.ts +13 -0
  98. package/src/acp/__tests__/agent-process.test.ts +258 -4
  99. package/src/acp/__tests__/auth-required.test.ts +96 -0
  100. package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
  101. package/src/acp/__tests__/client-handler.test.ts +90 -2
  102. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  103. package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
  104. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  105. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  106. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  107. package/src/acp/__tests__/session-manager.test.ts +895 -4
  108. package/src/acp/acp-claude-oauth.ts +328 -14
  109. package/src/acp/acp-credentials.ts +19 -0
  110. package/src/acp/agent-process.test.ts +4 -1
  111. package/src/acp/agent-process.ts +96 -10
  112. package/src/acp/auth-required.ts +50 -1
  113. package/src/acp/auto-install.test.ts +9 -7
  114. package/src/acp/claude-token-refresh.ts +150 -0
  115. package/src/acp/client-handler.ts +16 -3
  116. package/src/acp/model-config.test.ts +166 -0
  117. package/src/acp/model-config.ts +138 -0
  118. package/src/acp/prepare-agent-env.ts +21 -6
  119. package/src/acp/resolve-agent.test.ts +220 -6
  120. package/src/acp/resolve-agent.ts +88 -23
  121. package/src/acp/session-manager.test.ts +196 -3
  122. package/src/acp/session-manager.ts +441 -18
  123. package/src/acp/types.ts +50 -5
  124. package/src/agent/loop.ts +1 -0
  125. package/src/api/events/acp-session-model-update.test.ts +88 -0
  126. package/src/api/events/acp-session-model-update.ts +46 -0
  127. package/src/api/events/app-files-changed.ts +4 -3
  128. package/src/api/events/notification-intent.ts +2 -0
  129. package/src/api/events/user-message-echo.ts +7 -1
  130. package/src/api/index.ts +6 -0
  131. package/src/api/responses/conversation-message.ts +5 -0
  132. package/src/apps/app-store.ts +1 -1
  133. package/src/apps/resolve-app.ts +81 -0
  134. package/src/apps/source-fingerprint.ts +266 -0
  135. package/src/avatar/identity-avatar.ts +7 -2
  136. package/src/bundler/app-compiler.ts +15 -0
  137. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  138. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  139. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  140. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  141. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  142. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  143. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  144. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  145. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  146. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  147. package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
  148. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  149. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  150. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  151. package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
  152. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  153. package/src/calls/barge-in-guard.ts +118 -0
  154. package/src/calls/call-constants.ts +24 -4
  155. package/src/calls/call-controller.ts +499 -199
  156. package/src/calls/call-transport.ts +16 -0
  157. package/src/calls/media-stream-output.ts +32 -0
  158. package/src/calls/media-stream-server.ts +82 -30
  159. package/src/calls/media-stream-stt-session.ts +15 -0
  160. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  161. package/src/calls/resolve-call-tts-provider.ts +33 -2
  162. package/src/calls/tts-call-strategy.ts +9 -5
  163. package/src/calls/types.ts +2 -0
  164. package/src/calls/voice-control-protocol.ts +155 -21
  165. package/src/calls/voice-leg-coordinator.ts +189 -0
  166. package/src/calls/voice-progress-cadence.ts +423 -0
  167. package/src/calls/voice-session-bridge.ts +92 -72
  168. package/src/calls/voice-triage-escalate.ts +178 -59
  169. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  170. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  171. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  172. package/src/cli/commands/apps.help.ts +67 -13
  173. package/src/cli/commands/apps.ts +183 -0
  174. package/src/cli/commands/bash.help.ts +8 -13
  175. package/src/cli/commands/bash.ts +7 -25
  176. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  177. package/src/cli/commands/channels/index.help.ts +70 -2
  178. package/src/cli/commands/channels/index.ts +16 -3
  179. package/src/cli/commands/channels/send.ts +128 -0
  180. package/src/cli/commands/credentials.help.ts +6 -2
  181. package/src/cli/commands/credentials.ts +2 -2
  182. package/src/cli/commands/mcp.help.ts +8 -8
  183. package/src/cli/commands/mcp.ts +1 -1
  184. package/src/cli/commands/oauth/status.ts +5 -0
  185. package/src/cli/commands/roadmap.help.ts +5 -1
  186. package/src/cli/commands/roadmap.ts +1 -1
  187. package/src/cli/lib/bundled-marketplace.json +103 -90
  188. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  189. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  190. package/src/config/__tests__/platform-identity.test.ts +255 -0
  191. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  192. package/src/config/acp-agent-write.ts +43 -0
  193. package/src/config/acp-defaults.test.ts +13 -3
  194. package/src/config/acp-defaults.ts +23 -6
  195. package/src/config/acp-schema.test.ts +81 -0
  196. package/src/config/acp-schema.ts +30 -2
  197. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  198. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  199. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  200. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  201. package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
  202. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  203. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  204. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  205. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  206. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  207. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  208. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  209. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  210. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  211. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  212. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  213. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  214. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  215. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  216. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  217. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  218. package/src/config/default-profile-catalog.ts +1 -1
  219. package/src/config/env.ts +2 -2
  220. package/src/config/feature-flag-registry.json +33 -13
  221. package/src/config/loader.ts +2 -0
  222. package/src/config/memory-v3-gate.test.ts +31 -0
  223. package/src/config/memory-v3-gate.ts +20 -16
  224. package/src/config/platform-identity.ts +201 -0
  225. package/src/config/platform-rehydration.ts +11 -27
  226. package/src/config/schema.ts +2 -2
  227. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  228. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  229. package/src/config/schemas/calls.ts +1 -28
  230. package/src/config/schemas/live-voice.ts +1 -173
  231. package/src/config/schemas/mcp.ts +1 -1
  232. package/src/config/schemas/memory-retrospective.ts +10 -0
  233. package/src/config/schemas/services.ts +18 -0
  234. package/src/config/schemas/skills.ts +0 -14
  235. package/src/config/schemas/voice.ts +150 -0
  236. package/src/credential-execution/process-manager.test.ts +18 -19
  237. package/src/credential-health/credential-health-service.ts +11 -17
  238. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  239. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  240. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  241. package/src/daemon/app-change-notify.ts +45 -0
  242. package/src/daemon/config-watcher.ts +13 -194
  243. package/src/daemon/conversation-error.ts +3 -2
  244. package/src/daemon/conversation-media-retry.ts +2 -1
  245. package/src/daemon/conversation-process.ts +35 -64
  246. package/src/daemon/conversation-runtime-assembly.ts +20 -7
  247. package/src/daemon/conversation-skill-tools.ts +13 -31
  248. package/src/daemon/conversation-store.ts +1 -1
  249. package/src/daemon/conversation-surfaces.ts +21 -10
  250. package/src/daemon/conversation-tool-setup.ts +45 -59
  251. package/src/daemon/conversation.ts +0 -3
  252. package/src/daemon/dictation-profile-store.ts +2 -1
  253. package/src/daemon/handlers/shared.ts +2 -1
  254. package/src/daemon/handlers/skills.ts +6 -3
  255. package/src/daemon/host-bash-proxy.ts +8 -12
  256. package/src/daemon/host-browser-proxy.ts +15 -1
  257. package/src/daemon/host-cu-proxy.ts +224 -19
  258. package/src/daemon/host-file-proxy.ts +8 -10
  259. package/src/daemon/host-proxy-preactivation.ts +2 -1
  260. package/src/daemon/host-transfer-proxy.ts +14 -18
  261. package/src/daemon/lifecycle.ts +0 -5
  262. package/src/daemon/mcp-reload-service.ts +5 -5
  263. package/src/daemon/message-types/sync.ts +1 -0
  264. package/src/daemon/orphan-reaper.ts +1 -1
  265. package/src/daemon/providers-setup.ts +1 -1
  266. package/src/daemon/reaction-history-render.ts +2 -1
  267. package/src/daemon/shutdown-handlers.ts +1 -9
  268. package/src/daemon/skill-memory-refresh.ts +64 -0
  269. package/src/daemon/tool-side-effects.ts +7 -18
  270. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  271. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  272. package/src/desktop/desktop-chrome-policy.ts +47 -0
  273. package/src/desktop/desktop-chrome-session.ts +37 -0
  274. package/src/desktop/desktop-dependencies.test.ts +102 -0
  275. package/src/desktop/desktop-dependencies.ts +308 -0
  276. package/src/desktop/desktop-feature.test.ts +18 -0
  277. package/src/desktop/desktop-feature.ts +19 -0
  278. package/src/desktop/desktop-panel-config.test.ts +135 -0
  279. package/src/desktop/desktop-panel-config.ts +149 -0
  280. package/src/desktop/desktop-session-manager.test.ts +730 -0
  281. package/src/desktop/desktop-session-manager.ts +850 -0
  282. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  283. package/src/desktop/desktop-stream-bridge.ts +214 -0
  284. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  285. package/src/desktop/desktop-wallpaper.ts +113 -0
  286. package/src/desktop/desktop-window-drag.ts +177 -0
  287. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  288. package/src/desktop/desktop-window-manager-config.ts +238 -0
  289. package/src/desktop/desktop-window-theme.ts +170 -0
  290. package/src/heartbeat/heartbeat-service.ts +21 -3
  291. package/src/home/__tests__/feed-writer.test.ts +22 -0
  292. package/src/home/feed-writer.ts +11 -2
  293. package/src/hooks/types.ts +33 -0
  294. package/src/i18n/AGENTS.md +22 -0
  295. package/src/i18n/__tests__/i18n.test.ts +108 -0
  296. package/src/i18n/index.ts +16 -0
  297. package/src/i18n/locales.ts +93 -0
  298. package/src/i18n/messages.ts +61 -0
  299. package/src/i18n/title.ts +25 -0
  300. package/src/inbound/platform-callback-registration.ts +4 -5
  301. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
  302. package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
  303. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  304. package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
  305. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  306. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  307. package/src/live-voice/__tests__/protocol.test.ts +39 -0
  308. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  309. package/src/live-voice/__tests__/session-controls.test.ts +79 -0
  310. package/src/live-voice/live-voice-photo.ts +25 -6
  311. package/src/live-voice/live-voice-session.ts +297 -699
  312. package/src/live-voice/protocol.ts +60 -2
  313. package/src/live-voice/session-controls.ts +111 -0
  314. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  315. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  316. package/src/mcp/effective-config.ts +3 -3
  317. package/src/mcp/mcp-header-store.ts +29 -45
  318. package/src/mcp/spec-schema.ts +111 -0
  319. package/src/mcp/startup.ts +8 -4
  320. package/src/mcp/workspace-mcp-config.ts +188 -0
  321. package/src/messaging/provider.ts +10 -3
  322. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  323. package/src/messaging/providers/callback-routing.ts +18 -0
  324. package/src/messaging/providers/channel-transport.ts +56 -0
  325. package/src/messaging/providers/discord/transport.ts +17 -0
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  327. package/src/messaging/providers/slack/adapter.ts +7 -44
  328. package/src/messaging/providers/slack/transport.ts +16 -0
  329. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  330. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  331. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  332. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  333. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  334. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  335. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  336. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  337. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  338. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  339. package/src/notifications/adapters/macos.ts +3 -0
  340. package/src/notifications/assistant-reply-producer.ts +8 -3
  341. package/src/notifications/conversation-pairing.ts +36 -16
  342. package/src/notifications/delivered-post-record.ts +11 -1
  343. package/src/notifications/guardian-feed-projection.ts +125 -5
  344. package/src/notifications/home-feed-side-effect.ts +55 -7
  345. package/src/notifications/schedule-result-producer.ts +66 -22
  346. package/src/oauth/AGENTS.md +2 -0
  347. package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
  348. package/src/oauth/byo-connection.test.ts +105 -0
  349. package/src/oauth/byo-connection.ts +36 -6
  350. package/src/oauth/connection-resolver.test.ts +100 -0
  351. package/src/oauth/connection-resolver.ts +120 -3
  352. package/src/oauth/provider-serializer.ts +9 -0
  353. package/src/oauth/scope-utils.ts +24 -0
  354. package/src/oauth/seed-providers.ts +314 -27
  355. package/src/outbound-proxy/certs.ts +3 -5
  356. package/src/permissions/checker.test.ts +11 -6
  357. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  358. package/src/persistence/conversation-crud.ts +71 -63
  359. package/src/persistence/conversation-lineage.ts +12 -4
  360. package/src/persistence/conversation-queries.ts +2 -2
  361. package/src/persistence/conversation-title-placeholders.ts +23 -0
  362. package/src/persistence/conversation-title-service.ts +9 -26
  363. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  364. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  365. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  366. package/src/persistence/message-cursor.ts +78 -0
  367. package/src/persistence/message-reads.ts +30 -0
  368. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  369. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  370. package/src/persistence/schema/memory-core.ts +5 -0
  371. package/src/platform/client.test.ts +4 -2
  372. package/src/platform/client.ts +2 -10
  373. package/src/plugin-api/constants.ts +2 -0
  374. package/src/plugin-api/index.ts +1 -0
  375. package/src/plugin-api/types.ts +2 -0
  376. package/src/plugins/defaults/index.ts +2 -0
  377. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  378. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  379. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  380. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  381. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  382. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  383. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  384. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  385. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  386. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  387. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  388. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  389. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  390. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  391. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  392. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  393. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  394. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  395. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  396. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  397. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  398. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  399. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  400. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  401. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  402. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  403. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  404. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  405. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  406. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  407. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  408. package/src/plugins/defaults/memory/startup.ts +28 -2
  409. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  410. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  411. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  412. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  413. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  414. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  415. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  416. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  417. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  418. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  419. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  420. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  421. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  422. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  423. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  424. package/src/plugins/defaults/memory/worker.ts +6 -0
  425. package/src/plugins/mcp-servers.ts +15 -75
  426. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  427. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  428. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  429. package/src/providers/anthropic/client.ts +0 -47
  430. package/src/providers/gemini/client.ts +9 -23
  431. package/src/providers/gemini-thought-signature.test.ts +63 -0
  432. package/src/providers/gemini-thought-signature.ts +44 -0
  433. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  434. package/src/providers/inference/auth.ts +2 -3
  435. package/src/providers/inference/connection-availability.ts +27 -8
  436. package/src/providers/inference/endpoint-probe.ts +10 -1
  437. package/src/providers/inference/resolve-auth.ts +22 -4
  438. package/src/providers/inference/service-account-token.ts +248 -0
  439. package/src/providers/model-catalog.ts +34 -0
  440. package/src/providers/model-intents.ts +10 -5
  441. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  442. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  443. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  444. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  445. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  446. package/src/providers/openai/chat-completions-provider.ts +212 -79
  447. package/src/providers/openai/codex-models.ts +0 -4
  448. package/src/providers/openai/google-thought-signature.ts +320 -0
  449. package/src/providers/opencode/client.test.ts +73 -4
  450. package/src/providers/opencode/client.ts +60 -3
  451. package/src/providers/openrouter/client.ts +20 -0
  452. package/src/providers/outbound-request-sanitize.ts +96 -0
  453. package/src/providers/retry.ts +30 -19
  454. package/src/providers/unparseable-tool-args.ts +5 -1
  455. package/src/runtime/AGENTS.md +3 -1
  456. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  457. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  458. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  459. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  460. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  461. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  462. package/src/runtime/agent-wake.ts +2 -1
  463. package/src/runtime/assistant-event-hub.ts +21 -3
  464. package/src/runtime/auth/same-actor.ts +38 -3
  465. package/src/runtime/channel-send.ts +316 -0
  466. package/src/runtime/host-observe.ts +11 -1
  467. package/src/runtime/http-server.ts +160 -151
  468. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
  469. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  470. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
  471. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  472. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  473. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  474. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  475. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  476. package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
  477. package/src/runtime/routes/acp-routes.test.ts +15 -0
  478. package/src/runtime/routes/acp-routes.ts +72 -9
  479. package/src/runtime/routes/app-management-routes.ts +90 -2
  480. package/src/runtime/routes/app-routes.ts +1 -1
  481. package/src/runtime/routes/channel-send-routes.ts +85 -0
  482. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  483. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  484. package/src/runtime/routes/conversation-routes.ts +16 -3
  485. package/src/runtime/routes/credential-routes.ts +20 -14
  486. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  487. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  488. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  489. package/src/runtime/routes/events-routes.ts +4 -0
  490. package/src/runtime/routes/host-cu-routes.ts +6 -0
  491. package/src/runtime/routes/index.ts +4 -2
  492. package/src/runtime/routes/log-export-routes.ts +2 -0
  493. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  494. package/src/runtime/routes/migration-routes.ts +6 -20
  495. package/src/runtime/routes/oauth-commands-routes.ts +50 -0
  496. package/src/runtime/routes/oauth-providers.ts +7 -0
  497. package/src/runtime/routes/platform-routes.ts +18 -15
  498. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  499. package/src/runtime/routes/workspace-routes.ts +46 -63
  500. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  501. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  502. package/src/runtime/routes/workspace-utils.ts +17 -0
  503. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  504. package/src/runtime/services/conversation-serializer.ts +16 -3
  505. package/src/runtime/sync/resource-sync-events.ts +2 -1
  506. package/src/schedule/run-script.ts +2 -1
  507. package/src/schedule/schedule-store.ts +9 -2
  508. package/src/schedule/worker.ts +1 -1
  509. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  510. package/src/security/ces-http-record-backend.ts +120 -0
  511. package/src/security/ces-rpc-record-backend.ts +7 -0
  512. package/src/security/untrusted-content.ts +3 -1
  513. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  514. package/src/skills/workspace-skill-md-signature.ts +50 -0
  515. package/src/subagent/consult-context.ts +12 -25
  516. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  517. package/src/tools/acp/list-agents.test.ts +3 -1
  518. package/src/tools/acp/spawn.test.ts +146 -11
  519. package/src/tools/acp/spawn.ts +21 -3
  520. package/src/tools/acp/status.test.ts +94 -0
  521. package/src/tools/acp/status.ts +40 -3
  522. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  523. package/src/tools/browser/browser-execution.ts +1 -1
  524. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  525. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  526. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  527. package/src/tools/browser/network-recorder.ts +2 -1
  528. package/src/tools/capability-offer.test.ts +22 -2
  529. package/src/tools/capability-offer.ts +16 -1
  530. package/src/tools/computer-use/definitions.ts +121 -2
  531. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  532. package/src/tools/credentials/metadata-store.ts +100 -0
  533. package/src/tools/filesystem/search.ts +2 -1
  534. package/src/tools/host-filesystem/edit.test.ts +2 -1
  535. package/src/tools/host-filesystem/edit.ts +2 -2
  536. package/src/tools/host-filesystem/read.test.ts +2 -1
  537. package/src/tools/host-filesystem/read.ts +2 -2
  538. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  539. package/src/tools/host-filesystem/transfer.ts +2 -2
  540. package/src/tools/host-filesystem/write.test.ts +2 -1
  541. package/src/tools/host-filesystem/write.ts +2 -2
  542. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  543. package/src/tools/host-terminal/host-shell.ts +4 -20
  544. package/src/tools/network/web-search.ts +6 -1
  545. package/src/tools/policy-context.ts +6 -7
  546. package/src/tools/skills/scaffold-managed.ts +61 -3
  547. package/src/tools/subagent/spawn.ts +5 -8
  548. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  549. package/src/tools/terminal/sanitized-bash.ts +105 -0
  550. package/src/tools/ui-surface/channel-variants.ts +10 -59
  551. package/src/tools/watch/watch-retro-report.ts +0 -8
  552. package/src/types/host-capabilities.ts +5 -3
  553. package/src/util/__tests__/language-subtag.test.ts +23 -1
  554. package/src/util/ca-bundle.ts +13 -0
  555. package/src/util/language-subtag.ts +16 -0
  556. package/src/util/platform.ts +8 -0
  557. package/src/util/unicode.ts +15 -1
  558. package/src/watch/watch-retro.ts +6 -8
  559. package/src/watch/watch-timeline.ts +2 -2
  560. package/src/workflows/run-manager.ts +2 -1
  561. package/src/workspace/git-porcelain.ts +66 -0
  562. package/src/workspace/git-service.ts +6 -55
  563. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  564. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  565. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  566. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  567. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  568. package/src/workspace/migrations/registry.ts +6 -0
  569. package/src/__tests__/app-source-watcher.test.ts +0 -218
  570. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  571. package/src/__tests__/speaker-identification.test.ts +0 -56
  572. package/src/calls/speaker-identification.ts +0 -233
  573. package/src/daemon/app-source-watcher.ts +0 -218
  574. package/src/messaging/providers/whatsapp/client.ts +0 -29
  575. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  576. package/src/util/fs-watcher-error.ts +0 -36
  577. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  } from "../config/schemas/services.js";
7
7
  import { VellumPlatformClient } from "../platform/client.js";
8
8
  import { getLogger } from "../util/logger.js";
9
- import { BYOOAuthConnection } from "./byo-connection.js";
9
+ import { BYOOAuthConnection, type BYOTokenHeader } from "./byo-connection.js";
10
10
  import type { OAuthConnection } from "./connection.js";
11
11
  import { getConnectionAccessTokenResult } from "./credential-token-resolver.js";
12
12
  import { syncManualTokenConnection } from "./manual-token-connection.js";
@@ -129,7 +129,10 @@ export async function resolveOAuthConnectionWithMeta(
129
129
  accountInfo: resolution.accountLabel ?? account ?? null,
130
130
  client,
131
131
  connectionId: resolution.id,
132
- baseUrl: providerRow?.baseUrl ?? undefined,
132
+ // A templated base URL (`https://{tenant_host}`, `.../{realm_id}`) is
133
+ // filled in by the platform from what it pinned to the connection;
134
+ // sent as-is it would fail the proxy's allowlist and shadow that.
135
+ baseUrl: platformProxyBaseUrl(providerRow?.baseUrl),
133
136
  });
134
137
  return {
135
138
  connection,
@@ -215,15 +218,126 @@ export async function resolveOAuthConnectionWithMeta(
215
218
  );
216
219
  }
217
220
 
221
+ const effectiveBaseUrl = resolveEffectiveBaseUrl(
222
+ conn.provider,
223
+ baseUrl,
224
+ conn.metadata,
225
+ );
218
226
  const connection = new BYOOAuthConnection({
219
227
  id: conn.id,
220
228
  provider: conn.provider,
221
- baseUrl: resolveEffectiveBaseUrl(conn.provider, baseUrl, conn.metadata),
229
+ baseUrl: effectiveBaseUrl,
222
230
  accountInfo: conn.accountInfo,
231
+ tokenHeader: resolveTokenHeader(
232
+ providerRow?.injectionTemplates,
233
+ effectiveBaseUrl,
234
+ ),
223
235
  });
224
236
  return { connection, ambiguous, allAccounts };
225
237
  }
226
238
 
239
+ /**
240
+ * Pick the header a provider expects its access token in from the seed's
241
+ * header-type injection templates.
242
+ *
243
+ * Prefers the template whose `hostPattern` matches the base URL's host
244
+ * (`*.myshopify.com` style globs included), then falls back to the first
245
+ * header template. Returns `null` when the provider declares none, which
246
+ * leaves `BYOOAuthConnection` on its `Authorization: Bearer` default.
247
+ *
248
+ * Shopify is why this exists: its Admin API reads `X-Shopify-Access-Token`
249
+ * and ignores `Authorization`, so a Bearer header reaches the shop as an
250
+ * unauthenticated request and comes back 401.
251
+ */
252
+ export function resolveTokenHeader(
253
+ rawTemplates: string | null | undefined,
254
+ baseUrl: string,
255
+ ): BYOTokenHeader | null {
256
+ if (!rawTemplates) {
257
+ return null;
258
+ }
259
+ let parsed: unknown;
260
+ try {
261
+ parsed = JSON.parse(rawTemplates);
262
+ } catch {
263
+ return null;
264
+ }
265
+ if (!Array.isArray(parsed)) {
266
+ return null;
267
+ }
268
+
269
+ const headerTemplates = parsed.filter(
270
+ (
271
+ t,
272
+ ): t is {
273
+ hostPattern?: unknown;
274
+ headerName: string;
275
+ valuePrefix?: unknown;
276
+ } =>
277
+ typeof t === "object" &&
278
+ t !== null &&
279
+ (t as { injectionType?: unknown }).injectionType === "header" &&
280
+ typeof (t as { headerName?: unknown }).headerName === "string" &&
281
+ (t as { headerName: string }).headerName.trim() !== "",
282
+ );
283
+ if (headerTemplates.length === 0) {
284
+ return null;
285
+ }
286
+
287
+ let host = "";
288
+ try {
289
+ host = new URL(baseUrl).hostname.toLowerCase();
290
+ } catch {
291
+ // Templated or malformed base URLs (e.g. a still-unfilled
292
+ // `https://{tenant_host}`) cannot be matched by host; fall through to
293
+ // the first header template.
294
+ }
295
+ const matching =
296
+ host === ""
297
+ ? undefined
298
+ : headerTemplates.find(
299
+ (t) =>
300
+ typeof t.hostPattern === "string" &&
301
+ hostMatchesPattern(host, t.hostPattern),
302
+ );
303
+ const chosen = matching ?? headerTemplates[0];
304
+ return {
305
+ name: chosen.headerName.trim(),
306
+ valuePrefix:
307
+ typeof chosen.valuePrefix === "string" ? chosen.valuePrefix : "",
308
+ };
309
+ }
310
+
311
+ function hostMatchesPattern(host: string, pattern: string): boolean {
312
+ const p = pattern.trim().toLowerCase();
313
+ if (p === "") {
314
+ return false;
315
+ }
316
+ if (p.startsWith("*.")) {
317
+ const suffix = p.slice(1); // ".myshopify.com"
318
+ return host.endsWith(suffix) && host.length > suffix.length;
319
+ }
320
+ return host === p;
321
+ }
322
+
323
+ /**
324
+ * The base URL a managed connection sends to the platform proxy, or
325
+ * `undefined` to let the proxy use the provider's configured default.
326
+ *
327
+ * Per-tenant and per-realm providers seed a template the platform fills from
328
+ * the connection (`{tenant_host}`, `{realm_id}`). Forwarding the unfilled
329
+ * template would fail the proxy's allowlist check, so those fall through to
330
+ * the platform's own default.
331
+ */
332
+ export function platformProxyBaseUrl(
333
+ seedBaseUrl: string | null | undefined,
334
+ ): string | undefined {
335
+ if (!seedBaseUrl || /\{[a-z_]+\}/.test(seedBaseUrl)) {
336
+ return undefined;
337
+ }
338
+ return seedBaseUrl;
339
+ }
340
+
227
341
  /**
228
342
  * Resolve the effective API base URL for a connection, preferring per-tenant
229
343
  * values stored on the connection's `metadata` over the provider's static
@@ -301,6 +415,9 @@ interface PlatformConnectionEntry {
301
415
  /** Scopes the platform actually granted this connection. May be absent for
302
416
  * older connections or providers that don't report scopes. */
303
417
  scopes_granted?: string[] | null;
418
+ /** Provider-supplied values the connection is scoped by (QuickBooks'
419
+ * `realm_id`). Absent from older platforms; empty for most providers. */
420
+ provider_params?: Record<string, string> | null;
304
421
  }
305
422
 
306
423
  interface PlatformConnectionResolution {
@@ -20,6 +20,8 @@ export type SerializedProvider = ReturnType<typeof serializeProvider> &
20
20
 
21
21
  import { isChannelBotProvider } from "@vellumai/service-contracts/channels";
22
22
 
23
+ import { PROVIDER_SEED_DATA } from "./seed-providers.js";
24
+
23
25
  /**
24
26
  * Lightweight summary projection of an OAuth provider, suitable for API
25
27
  * list responses where full detail is not needed. All keys are snake_case
@@ -36,6 +38,12 @@ export interface SerializedProviderSummary {
36
38
  supports_managed_mode: boolean;
37
39
  managed_service_is_paid: boolean;
38
40
  feature_flag: string | null;
41
+ /**
42
+ * Per-tenant providers only (Shopify): what a client must collect before
43
+ * starting a managed connect, since the provider's OAuth endpoints live on
44
+ * the customer's own host. `null` for providers with one global host.
45
+ */
46
+ tenant_host: { pattern: string; label: string; placeholder: string } | null;
39
47
  /**
40
48
  * Which sense of "connected" this provider represents: `assistant` for a bot
41
49
  * credential people reach the assistant through, `user` for a grant letting
@@ -164,6 +172,7 @@ export function serializeProviderSummary(
164
172
  supports_managed_mode: !!row.managedServiceConfigKey,
165
173
  managed_service_is_paid: !!row.managedServiceIsPaid,
166
174
  feature_flag: row.featureFlag ?? null,
175
+ tenant_host: PROVIDER_SEED_DATA[row.provider]?.tenantHost ?? null,
167
176
  acts_as: isChannelBotProvider(row.provider) ? "assistant" : "user",
168
177
  };
169
178
  }
@@ -72,6 +72,30 @@ export function expectedScopesForStoredToken(
72
72
  return parsed.length > 0 ? parsed : defaultScopes;
73
73
  }
74
74
 
75
+ /**
76
+ * The required scopes a stored grant lacks. Requirements come from the request
77
+ * that produced the stored token ({@link expectedScopesForStoredToken}), so a
78
+ * provider's bot-only scopes are never demanded of a user token. An empty
79
+ * requirement or an unrecorded grant reports nothing, because unknown scope
80
+ * data must never read as a failure.
81
+ */
82
+ export function missingScopesForStoredToken(
83
+ defaultScopes: string[],
84
+ authorizeParams: Record<string, string> | undefined,
85
+ scopeSeparator: string | undefined,
86
+ grantedScopes: string[],
87
+ ): string[] {
88
+ const expected = expectedScopesForStoredToken(
89
+ defaultScopes,
90
+ authorizeParams,
91
+ scopeSeparator,
92
+ );
93
+ if (expected.length === 0 || grantedScopes.length === 0) {
94
+ return [];
95
+ }
96
+ return scopeDifference(expected, grantedScopes);
97
+ }
98
+
75
99
  const GMAIL_FULL_ACCESS_SCOPE = "https://mail.google.com/";
76
100
  const GMAIL_READONLY_SCOPE = "https://www.googleapis.com/auth/gmail.readonly";
77
101
 
@@ -9,6 +9,30 @@ import { migrateProviderBaseUrl, seedProviders } from "./oauth-store.js";
9
9
  */
10
10
  const STALE_GOOGLE_BASE_URL = "https://gmail.googleapis.com/gmail/v1/users/me";
11
11
 
12
+ /**
13
+ * The hosts a Slack credential may be sent to, shared by the person-identity
14
+ * `slack` provider and the `slack_channel` bot: both read messages, and a file
15
+ * shared in a message is fetched from the file object's `url_private`,
16
+ * `url_private_download`, or `thumb_*` URL on `files.slack.com` with the same
17
+ * Bearer token as the Web API. Slack documents that one file host by name.
18
+ */
19
+ const SLACK_PROVIDER_INJECTION_TEMPLATES: NonNullable<
20
+ (typeof PROVIDER_SEED_DATA)[string]["injectionTemplates"]
21
+ > = [
22
+ {
23
+ hostPattern: "slack.com",
24
+ injectionType: "header",
25
+ headerName: "Authorization",
26
+ valuePrefix: "Bearer ",
27
+ },
28
+ {
29
+ hostPattern: "files.slack.com",
30
+ injectionType: "header",
31
+ headerName: "Authorization",
32
+ valuePrefix: "Bearer ",
33
+ },
34
+ ];
35
+
12
36
  /**
13
37
  * Protocol-level seed data for each well-known OAuth provider.
14
38
  *
@@ -66,6 +90,13 @@ export const PROVIDER_SEED_DATA: Record<
66
90
  }>;
67
91
  appType?: string;
68
92
  setupNotes?: string[];
93
+ /**
94
+ * Per-tenant providers only: the host the user supplies at connect time,
95
+ * which the platform substitutes into `{tenant_host}` URL placeholders.
96
+ * `pattern` mirrors the platform registry's validation so clients can
97
+ * reject a malformed host before the request leaves the browser.
98
+ */
99
+ tenantHost?: { pattern: string; label: string; placeholder: string };
69
100
  identityUrl?: string;
70
101
  identityMethod?: string;
71
102
  identityHeaders?: Record<string, string>;
@@ -183,14 +214,13 @@ export const PROVIDER_SEED_DATA: Record<
183
214
  clientIdPlaceholder: null,
184
215
  logoUrl:
185
216
  "https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/slack/default.svg",
186
- // Sent as the bot `scope` parameter, while this flow persists the
187
- // `authed_user` token and stores `authed_user.scope` as `grantedScopes`.
188
- // Credential health compares this list against that grant, so a scope
189
- // listed here that `user_scope` does not also request reports as
190
- // `missing_scopes`, a hard failure that disables the provider's tools.
191
- // The wider bot set belongs to Socket Mode installs, which verify the live
192
- // `x-oauth-scopes` header against SLACK_REQUIRED_BOT_SCOPES in
193
- // channel-readiness-service.ts.
217
+ // Sent as the bot `scope` parameter on the same authorize request. This
218
+ // flow persists the `authed_user` token and stores `authed_user.scope` as
219
+ // `grantedScopes`; credential health measures that grant against
220
+ // `user_scope` below, never against this list, so a scope the stored
221
+ // token needs belongs in `user_scope`. The wider bot set belongs to Socket
222
+ // Mode installs, which verify the live `x-oauth-scopes` header against
223
+ // SLACK_REQUIRED_BOT_SCOPES in channel-readiness-service.ts.
194
224
  defaultScopes: [
195
225
  "channels:join",
196
226
  "channels:read",
@@ -212,19 +242,17 @@ export const PROVIDER_SEED_DATA: Record<
212
242
  // only credential this flow stores, so it acts as the installer for every
213
243
  // call including `chat.postMessage`. The manifest's user list is read-only
214
244
  // instead, since a Socket Mode install keeps a bot token beside it.
245
+ // `files:read` is what lets that token fetch a file shared in a message
246
+ // from files.slack.com. Credential health measures every stored grant
247
+ // against this list, so a scope added here reports each existing
248
+ // connection as `missing_scopes` until it reconnects: that is how a newly
249
+ // required scope reaches an install.
215
250
  authorizeParams: {
216
251
  user_scope:
217
- "channels:read,channels:history,groups:read,groups:history,im:read,im:history,im:write,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write",
252
+ "channels:read,channels:history,groups:read,groups:history,im:read,im:history,im:write,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write,files:read",
218
253
  },
219
254
  loopbackPort: 17322,
220
- injectionTemplates: [
221
- {
222
- hostPattern: "slack.com",
223
- injectionType: "header",
224
- headerName: "Authorization",
225
- valuePrefix: "Bearer ",
226
- },
227
- ],
255
+ injectionTemplates: SLACK_PROVIDER_INJECTION_TEMPLATES,
228
256
  appType: "Slack App",
229
257
  identityUrl: "https://slack.com/api/auth.test",
230
258
  identityOkField: "ok",
@@ -982,7 +1010,12 @@ export const PROVIDER_SEED_DATA: Record<
982
1010
  // default, because Figma rejects the whole authorization request if the
983
1011
  // app cannot grant a requested scope. `selections:read` is withheld for
984
1012
  // that same reason: it is not offered in the app's OAuth scope list, so
985
- // requesting it would fail the entire authorization.
1013
+ // requesting it would fail the entire authorization. `folders:read` is
1014
+ // withheld likewise: Figma documents it, but the app's scope picker only
1015
+ // offers `folder_metadata:read` under Folders. `file_code_connect:write`
1016
+ // is withheld because Figma only offers it to apps owned by an
1017
+ // Organization-plan team, which not every managed app is, and nothing
1018
+ // here uses Code Connect; it stays in availableScopes for BYO apps.
986
1019
  defaultScopes: [
987
1020
  "current_user:read",
988
1021
  "file_content:read",
@@ -992,7 +1025,6 @@ export const PROVIDER_SEED_DATA: Record<
992
1025
  "file_comments:write",
993
1026
  "file_dev_resources:read",
994
1027
  "file_dev_resources:write",
995
- "folders:read",
996
1028
  "folder_metadata:read",
997
1029
  "library_content:read",
998
1030
  "library_assets:read",
@@ -1032,6 +1064,10 @@ export const PROVIDER_SEED_DATA: Record<
1032
1064
  scope: "file_dev_resources:write",
1033
1065
  description: "Write dev resources to files",
1034
1066
  },
1067
+ {
1068
+ scope: "file_code_connect:write",
1069
+ description: "Write and change component code (Code Connect)",
1070
+ },
1035
1071
  {
1036
1072
  scope: "folders:read",
1037
1073
  description: "List folders and files in folders",
@@ -1194,14 +1230,7 @@ export const PROVIDER_SEED_DATA: Record<
1194
1230
  logoUrl:
1195
1231
  "https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/slack/default.svg",
1196
1232
  defaultScopes: [],
1197
- injectionTemplates: [
1198
- {
1199
- hostPattern: "slack.com",
1200
- injectionType: "header",
1201
- headerName: "Authorization",
1202
- valuePrefix: "Bearer ",
1203
- },
1204
- ],
1233
+ injectionTemplates: SLACK_PROVIDER_INJECTION_TEMPLATES,
1205
1234
  },
1206
1235
 
1207
1236
  // The bot that sits in a server and talks to people there, which is a
@@ -1329,6 +1358,264 @@ export const PROVIDER_SEED_DATA: Record<
1329
1358
  identityUrl: "https://api.link.com/userinfo",
1330
1359
  identityResponsePaths: ["email", "phone"],
1331
1360
  },
1361
+ shopify: {
1362
+ provider: "shopify",
1363
+ // Shopify is per-tenant: every OAuth endpoint lives on the merchant's own
1364
+ // myshopify.com domain, so these carry a {tenant_host} placeholder the
1365
+ // platform fills in from the shop domain the user supplies at connect
1366
+ // time. There is no global Shopify authorization host to point at.
1367
+ authorizeUrl: "https://{tenant_host}/admin/oauth/authorize",
1368
+ tokenExchangeUrl: "https://{tenant_host}/admin/oauth/access_token",
1369
+ // Requesting `expiring=1` at token exchange yields a short-lived access
1370
+ // token plus a refresh token, which is refreshed against the same
1371
+ // per-shop endpoint.
1372
+ refreshUrl: "https://{tenant_host}/admin/oauth/access_token",
1373
+ pingUrl: "https://{tenant_host}/admin/api/2026-07/shop.json",
1374
+ baseUrl: "https://{tenant_host}",
1375
+ displayLabel: "Shopify",
1376
+ description: "Products, orders, customers, and inventory",
1377
+ dashboardUrl: "https://shopify.dev/dashboard",
1378
+ clientIdPlaceholder: null,
1379
+ logoUrl: "https://cdn.simpleicons.org/shopify",
1380
+ // Shopify parses `scope` as a comma-separated list. A space-separated
1381
+ // list is read as one malformed scope and the request is rejected.
1382
+ scopeSeparator: ",",
1383
+ // Admin API scopes only: Storefront, Customer Account and Shop APIs are
1384
+ // buyer-facing surfaces an assistant has no use for. Every scope here is
1385
+ // grantable without Shopify approval -- the approval-gated ones
1386
+ // (read_all_orders, customer payment methods, subscription contracts,
1387
+ // dispute evidence) are deliberately absent, because Shopify rejects the
1388
+ // whole authorization request if the app cannot grant a requested scope.
1389
+ // A write_* grant implies its read_* counterpart, so the response may
1390
+ // name only the write scope.
1391
+ defaultScopes: [
1392
+ "read_products",
1393
+ "write_products",
1394
+ "read_orders",
1395
+ "write_orders",
1396
+ "read_draft_orders",
1397
+ "write_draft_orders",
1398
+ "read_customers",
1399
+ "write_customers",
1400
+ "read_inventory",
1401
+ "read_locations",
1402
+ "read_fulfillments",
1403
+ "read_discounts",
1404
+ "write_discounts",
1405
+ "read_price_rules",
1406
+ ],
1407
+ availableScopes: [
1408
+ {
1409
+ scope: "read_products",
1410
+ description: "Read products, variants, and collections",
1411
+ },
1412
+ {
1413
+ scope: "write_products",
1414
+ description: "Create and update products, variants, and collections",
1415
+ },
1416
+ {
1417
+ scope: "read_orders",
1418
+ description: "Read orders, transactions, and abandoned checkouts",
1419
+ },
1420
+ {
1421
+ scope: "write_orders",
1422
+ description: "Create and update orders and fulfillments",
1423
+ },
1424
+ { scope: "read_draft_orders", description: "Read draft orders" },
1425
+ {
1426
+ scope: "write_draft_orders",
1427
+ description: "Create and update draft orders",
1428
+ },
1429
+ {
1430
+ scope: "read_customers",
1431
+ description: "Read customers, segments, and companies",
1432
+ },
1433
+ {
1434
+ scope: "write_customers",
1435
+ description: "Create and update customers and segments",
1436
+ },
1437
+ {
1438
+ scope: "read_inventory",
1439
+ description: "Read inventory levels and items",
1440
+ },
1441
+ {
1442
+ scope: "write_inventory",
1443
+ description: "Adjust inventory levels and items",
1444
+ },
1445
+ { scope: "read_locations", description: "Read store locations" },
1446
+ { scope: "read_fulfillments", description: "Read fulfillment services" },
1447
+ {
1448
+ scope: "write_fulfillments",
1449
+ description: "Create and update fulfillment services",
1450
+ },
1451
+ { scope: "read_discounts", description: "Read discounts" },
1452
+ { scope: "write_discounts", description: "Create and update discounts" },
1453
+ { scope: "read_price_rules", description: "Read price rules" },
1454
+ {
1455
+ scope: "write_price_rules",
1456
+ description: "Create and update price rules",
1457
+ },
1458
+ { scope: "read_files", description: "Read files uploaded to the store" },
1459
+ { scope: "write_files", description: "Upload and update files" },
1460
+ { scope: "read_gift_cards", description: "Read gift cards" },
1461
+ {
1462
+ scope: "write_gift_cards",
1463
+ description: "Create and update gift cards",
1464
+ },
1465
+ { scope: "read_returns", description: "Read returns" },
1466
+ { scope: "write_returns", description: "Create and update returns" },
1467
+ { scope: "read_markets", description: "Read markets" },
1468
+ { scope: "read_locales", description: "Read shop locales" },
1469
+ {
1470
+ scope: "read_shipping",
1471
+ description: "Read shipping and delivery carrier services",
1472
+ },
1473
+ { scope: "read_analytics", description: "Read analytics and reports" },
1474
+ {
1475
+ scope: "read_marketing_events",
1476
+ description: "Read marketing events and activities",
1477
+ },
1478
+ { scope: "read_order_edits", description: "Read order edits" },
1479
+ {
1480
+ scope: "write_order_edits",
1481
+ description: "Create and apply order edits",
1482
+ },
1483
+ {
1484
+ scope: "read_metaobjects",
1485
+ description: "Read metaobjects and their definitions",
1486
+ },
1487
+ {
1488
+ scope: "write_metaobjects",
1489
+ description: "Create and update metaobjects",
1490
+ },
1491
+ { scope: "read_translations", description: "Read translated content" },
1492
+ {
1493
+ scope: "read_shopify_payments_payouts",
1494
+ description: "Read Shopify Payments payouts and balance",
1495
+ },
1496
+ {
1497
+ scope: "read_shopify_payments_disputes",
1498
+ description: "Read Shopify Payments disputes",
1499
+ },
1500
+ ],
1501
+ loopbackPort: 17341,
1502
+ managedServiceConfigKey: "shopify-oauth",
1503
+ // Mirrors `extra_config.tenant_host` in the platform provider registry.
1504
+ tenantHost: {
1505
+ pattern: "^[a-z0-9][a-z0-9-]*\\.myshopify\\.com$",
1506
+ label: "Shop domain",
1507
+ placeholder: "your-store.myshopify.com",
1508
+ },
1509
+ injectionTemplates: [
1510
+ {
1511
+ hostPattern: "*.myshopify.com",
1512
+ injectionType: "header",
1513
+ headerName: "X-Shopify-Access-Token",
1514
+ valuePrefix: "",
1515
+ },
1516
+ ],
1517
+ appType: "App",
1518
+ setupNotes: [
1519
+ "Shopify OAuth is per-shop: the authorize, token and API endpoints all live on the merchant's own myshopify.com domain, so connecting requires the shop domain (for example your-store.myshopify.com) alongside the usual credentials.",
1520
+ "The Admin API authenticates with the X-Shopify-Access-Token header rather than an Authorization Bearer header.",
1521
+ ],
1522
+ // The Admin API's shop endpoint nests its payload under "shop"; the
1523
+ // myshopify domain is the stable, human-recognisable handle.
1524
+ identityUrl: "https://{tenant_host}/admin/api/2026-07/shop.json",
1525
+ identityResponsePaths: ["shop.myshopify_domain", "shop.name"],
1526
+ // Gated like monday/figma/stripe-link: the platform side and the
1527
+ // shop-domain input in the clients land separately, and until both are
1528
+ // in place a visible Shopify tile would offer a connect flow that
1529
+ // cannot complete.
1530
+ featureFlag: "shopify-oauth",
1531
+ },
1532
+
1533
+ quickbooks: {
1534
+ provider: "quickbooks",
1535
+ authorizeUrl: "https://appcenter.intuit.com/connect/oauth2",
1536
+ tokenExchangeUrl:
1537
+ "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
1538
+ // Access tokens live an hour; refresh tokens 100 days and are rotated on
1539
+ // every refresh, which the platform persists. Both token endpoints
1540
+ // authenticate the client with HTTP Basic.
1541
+ refreshUrl: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
1542
+ tokenEndpointAuthMethod: "client_secret_basic",
1543
+ // No revokeUrl: Intuit's revocation endpoint wants a JSON body under HTTP
1544
+ // Basic client auth, and the daemon's revoke helper only posts an
1545
+ // unauthenticated form body, so a seeded URL would fail silently on every
1546
+ // your-own disconnect. The platform registry revokes managed tokens itself.
1547
+ // Every Accounting API path is scoped to the company (realm) the user
1548
+ // picked on Intuit's consent screen. The realm arrives only as the
1549
+ // callback's `realmId` query parameter; the platform captures it into the
1550
+ // connection's `provider_params` and fills the `{realm_id}` placeholder
1551
+ // server-side, so managed callers send paths relative to the company
1552
+ // (`/query`, `/customer/123`). Production and sandbox keys use different
1553
+ // hosts; the platform picks the host per environment.
1554
+ baseUrl: "https://quickbooks.api.intuit.com/v3/company/{realm_id}",
1555
+ // No pingUrl: the only company-independent probe would still need the
1556
+ // realm in its path, which the ping route cannot fill in (it sends the
1557
+ // URL's origin as a per-request base override, which would also pin a
1558
+ // managed sandbox connection to the production host).
1559
+ displayLabel: "QuickBooks",
1560
+ description: "Invoices, customers, vendors, and accounting data",
1561
+ dashboardUrl: "https://developer.intuit.com/app/developer/dashboard",
1562
+ clientIdPlaceholder: null,
1563
+ logoUrl: "https://cdn.simpleicons.org/quickbooks",
1564
+ // The Accounting scope covers the QuickBooks Online API. Payments is a
1565
+ // separate product the app must be enabled for, and the OpenID scopes
1566
+ // only add the signing-in user's profile, so none are on by default.
1567
+ defaultScopes: ["com.intuit.quickbooks.accounting"],
1568
+ availableScopes: [
1569
+ {
1570
+ scope: "com.intuit.quickbooks.accounting",
1571
+ description:
1572
+ "Read and write QuickBooks Online accounting data: customers, vendors, invoices, bills, payments, items, accounts, and reports",
1573
+ },
1574
+ {
1575
+ scope: "com.intuit.quickbooks.payment",
1576
+ description:
1577
+ "QuickBooks Payments: charges, refunds, bank accounts, and cards (requires a Payments-enabled app)",
1578
+ },
1579
+ { scope: "openid", description: "Sign in with Intuit (OpenID Connect)" },
1580
+ { scope: "profile", description: "The signing-in user's name" },
1581
+ { scope: "email", description: "The signing-in user's email address" },
1582
+ { scope: "phone", description: "The signing-in user's phone number" },
1583
+ { scope: "address", description: "The signing-in user's address" },
1584
+ ],
1585
+ loopbackPort: 17342,
1586
+ managedServiceConfigKey: "quickbooks-oauth",
1587
+ injectionTemplates: [
1588
+ {
1589
+ hostPattern: "quickbooks.api.intuit.com",
1590
+ injectionType: "header",
1591
+ headerName: "Authorization",
1592
+ valuePrefix: "Bearer ",
1593
+ },
1594
+ {
1595
+ hostPattern: "sandbox-quickbooks.api.intuit.com",
1596
+ injectionType: "header",
1597
+ headerName: "Authorization",
1598
+ valuePrefix: "Bearer ",
1599
+ },
1600
+ ],
1601
+ appType: "App",
1602
+ setupNotes: [
1603
+ "QuickBooks scopes every Accounting API call to the company (realm) chosen on Intuit's consent screen. Managed connections carry the realm as provider_params.realm_id and requests are sent relative to /v3/company/{realmId}.",
1604
+ "Intuit development keys only authorize sandbox companies, which live on sandbox-quickbooks.api.intuit.com; production keys use quickbooks.api.intuit.com.",
1605
+ "The Accounting API returns XML unless the request carries Accept: application/json.",
1606
+ ],
1607
+ // CompanyInfo does not repeat the realm (its Id is always "1"); the
1608
+ // platform keys the connection on the captured realm and labels it with
1609
+ // the company name.
1610
+ identityUrl:
1611
+ "https://quickbooks.api.intuit.com/v3/company/{realm_id}/companyinfo/{realm_id}",
1612
+ identityHeaders: { Accept: "application/json" },
1613
+ identityResponsePaths: ["CompanyInfo.CompanyName", "CompanyInfo.LegalName"],
1614
+ // Gated like figma/shopify: the platform side lands separately, and until
1615
+ // the client ids are live a visible tile would offer a connect flow that
1616
+ // cannot complete.
1617
+ featureFlag: "quickbooks-oauth",
1618
+ },
1332
1619
  };
1333
1620
 
1334
1621
  export const SEEDED_PROVIDER_KEYS = new Set(Object.keys(PROVIDER_SEED_DATA));
@@ -2,6 +2,8 @@ import { X509Certificate } from "node:crypto";
2
2
  import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
 
5
+ import { writeCombinedCABundle } from "../util/ca-bundle.js";
6
+
5
7
  const CA_CERT_FILENAME = "ca.pem";
6
8
  const CA_KEY_FILENAME = "ca-key.pem";
7
9
  const COMBINED_CA_FILENAME = "combined-ca-bundle.pem";
@@ -263,11 +265,7 @@ export async function ensureCombinedCABundle(
263
265
  }
264
266
 
265
267
  try {
266
- const [systemCAs, proxyCACert] = await Promise.all([
267
- readFile(systemBundlePath, "utf-8"),
268
- readFile(caCertPath, "utf-8"),
269
- ]);
270
- await writeFile(combinedPath, systemCAs + "\n" + proxyCACert);
268
+ await writeCombinedCABundle(systemBundlePath, caCertPath, combinedPath);
271
269
  return combinedPath;
272
270
  } catch {
273
271
  return null;