@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
@@ -0,0 +1,255 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import {
4
+ getPlatformBaseUrl,
5
+ setPlatformAssistantId,
6
+ setPlatformBaseUrl,
7
+ setPlatformOrganizationId,
8
+ setPlatformUserId,
9
+ } from "../env.js";
10
+
11
+ const actualSecureKeys = await import("../../security/secure-keys.js");
12
+ mock.module("../../security/secure-keys.js", () => ({
13
+ ...actualSecureKeys,
14
+ getSecureKeyAsync: async () => undefined,
15
+ }));
16
+
17
+ const {
18
+ _resetPlatformIdentityEnsureForTests,
19
+ fetchPlatformIdentityIds,
20
+ PLATFORM_IDENTITY_VALIDATE_PATH,
21
+ resolvePlatformAssistantId,
22
+ } = await import("../platform-identity.js");
23
+
24
+ const BASE_URL = "https://platform.vellum.ai";
25
+ const ASSISTANT_ID = "11111111-2222-4333-8444-555555555555";
26
+ const ORG_ID = "22222222-3333-4444-8555-666666666666";
27
+ const USER_ID = "33333333-4444-4555-8666-777777777777";
28
+
29
+ describe("fetchPlatformIdentityIds", () => {
30
+ const originalFetch = globalThis.fetch;
31
+ const fetchCalls: Array<{ url: string; init?: RequestInit }> = [];
32
+ let fetchImpl: (
33
+ input: RequestInfo | URL,
34
+ init?: RequestInit,
35
+ ) => Promise<Response> = async () =>
36
+ new Response("not found", { status: 404 });
37
+
38
+ beforeEach(() => {
39
+ fetchCalls.length = 0;
40
+ fetchImpl = async () => new Response("not found", { status: 404 });
41
+ globalThis.fetch = (async (
42
+ input: RequestInfo | URL,
43
+ init?: RequestInit,
44
+ ) => {
45
+ const url = typeof input === "string" ? input : input.toString();
46
+ fetchCalls.push({ url, init });
47
+ return fetchImpl(input, init);
48
+ }) as unknown as typeof fetch;
49
+ });
50
+
51
+ afterEach(() => {
52
+ globalThis.fetch = originalFetch;
53
+ });
54
+
55
+ test("returns the three ids from a successful validate response", async () => {
56
+ fetchImpl = async () =>
57
+ new Response(
58
+ JSON.stringify({
59
+ assistant_id: ASSISTANT_ID,
60
+ organization_id: ORG_ID,
61
+ user_id: USER_ID,
62
+ name: "Example Assistant",
63
+ }),
64
+ { status: 200 },
65
+ );
66
+
67
+ await expect(
68
+ fetchPlatformIdentityIds(BASE_URL, "assistant-key"),
69
+ ).resolves.toEqual({
70
+ assistantId: ASSISTANT_ID,
71
+ organizationId: ORG_ID,
72
+ userId: USER_ID,
73
+ });
74
+ expect(fetchCalls[0]?.url).toBe(
75
+ `${BASE_URL}${PLATFORM_IDENTITY_VALIDATE_PATH}`,
76
+ );
77
+ expect(fetchCalls[0]?.init?.method).toBe("POST");
78
+ const headers = new Headers(fetchCalls[0]?.init?.headers);
79
+ expect(headers.get("Authorization")).toBe("Api-Key assistant-key");
80
+ });
81
+
82
+ test("strips a trailing slash from the base URL", async () => {
83
+ fetchImpl = async () =>
84
+ new Response(
85
+ JSON.stringify({
86
+ assistant_id: ASSISTANT_ID,
87
+ organization_id: ORG_ID,
88
+ user_id: USER_ID,
89
+ }),
90
+ { status: 200 },
91
+ );
92
+
93
+ await fetchPlatformIdentityIds(`${BASE_URL}/`, "assistant-key");
94
+ expect(fetchCalls[0]?.url).toBe(
95
+ `${BASE_URL}${PLATFORM_IDENTITY_VALIDATE_PATH}`,
96
+ );
97
+ });
98
+
99
+ test("returns null on 401 without throwing", async () => {
100
+ fetchImpl = async () => new Response("no", { status: 401 });
101
+ await expect(
102
+ fetchPlatformIdentityIds(BASE_URL, "assistant-key"),
103
+ ).resolves.toBeNull();
104
+ });
105
+
106
+ test("returns null on a network error without throwing", async () => {
107
+ fetchImpl = async () => {
108
+ throw new Error("network down");
109
+ };
110
+ await expect(
111
+ fetchPlatformIdentityIds(BASE_URL, "assistant-key"),
112
+ ).resolves.toBeNull();
113
+ });
114
+ });
115
+
116
+ describe("resolvePlatformAssistantId", () => {
117
+ const originalFetch = globalThis.fetch;
118
+ const originalAssistantApiKeyEnv = process.env.ASSISTANT_API_KEY;
119
+ const fetchCalls: Array<{ url: string; init?: RequestInit }> = [];
120
+ let fetchImpl: (
121
+ input: RequestInfo | URL,
122
+ init?: RequestInit,
123
+ ) => Promise<Response> = async () =>
124
+ new Response("not found", { status: 404 });
125
+
126
+ beforeEach(() => {
127
+ fetchCalls.length = 0;
128
+ fetchImpl = async () => new Response("not found", { status: 404 });
129
+ globalThis.fetch = (async (
130
+ input: RequestInfo | URL,
131
+ init?: RequestInit,
132
+ ) => {
133
+ const url = typeof input === "string" ? input : input.toString();
134
+ fetchCalls.push({ url, init });
135
+ return fetchImpl(input, init);
136
+ }) as unknown as typeof fetch;
137
+ delete process.env.ASSISTANT_API_KEY;
138
+ setPlatformBaseUrl(undefined);
139
+ setPlatformAssistantId(undefined);
140
+ setPlatformOrganizationId(undefined);
141
+ setPlatformUserId(undefined);
142
+ _resetPlatformIdentityEnsureForTests();
143
+ });
144
+
145
+ afterEach(() => {
146
+ globalThis.fetch = originalFetch;
147
+ if (originalAssistantApiKeyEnv === undefined) {
148
+ delete process.env.ASSISTANT_API_KEY;
149
+ } else {
150
+ process.env.ASSISTANT_API_KEY = originalAssistantApiKeyEnv;
151
+ }
152
+ setPlatformBaseUrl(undefined);
153
+ setPlatformAssistantId(undefined);
154
+ setPlatformOrganizationId(undefined);
155
+ setPlatformUserId(undefined);
156
+ _resetPlatformIdentityEnsureForTests();
157
+ });
158
+
159
+ test("returns the in-memory override without calling validate", async () => {
160
+ setPlatformAssistantId(ASSISTANT_ID);
161
+ process.env.ASSISTANT_API_KEY = "assistant-key";
162
+ await expect(resolvePlatformAssistantId()).resolves.toBe(ASSISTANT_ID);
163
+ expect(fetchCalls).toHaveLength(0);
164
+ });
165
+
166
+ test("returns empty when the in-memory override is unset and there is no API key", async () => {
167
+ await expect(resolvePlatformAssistantId()).resolves.toBe("");
168
+ expect(fetchCalls).toHaveLength(0);
169
+ });
170
+
171
+ test("loads ids from validate when the in-memory override is empty", async () => {
172
+ process.env.ASSISTANT_API_KEY = "assistant-key";
173
+ setPlatformBaseUrl(BASE_URL);
174
+ fetchImpl = async () =>
175
+ new Response(
176
+ JSON.stringify({
177
+ assistant_id: ASSISTANT_ID,
178
+ organization_id: ORG_ID,
179
+ user_id: USER_ID,
180
+ }),
181
+ { status: 200 },
182
+ );
183
+
184
+ await expect(resolvePlatformAssistantId()).resolves.toBe(ASSISTANT_ID);
185
+ expect(fetchCalls).toHaveLength(1);
186
+ expect(fetchCalls[0]?.url).toBe(
187
+ `${getPlatformBaseUrl().replace(/\/+$/, "")}${PLATFORM_IDENTITY_VALIDATE_PATH}`,
188
+ );
189
+ });
190
+
191
+ test("does not retry validate during the cooldown after a failure", async () => {
192
+ process.env.ASSISTANT_API_KEY = "assistant-key";
193
+ setPlatformBaseUrl(BASE_URL);
194
+ fetchImpl = async () => new Response("down", { status: 503 });
195
+
196
+ await expect(resolvePlatformAssistantId()).resolves.toBe("");
197
+ await expect(resolvePlatformAssistantId()).resolves.toBe("");
198
+ expect(fetchCalls).toHaveLength(1);
199
+ });
200
+
201
+ test("retries validate after a failed attempt once the cooldown is cleared", async () => {
202
+ process.env.ASSISTANT_API_KEY = "assistant-key";
203
+ setPlatformBaseUrl(BASE_URL);
204
+ fetchImpl = async () => new Response("down", { status: 503 });
205
+
206
+ await expect(resolvePlatformAssistantId()).resolves.toBe("");
207
+ _resetPlatformIdentityEnsureForTests();
208
+ fetchImpl = async () =>
209
+ new Response(
210
+ JSON.stringify({
211
+ assistant_id: ASSISTANT_ID,
212
+ organization_id: ORG_ID,
213
+ user_id: USER_ID,
214
+ }),
215
+ { status: 200 },
216
+ );
217
+
218
+ await expect(resolvePlatformAssistantId()).resolves.toBe(ASSISTANT_ID);
219
+ expect(fetchCalls).toHaveLength(2);
220
+ });
221
+
222
+ test("concurrent resolves share one in-flight validate", async () => {
223
+ process.env.ASSISTANT_API_KEY = "assistant-key";
224
+ setPlatformBaseUrl(BASE_URL);
225
+ let release!: (value: Response) => void;
226
+ let notifyFetchStarted!: () => void;
227
+ const fetchStarted = new Promise<void>((resolve) => {
228
+ notifyFetchStarted = resolve;
229
+ });
230
+ fetchImpl = () => {
231
+ notifyFetchStarted();
232
+ return new Promise<Response>((resolve) => {
233
+ release = resolve;
234
+ });
235
+ };
236
+
237
+ const first = resolvePlatformAssistantId();
238
+ const second = resolvePlatformAssistantId();
239
+ await fetchStarted;
240
+ expect(fetchCalls).toHaveLength(1);
241
+ release(
242
+ new Response(
243
+ JSON.stringify({
244
+ assistant_id: ASSISTANT_ID,
245
+ organization_id: ORG_ID,
246
+ user_id: USER_ID,
247
+ }),
248
+ { status: 200 },
249
+ ),
250
+ );
251
+ await expect(first).resolves.toBe(ASSISTANT_ID);
252
+ await expect(second).resolves.toBe(ASSISTANT_ID);
253
+ expect(fetchCalls).toHaveLength(1);
254
+ });
255
+ });
@@ -27,6 +27,8 @@ const {
27
27
  setPlatformOrganizationId,
28
28
  setPlatformUserId,
29
29
  } = await import("../env.js");
30
+ const { _resetPlatformIdentityEnsureForTests } =
31
+ await import("../platform-identity.js");
30
32
  const { rehydratePlatformCredentials } =
31
33
  await import("../platform-rehydration.js");
32
34
 
@@ -39,23 +41,44 @@ describe("rehydratePlatformCredentials", () => {
39
41
  const originalPlatformUrlEnv = process.env.VELLUM_PLATFORM_URL;
40
42
  const originalOrgEnv = process.env.PLATFORM_ORGANIZATION_ID;
41
43
  const originalUserEnv = process.env.PLATFORM_USER_ID;
44
+ const originalAssistantApiKeyEnv = process.env.ASSISTANT_API_KEY;
45
+ const originalFetch = globalThis.fetch;
46
+ const fetchCalls: Array<{ url: string; init?: RequestInit }> = [];
47
+ let fetchImpl: (
48
+ input: RequestInfo | URL,
49
+ init?: RequestInit,
50
+ ) => Promise<Response> = async () =>
51
+ new Response("not found", { status: 404 });
42
52
 
43
53
  beforeEach(() => {
44
54
  mockSecureKeys = {};
45
55
  throwForKey = undefined;
56
+ fetchCalls.length = 0;
57
+ fetchImpl = async () => new Response("not found", { status: 404 });
58
+ globalThis.fetch = (async (
59
+ input: RequestInfo | URL,
60
+ init?: RequestInit,
61
+ ) => {
62
+ const url = typeof input === "string" ? input : input.toString();
63
+ fetchCalls.push({ url, init });
64
+ return fetchImpl(input, init);
65
+ }) as unknown as typeof fetch;
46
66
  // Env vars take precedence over the rehydrated overrides for some fields;
47
67
  // clear them so the credential-store values are what we observe.
48
68
  delete process.env.VELLUM_PLATFORM_URL;
49
69
  delete process.env.PLATFORM_ORGANIZATION_ID;
50
70
  delete process.env.PLATFORM_USER_ID;
71
+ delete process.env.ASSISTANT_API_KEY;
51
72
  // Reset in-memory overrides between tests.
52
73
  setPlatformBaseUrl(undefined);
53
74
  setPlatformAssistantId(undefined);
54
75
  setPlatformOrganizationId(undefined);
55
76
  setPlatformUserId(undefined);
77
+ _resetPlatformIdentityEnsureForTests();
56
78
  });
57
79
 
58
80
  afterEach(() => {
81
+ globalThis.fetch = originalFetch;
59
82
  if (originalPlatformUrlEnv === undefined) {
60
83
  delete process.env.VELLUM_PLATFORM_URL;
61
84
  } else {
@@ -71,13 +94,18 @@ describe("rehydratePlatformCredentials", () => {
71
94
  } else {
72
95
  process.env.PLATFORM_USER_ID = originalUserEnv;
73
96
  }
97
+ if (originalAssistantApiKeyEnv === undefined) {
98
+ delete process.env.ASSISTANT_API_KEY;
99
+ } else {
100
+ process.env.ASSISTANT_API_KEY = originalAssistantApiKeyEnv;
101
+ }
74
102
  setPlatformBaseUrl(undefined);
75
103
  setPlatformAssistantId(undefined);
76
104
  setPlatformOrganizationId(undefined);
77
105
  setPlatformUserId(undefined);
78
106
  });
79
107
 
80
- test("rehydrates base URL and all platform IDs from the credential store", async () => {
108
+ test("rehydrates the platform base URL from the credential store", async () => {
81
109
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] = PROD_URL;
82
110
  mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
83
111
  ASSISTANT_ID;
@@ -88,21 +116,18 @@ describe("rehydratePlatformCredentials", () => {
88
116
  await rehydratePlatformCredentials();
89
117
 
90
118
  expect(getPlatformBaseUrl()).toBe(PROD_URL);
91
- expect(getPlatformAssistantId()).toBe(ASSISTANT_ID);
92
- expect(getPlatformOrganizationId()).toBe(ORG_ID);
93
- expect(getPlatformUserId()).toBe(USER_ID);
119
+ expect(getPlatformAssistantId()).toBe("");
120
+ expect(getPlatformOrganizationId()).toBe("");
121
+ expect(getPlatformUserId()).toBe("");
94
122
  });
95
123
 
96
124
  test("trims stored values before applying them", async () => {
97
125
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
98
126
  ` ${PROD_URL} `;
99
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
100
- `\n${ASSISTANT_ID}\n`;
101
127
 
102
128
  await rehydratePlatformCredentials();
103
129
 
104
130
  expect(getPlatformBaseUrl()).toBe(PROD_URL);
105
- expect(getPlatformAssistantId()).toBe(ASSISTANT_ID);
106
131
  });
107
132
 
108
133
  test("leaves overrides untouched when nothing is stored", async () => {
@@ -113,13 +138,78 @@ describe("rehydratePlatformCredentials", () => {
113
138
  expect(getPlatformUserId()).toBe("");
114
139
  });
115
140
 
116
- test("a per-field read failure does not block the remaining fields", async () => {
141
+ test("a credential-store read failure does not throw", async () => {
117
142
  throwForKey = credentialKey("vellum", "platform_base_url");
143
+
144
+ await expect(rehydratePlatformCredentials()).resolves.toBeUndefined();
145
+ expect(getPlatformAssistantId()).toBe("");
146
+ });
147
+
148
+ test("trades the assistant API key for ids via platform validate", async () => {
149
+ mockSecureKeys[credentialKey("vellum", "platform_base_url")] = PROD_URL;
150
+ mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
151
+ "assistant-key";
152
+ fetchImpl = async () =>
153
+ new Response(
154
+ JSON.stringify({
155
+ assistant_id: ASSISTANT_ID,
156
+ organization_id: ORG_ID,
157
+ user_id: USER_ID,
158
+ }),
159
+ { status: 200 },
160
+ );
161
+
162
+ await rehydratePlatformCredentials();
163
+
164
+ expect(fetchCalls).toHaveLength(1);
165
+ expect(fetchCalls[0]?.url).toBe(
166
+ `${PROD_URL}/v1/internal/assistants/validate/`,
167
+ );
168
+ const headers = new Headers(fetchCalls[0]?.init?.headers);
169
+ expect(headers.get("Authorization")).toBe("Api-Key assistant-key");
170
+ expect(getPlatformAssistantId()).toBe(ASSISTANT_ID);
171
+ expect(getPlatformOrganizationId()).toBe(ORG_ID);
172
+ expect(getPlatformUserId()).toBe(USER_ID);
173
+ });
174
+
175
+ test("validate ids are used when the vault also has a copy", async () => {
176
+ mockSecureKeys[credentialKey("vellum", "platform_base_url")] = PROD_URL;
177
+ mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
178
+ "assistant-key";
118
179
  mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
119
- ASSISTANT_ID;
180
+ "asst-from-vault";
181
+ fetchImpl = async () =>
182
+ new Response(
183
+ JSON.stringify({
184
+ assistant_id: ASSISTANT_ID,
185
+ organization_id: ORG_ID,
186
+ user_id: USER_ID,
187
+ }),
188
+ { status: 200 },
189
+ );
120
190
 
121
191
  await rehydratePlatformCredentials();
122
192
 
123
193
  expect(getPlatformAssistantId()).toBe(ASSISTANT_ID);
124
194
  });
195
+
196
+ test("does not populate ids from the vault when validate is unreachable", async () => {
197
+ mockSecureKeys[credentialKey("vellum", "platform_base_url")] = PROD_URL;
198
+ mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
199
+ "assistant-key";
200
+ mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
201
+ ASSISTANT_ID;
202
+ mockSecureKeys[credentialKey("vellum", "platform_organization_id")] =
203
+ ORG_ID;
204
+ mockSecureKeys[credentialKey("vellum", "platform_user_id")] = USER_ID;
205
+ fetchImpl = async () => {
206
+ throw new Error("network down");
207
+ };
208
+
209
+ await rehydratePlatformCredentials();
210
+
211
+ expect(getPlatformAssistantId()).toBe("");
212
+ expect(getPlatformOrganizationId()).toBe("");
213
+ expect(getPlatformUserId()).toBe("");
214
+ });
125
215
  });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Raw-config helper shared by the config PATCH and SET routes, the paths that
3
+ * write a coding agent's entry.
4
+ */
5
+
6
+ import { isPlainObject } from "../util/object.js";
7
+
8
+ /**
9
+ * Clearing a leaf of a coding agent's entry is a write of `null` - a PATCH of
10
+ * `{ acp: { agents: { <id>: { model: null } } } }` or a
11
+ * `config set acp.agents.<id>.model null`. The deep-merge assigns that literal
12
+ * `null` because the stored value is a scalar, and a SET writes it verbatim by
13
+ * design. Both `command` and `model` are optional strings in `AcpConfigSchema`,
14
+ * so a persisted `null` makes every later `loadConfig()` warn and take its
15
+ * salvage path, dropping the whole `acp` section with the agents defined in it.
16
+ * Clearing means removing the key, so drop it here on every write path.
17
+ *
18
+ * A cleared `command` then reads as an omitted one: a bundled id inherits the
19
+ * profile's command, and an id that has no profile fails the agents refinement
20
+ * with a message naming the id, which tells the user more than zod's "expected
21
+ * string, received null".
22
+ */
23
+ export function scrubNulledAcpAgentLeaves(raw: Record<string, unknown>): void {
24
+ const acp = raw.acp;
25
+ if (!isPlainObject(acp)) {
26
+ return;
27
+ }
28
+ const agents = acp.agents;
29
+ if (!isPlainObject(agents)) {
30
+ return;
31
+ }
32
+ for (const entry of Object.values(agents)) {
33
+ if (!isPlainObject(entry)) {
34
+ continue;
35
+ }
36
+ if (entry.command === null) {
37
+ delete entry.command;
38
+ }
39
+ if (entry.model === null) {
40
+ delete entry.model;
41
+ }
42
+ }
43
+ }
@@ -13,15 +13,17 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
13
13
  ]);
14
14
  });
15
15
 
16
- test("claude profile uses the @agentclientprotocol adapter binary", () => {
16
+ test("claude profile uses the @agentclientprotocol adapter binary on opus", () => {
17
17
  expect(DEFAULT_ACP_AGENT_PROFILES.claude).toEqual({
18
18
  command: "claude-agent-acp",
19
19
  args: [],
20
20
  description: "Claude Code (via @agentclientprotocol/claude-agent-acp)",
21
+ model: "opus",
21
22
  });
22
23
  });
23
24
 
24
25
  test("codex profile uses the @agentclientprotocol adapter binary", () => {
26
+ // No model: Codex aliases are its own, so the adapter's default stands.
25
27
  expect(DEFAULT_ACP_AGENT_PROFILES.codex).toEqual({
26
28
  command: "codex-acp",
27
29
  args: [],
@@ -44,11 +46,19 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
44
46
  describe("DEFAULT_AGENT_NPM_PACKAGES", () => {
45
47
  test("is keyed by command name with the canonical npm package", () => {
46
48
  expect(DEFAULT_AGENT_NPM_PACKAGES).toEqual({
47
- "claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
48
- "codex-acp": "@agentclientprotocol/codex-acp",
49
+ "claude-agent-acp": "@agentclientprotocol/claude-agent-acp@0.75.1",
50
+ "codex-acp": "@agentclientprotocol/codex-acp@1.10.0",
49
51
  });
50
52
  });
51
53
 
54
+ test("every package spec pins an exact version", () => {
55
+ // A bare name would let a clean install resolve whatever npm calls
56
+ // latest, which is what the repo's pinning rule exists to prevent.
57
+ for (const spec of Object.values(DEFAULT_AGENT_NPM_PACKAGES)) {
58
+ expect(spec).toMatch(/^@?[^@]+@\d+\.\d+\.\d+$/);
59
+ }
60
+ });
61
+
52
62
  test("every default profile's command has a matching npm package", () => {
53
63
  for (const profile of Object.values(DEFAULT_ACP_AGENT_PROFILES)) {
54
64
  expect(DEFAULT_AGENT_NPM_PACKAGES[profile.command]).toBeDefined();
@@ -1,5 +1,11 @@
1
1
  import type { AcpAgentConfig } from "./acp-schema.js";
2
2
 
3
+ /**
4
+ * A bundled profile always names its command; a user entry for the same id
5
+ * may omit `command` and inherit this one.
6
+ */
7
+ export type AcpAgentProfile = AcpAgentConfig & { command: string };
8
+
3
9
  // Shared frozen empty args array — these defaults are read by every ACP spawn,
4
10
  // so a single accidental `args.push(...)` from one caller would corrupt every
5
11
  // subsequent read. Cast through `unknown` because `AcpAgentConfig.args` is
@@ -13,17 +19,22 @@ const FROZEN_EMPTY_ARGS = Object.freeze([] as string[]) as unknown as string[];
13
19
  * falls back to this map so common agents like `claude` and `codex` Just Work
14
20
  * without requiring per-user config.
15
21
  *
16
- * Keyed by agent id. Deeply frozen the outer object, each profile, and the
17
- * `args` arrays so mutation throws in strict mode rather than silently
22
+ * A profile's `model` is the model a session of that agent starts on when the
23
+ * ask names none, and is the rung `acp.agents.<id>.model` fills: a user config
24
+ * entry that names a model overrides it, one that omits it inherits it.
25
+ *
26
+ * Keyed by agent id. Deeply frozen: the outer object, each profile, and the
27
+ * `args` arrays, so mutation throws in strict mode rather than silently
18
28
  * corrupting the shared defaults.
19
29
  */
20
30
  export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
21
- Record<string, AcpAgentConfig>
31
+ Record<string, AcpAgentProfile>
22
32
  > = Object.freeze({
23
33
  claude: Object.freeze({
24
34
  command: "claude-agent-acp",
25
35
  args: FROZEN_EMPTY_ARGS,
26
36
  description: "Claude Code (via @agentclientprotocol/claude-agent-acp)",
37
+ model: "opus",
27
38
  }),
28
39
  codex: Object.freeze({
29
40
  command: "codex-acp",
@@ -33,15 +44,21 @@ export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
33
44
  });
34
45
 
35
46
  /**
36
- * Single source of truth for adapter binary → npm package name. Automatic
47
+ * Single source of truth for adapter binary → npm package spec. Automatic
37
48
  * installation and the resolver's install hints use this map, so a new
38
49
  * adapter only needs one entry here.
39
50
  *
40
51
  * Keyed by command name (not agent id) so the mapping follows the binary
41
52
  * regardless of how a user's config aliases an agent.
53
+ *
54
+ * Values carry an exact version, so a clean install lands the adapter version
55
+ * the daemon was tested against instead of whatever npm calls latest today.
56
+ * An adapter already on PATH is left alone: installation runs only when
57
+ * preflight found no binary at all, so this never moves a version the user
58
+ * installed themselves.
42
59
  */
43
60
  export const DEFAULT_AGENT_NPM_PACKAGES: Readonly<Record<string, string>> =
44
61
  Object.freeze({
45
- "claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
46
- "codex-acp": "@agentclientprotocol/codex-acp",
62
+ "claude-agent-acp": "@agentclientprotocol/claude-agent-acp@0.75.1",
63
+ "codex-acp": "@agentclientprotocol/codex-acp@1.10.0",
47
64
  });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Covers the ACP config leaf that carries model selection, the per-agent
3
+ * `model`. It is optional with no `.default()`, so an absent value must stay
4
+ * absent after parsing: the bundled profile supplies the model a session
5
+ * starts on, and a key materialized as `undefined` would shadow it.
6
+ *
7
+ * `command` is optional in the same shape, so an entry for a bundled id can
8
+ * carry a model and nothing else; the record's own check keeps a command
9
+ * required for every id that has no bundled profile to inherit one from.
10
+ */
11
+
12
+ import { describe, expect, test } from "bun:test";
13
+
14
+ import { AcpAgentConfigSchema, AcpConfigSchema } from "./acp-schema.js";
15
+
16
+ describe("AcpConfigSchema", () => {
17
+ test("populates defaults for an empty config", () => {
18
+ expect(AcpConfigSchema.parse({})).toEqual({
19
+ maxConcurrentSessions: 4,
20
+ agents: {},
21
+ });
22
+ });
23
+ });
24
+
25
+ describe("AcpConfigSchema agents", () => {
26
+ test("a bundled id's entry may omit command and name only a model", () => {
27
+ const parsed = AcpConfigSchema.parse({
28
+ agents: { claude: { model: "sonnet" } },
29
+ });
30
+
31
+ expect(parsed.agents).toEqual({ claude: { args: [], model: "sonnet" } });
32
+ expect("command" in parsed.agents.claude!).toBe(false);
33
+ });
34
+
35
+ test("an id with no bundled profile needs a command", () => {
36
+ const result = AcpConfigSchema.safeParse({
37
+ agents: { mine: { model: "sonnet" } },
38
+ });
39
+
40
+ expect(result.success).toBe(false);
41
+ if (result.success) {
42
+ return;
43
+ }
44
+ expect(result.error.issues.map((issue) => issue.path)).toEqual([
45
+ ["agents", "mine", "command"],
46
+ ]);
47
+ expect(result.error.issues[0]?.message).toContain("acp.agents.mine");
48
+ });
49
+
50
+ test("an id naming an Object.prototype member inherits no command", () => {
51
+ expect(
52
+ AcpConfigSchema.safeParse({ agents: { constructor: { model: "x" } } })
53
+ .success,
54
+ ).toBe(false);
55
+ });
56
+ });
57
+
58
+ describe("AcpAgentConfigSchema", () => {
59
+ test("round-trips a per-agent model override", () => {
60
+ expect(
61
+ AcpAgentConfigSchema.parse({
62
+ command: "claude-agent-acp",
63
+ model: "opus",
64
+ }),
65
+ ).toEqual({ command: "claude-agent-acp", args: [], model: "opus" });
66
+ });
67
+
68
+ test("leaves model unset when omitted, so a bundled profile's shows through", () => {
69
+ const parsed = AcpAgentConfigSchema.parse({ command: "claude-agent-acp" });
70
+
71
+ expect(parsed.model).toBeUndefined();
72
+ expect("model" in parsed).toBe(false);
73
+ });
74
+
75
+ test("rejects a non-string model", () => {
76
+ expect(
77
+ AcpAgentConfigSchema.safeParse({ command: "claude-agent-acp", model: 3 })
78
+ .success,
79
+ ).toBe(false);
80
+ });
81
+ });