@vellumai/assistant 0.12.1 → 0.12.2-staging.1

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 (548) 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/openapi.yaml +327 -52
  6. package/package.json +3 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  254. package/src/desktop/desktop-chrome-policy.ts +47 -0
  255. package/src/desktop/desktop-chrome-session.ts +37 -0
  256. package/src/desktop/desktop-dependencies.test.ts +102 -0
  257. package/src/desktop/desktop-dependencies.ts +308 -0
  258. package/src/desktop/desktop-feature.test.ts +18 -0
  259. package/src/desktop/desktop-feature.ts +19 -0
  260. package/src/desktop/desktop-panel-config.test.ts +135 -0
  261. package/src/desktop/desktop-panel-config.ts +149 -0
  262. package/src/desktop/desktop-session-manager.test.ts +730 -0
  263. package/src/desktop/desktop-session-manager.ts +850 -0
  264. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  265. package/src/desktop/desktop-stream-bridge.ts +214 -0
  266. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  267. package/src/desktop/desktop-wallpaper.ts +113 -0
  268. package/src/desktop/desktop-window-drag.ts +177 -0
  269. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  270. package/src/desktop/desktop-window-manager-config.ts +238 -0
  271. package/src/desktop/desktop-window-theme.ts +170 -0
  272. package/src/heartbeat/heartbeat-service.ts +21 -3
  273. package/src/home/__tests__/feed-writer.test.ts +22 -0
  274. package/src/home/feed-writer.ts +11 -2
  275. package/src/hooks/types.ts +33 -0
  276. package/src/i18n/AGENTS.md +22 -0
  277. package/src/i18n/__tests__/i18n.test.ts +108 -0
  278. package/src/i18n/index.ts +16 -0
  279. package/src/i18n/locales.ts +93 -0
  280. package/src/i18n/messages.ts +61 -0
  281. package/src/i18n/title.ts +25 -0
  282. package/src/inbound/platform-callback-registration.ts +4 -5
  283. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  284. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  285. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  286. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  287. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  288. package/src/live-voice/live-voice-photo.ts +25 -6
  289. package/src/live-voice/live-voice-session.ts +213 -693
  290. package/src/live-voice/protocol.ts +0 -2
  291. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  292. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  293. package/src/mcp/effective-config.ts +3 -3
  294. package/src/mcp/mcp-header-store.ts +29 -45
  295. package/src/mcp/spec-schema.ts +111 -0
  296. package/src/mcp/startup.ts +8 -4
  297. package/src/mcp/workspace-mcp-config.ts +188 -0
  298. package/src/messaging/provider.ts +10 -3
  299. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  300. package/src/messaging/providers/callback-routing.ts +18 -0
  301. package/src/messaging/providers/channel-transport.ts +56 -0
  302. package/src/messaging/providers/discord/transport.ts +17 -0
  303. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  304. package/src/messaging/providers/slack/adapter.ts +7 -44
  305. package/src/messaging/providers/slack/transport.ts +16 -0
  306. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  307. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  308. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  309. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  310. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  311. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  312. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  313. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  314. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  315. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  316. package/src/notifications/adapters/macos.ts +3 -0
  317. package/src/notifications/assistant-reply-producer.ts +8 -3
  318. package/src/notifications/conversation-pairing.ts +36 -16
  319. package/src/notifications/delivered-post-record.ts +11 -1
  320. package/src/notifications/guardian-feed-projection.ts +125 -5
  321. package/src/notifications/home-feed-side-effect.ts +55 -7
  322. package/src/notifications/schedule-result-producer.ts +66 -22
  323. package/src/oauth/AGENTS.md +2 -0
  324. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  325. package/src/oauth/byo-connection.test.ts +105 -0
  326. package/src/oauth/byo-connection.ts +36 -6
  327. package/src/oauth/connection-resolver.test.ts +73 -0
  328. package/src/oauth/connection-resolver.ts +95 -2
  329. package/src/oauth/scope-utils.ts +24 -0
  330. package/src/oauth/seed-providers.ts +204 -25
  331. package/src/outbound-proxy/certs.ts +3 -5
  332. package/src/permissions/checker.test.ts +11 -6
  333. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  334. package/src/persistence/conversation-crud.ts +71 -63
  335. package/src/persistence/conversation-lineage.ts +12 -4
  336. package/src/persistence/conversation-queries.ts +2 -2
  337. package/src/persistence/conversation-title-placeholders.ts +23 -0
  338. package/src/persistence/conversation-title-service.ts +9 -26
  339. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  340. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  341. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  342. package/src/persistence/message-cursor.ts +78 -0
  343. package/src/persistence/message-reads.ts +30 -0
  344. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  345. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  346. package/src/persistence/schema/memory-core.ts +5 -0
  347. package/src/platform/client.test.ts +4 -2
  348. package/src/platform/client.ts +2 -10
  349. package/src/plugin-api/constants.ts +2 -0
  350. package/src/plugin-api/index.ts +1 -0
  351. package/src/plugin-api/types.ts +2 -0
  352. package/src/plugins/defaults/index.ts +2 -0
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  358. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  359. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  360. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  361. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  362. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  363. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  364. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  365. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  366. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  367. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  368. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  369. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  370. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  371. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  372. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  374. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  375. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  376. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  377. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  378. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  379. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  380. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  381. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  382. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  383. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  384. package/src/plugins/defaults/memory/startup.ts +28 -2
  385. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  386. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  387. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  388. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  389. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  390. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  391. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  392. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  393. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  394. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  395. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  396. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  397. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  398. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  399. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  400. package/src/plugins/defaults/memory/worker.ts +6 -0
  401. package/src/plugins/mcp-servers.ts +15 -75
  402. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  403. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  404. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  405. package/src/providers/anthropic/client.ts +0 -47
  406. package/src/providers/gemini/client.ts +9 -23
  407. package/src/providers/gemini-thought-signature.test.ts +63 -0
  408. package/src/providers/gemini-thought-signature.ts +44 -0
  409. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  410. package/src/providers/inference/auth.ts +2 -3
  411. package/src/providers/inference/connection-availability.ts +27 -8
  412. package/src/providers/inference/endpoint-probe.ts +10 -1
  413. package/src/providers/inference/resolve-auth.ts +22 -4
  414. package/src/providers/inference/service-account-token.ts +248 -0
  415. package/src/providers/model-catalog.ts +34 -0
  416. package/src/providers/model-intents.ts +10 -5
  417. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  418. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  419. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  420. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  421. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  422. package/src/providers/openai/chat-completions-provider.ts +212 -79
  423. package/src/providers/openai/codex-models.ts +0 -4
  424. package/src/providers/openai/google-thought-signature.ts +320 -0
  425. package/src/providers/opencode/client.test.ts +73 -4
  426. package/src/providers/opencode/client.ts +60 -3
  427. package/src/providers/openrouter/client.ts +20 -0
  428. package/src/providers/outbound-request-sanitize.ts +96 -0
  429. package/src/providers/retry.ts +30 -19
  430. package/src/providers/unparseable-tool-args.ts +5 -1
  431. package/src/runtime/AGENTS.md +3 -1
  432. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  433. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  434. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  435. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  436. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  437. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  438. package/src/runtime/agent-wake.ts +2 -1
  439. package/src/runtime/assistant-event-hub.ts +21 -3
  440. package/src/runtime/auth/same-actor.ts +38 -3
  441. package/src/runtime/channel-send.ts +316 -0
  442. package/src/runtime/host-observe.ts +11 -1
  443. package/src/runtime/http-server.ts +160 -151
  444. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  445. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  446. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  447. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  448. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  449. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  450. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  451. package/src/runtime/routes/acp-routes.test.ts +15 -0
  452. package/src/runtime/routes/acp-routes.ts +72 -9
  453. package/src/runtime/routes/app-management-routes.ts +73 -0
  454. package/src/runtime/routes/app-routes.ts +1 -1
  455. package/src/runtime/routes/channel-send-routes.ts +85 -0
  456. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  457. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  458. package/src/runtime/routes/conversation-routes.ts +16 -3
  459. package/src/runtime/routes/credential-routes.ts +19 -10
  460. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  461. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  462. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  463. package/src/runtime/routes/events-routes.ts +4 -0
  464. package/src/runtime/routes/host-cu-routes.ts +6 -0
  465. package/src/runtime/routes/index.ts +4 -2
  466. package/src/runtime/routes/log-export-routes.ts +2 -0
  467. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  468. package/src/runtime/routes/migration-routes.ts +6 -20
  469. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  470. package/src/runtime/routes/platform-routes.ts +18 -15
  471. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  472. package/src/runtime/routes/workspace-routes.ts +46 -63
  473. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  474. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  475. package/src/runtime/routes/workspace-utils.ts +17 -0
  476. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  477. package/src/runtime/services/conversation-serializer.ts +16 -3
  478. package/src/runtime/sync/resource-sync-events.ts +2 -1
  479. package/src/schedule/run-script.ts +2 -1
  480. package/src/schedule/schedule-store.ts +9 -2
  481. package/src/schedule/worker.ts +1 -1
  482. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  483. package/src/security/ces-http-record-backend.ts +120 -0
  484. package/src/security/ces-rpc-record-backend.ts +7 -0
  485. package/src/security/untrusted-content.ts +3 -1
  486. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  487. package/src/skills/workspace-skill-md-signature.ts +50 -0
  488. package/src/subagent/consult-context.ts +12 -25
  489. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  490. package/src/tools/acp/list-agents.test.ts +3 -1
  491. package/src/tools/acp/spawn.test.ts +146 -11
  492. package/src/tools/acp/spawn.ts +21 -3
  493. package/src/tools/acp/status.test.ts +94 -0
  494. package/src/tools/acp/status.ts +40 -3
  495. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/browser/browser-execution.ts +1 -1
  497. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  498. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  499. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  500. package/src/tools/browser/network-recorder.ts +2 -1
  501. package/src/tools/capability-offer.test.ts +22 -2
  502. package/src/tools/capability-offer.ts +16 -1
  503. package/src/tools/computer-use/definitions.ts +121 -2
  504. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  505. package/src/tools/credentials/metadata-store.ts +100 -0
  506. package/src/tools/filesystem/search.ts +2 -1
  507. package/src/tools/host-filesystem/edit.test.ts +2 -1
  508. package/src/tools/host-filesystem/edit.ts +2 -2
  509. package/src/tools/host-filesystem/read.test.ts +2 -1
  510. package/src/tools/host-filesystem/read.ts +2 -2
  511. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  512. package/src/tools/host-filesystem/transfer.ts +2 -2
  513. package/src/tools/host-filesystem/write.test.ts +2 -1
  514. package/src/tools/host-filesystem/write.ts +2 -2
  515. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  516. package/src/tools/host-terminal/host-shell.ts +4 -20
  517. package/src/tools/network/web-search.ts +6 -1
  518. package/src/tools/policy-context.ts +6 -7
  519. package/src/tools/skills/scaffold-managed.ts +61 -3
  520. package/src/tools/subagent/spawn.ts +5 -8
  521. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  522. package/src/tools/terminal/sanitized-bash.ts +105 -0
  523. package/src/tools/watch/watch-retro-report.ts +0 -8
  524. package/src/types/host-capabilities.ts +5 -3
  525. package/src/util/__tests__/language-subtag.test.ts +23 -1
  526. package/src/util/ca-bundle.ts +13 -0
  527. package/src/util/language-subtag.ts +16 -0
  528. package/src/util/platform.ts +8 -0
  529. package/src/util/unicode.ts +15 -1
  530. package/src/watch/watch-timeline.ts +2 -2
  531. package/src/workflows/run-manager.ts +2 -1
  532. package/src/workspace/git-porcelain.ts +66 -0
  533. package/src/workspace/git-service.ts +6 -55
  534. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  535. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  536. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  537. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  538. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  539. package/src/workspace/migrations/registry.ts +6 -0
  540. package/src/__tests__/app-source-watcher.test.ts +0 -218
  541. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  542. package/src/__tests__/speaker-identification.test.ts +0 -56
  543. package/src/calls/speaker-identification.ts +0 -233
  544. package/src/daemon/app-source-watcher.ts +0 -218
  545. package/src/messaging/providers/whatsapp/client.ts +0 -29
  546. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  547. package/src/util/fs-watcher-error.ts +0 -36
  548. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -8,9 +8,16 @@
8
8
 
9
9
  import { describe, expect, mock, test } from "bun:test";
10
10
 
11
- import type { AuthMethod, InitializeResponse } from "@agentclientprotocol/sdk";
11
+ import type {
12
+ AuthMethod,
13
+ InitializeResponse,
14
+ SessionConfigOption,
15
+ } from "@agentclientprotocol/sdk";
16
+ import * as acp from "@agentclientprotocol/sdk";
12
17
 
13
18
  import { AcpAgentProcess } from "../agent-process.js";
19
+ import { AcpConfigOptionRefusedError } from "../types.js";
20
+ import { modelOption } from "./helpers/acp-model-option.js";
14
21
 
15
22
  function makeProcess(): AcpAgentProcess {
16
23
  return new AcpAgentProcess(
@@ -160,6 +167,139 @@ describe("AcpAgentProcess loadSession/resumeSession", () => {
160
167
  });
161
168
  });
162
169
 
170
+ describe("AcpAgentProcess config options", () => {
171
+ /** Injects a connection whose session calls report `configOptions`. */
172
+ function stubSessionResponses(
173
+ proc: AcpAgentProcess,
174
+ configOptions: SessionConfigOption[] | null | undefined,
175
+ ): void {
176
+ (proc as unknown as { connection: unknown }).connection = {
177
+ newSession: () =>
178
+ Promise.resolve({ sessionId: "session-1", configOptions }),
179
+ loadSession: () => Promise.resolve({ configOptions }),
180
+ resumeSession: () => Promise.resolve({ configOptions }),
181
+ };
182
+ }
183
+
184
+ test("createSession returns the reported options", async () => {
185
+ const proc = makeProcess();
186
+ const options = [modelOption("sonnet")];
187
+ stubSessionResponses(proc, options);
188
+
189
+ await expect(proc.createSession("/tmp/project")).resolves.toEqual({
190
+ sessionId: "session-1",
191
+ configOptions: options,
192
+ });
193
+ });
194
+
195
+ test("loadSession returns the reported options", async () => {
196
+ const proc = makeProcess();
197
+ const options = [modelOption("sonnet")];
198
+ stubSessionResponses(proc, options);
199
+
200
+ await expect(
201
+ proc.loadSession("session-1", "/tmp/project"),
202
+ ).resolves.toEqual({ configOptions: options });
203
+ });
204
+
205
+ test("resumeSession returns the reported options", async () => {
206
+ const proc = makeProcess();
207
+ const options = [modelOption("opus")];
208
+ stubSessionResponses(proc, options);
209
+
210
+ await expect(
211
+ proc.resumeSession("session-1", "/tmp/project"),
212
+ ).resolves.toEqual({ configOptions: options });
213
+ });
214
+
215
+ test("a null or absent configOptions normalizes to an empty array", async () => {
216
+ const created = makeProcess();
217
+ stubSessionResponses(created, null);
218
+ await expect(created.createSession("/tmp/project")).resolves.toEqual({
219
+ sessionId: "session-1",
220
+ configOptions: [],
221
+ });
222
+
223
+ const loaded = makeProcess();
224
+ stubSessionResponses(loaded, null);
225
+ await expect(
226
+ loaded.loadSession("session-1", "/tmp/project"),
227
+ ).resolves.toEqual({ configOptions: [] });
228
+
229
+ const resumed = makeProcess();
230
+ stubSessionResponses(resumed, undefined);
231
+ await expect(
232
+ resumed.resumeSession("session-1", "/tmp/project"),
233
+ ).resolves.toEqual({ configOptions: [] });
234
+ });
235
+
236
+ test("setConfigOption forwards the value and returns the refreshed set", async () => {
237
+ const proc = makeProcess();
238
+ const calls: unknown[] = [];
239
+ const refreshed = [modelOption("opus")];
240
+ (proc as unknown as { connection: unknown }).connection = {
241
+ setSessionConfigOption: (params: unknown) => {
242
+ calls.push(params);
243
+ return Promise.resolve({ configOptions: refreshed });
244
+ },
245
+ };
246
+
247
+ await expect(
248
+ proc.setConfigOption("session-1", "model", "opus"),
249
+ ).resolves.toEqual(refreshed);
250
+
251
+ expect(calls).toEqual([
252
+ { sessionId: "session-1", configId: "model", value: "opus" },
253
+ ]);
254
+ });
255
+
256
+ test("setConfigOption sends the boolean request variant", async () => {
257
+ const proc = makeProcess();
258
+ const calls: unknown[] = [];
259
+ const refreshed = [modelOption("opus")];
260
+ (proc as unknown as { connection: unknown }).connection = {
261
+ setSessionConfigOption: (params: unknown) => {
262
+ calls.push(params);
263
+ return Promise.resolve({ configOptions: refreshed });
264
+ },
265
+ };
266
+
267
+ await proc.setConfigOption("session-1", "thinking", true);
268
+
269
+ expect(calls).toEqual([
270
+ {
271
+ sessionId: "session-1",
272
+ configId: "thinking",
273
+ type: "boolean",
274
+ value: true,
275
+ },
276
+ ]);
277
+ });
278
+
279
+ test("setConfigOption omits the discriminator for string values", async () => {
280
+ const proc = makeProcess();
281
+ const calls: unknown[] = [];
282
+ (proc as unknown as { connection: unknown }).connection = {
283
+ setSessionConfigOption: (params: unknown) => {
284
+ calls.push(params);
285
+ return Promise.resolve({ configOptions: [modelOption("opus")] });
286
+ },
287
+ };
288
+
289
+ await proc.setConfigOption("session-1", "model", "opus");
290
+
291
+ expect(calls[0]).not.toHaveProperty("type");
292
+ });
293
+
294
+ test("setConfigOption throws when the process is not spawned", async () => {
295
+ const proc = makeProcess();
296
+
297
+ await expect(
298
+ proc.setConfigOption("session-1", "model", "opus"),
299
+ ).rejects.toThrow('ACP agent "test-agent" is not spawned');
300
+ });
301
+ });
302
+
163
303
  describe("AcpAgentProcess auth_required retry", () => {
164
304
  // spawnedEnv is injected the way spawn() builds it ({ ...process.env,
165
305
  // ...config.env }), so the advertised var names use a VELLUM_TEST_ prefix
@@ -196,17 +336,19 @@ describe("AcpAgentProcess auth_required retry", () => {
196
336
  * initialize() so the advertised authMethods are captured.
197
337
  */
198
338
  async function setupAuthProcess(options: {
339
+ command?: string;
199
340
  env?: Record<string, string>;
200
341
  authMethods?: AuthMethod[];
201
342
  /** Rejections to throw from successive newSession calls before succeeding. */
202
343
  newSessionRejections?: unknown[];
203
344
  loadSessionRejections?: unknown[];
204
345
  resumeSessionRejections?: unknown[];
346
+ setConfigOptionRejections?: unknown[];
205
347
  promptRejections?: unknown[];
206
348
  }) {
207
349
  const proc = new AcpAgentProcess(
208
350
  "codex",
209
- { command: "echo", args: [], env: options.env },
351
+ { command: options.command ?? "echo", args: [], env: options.env },
210
352
  () => {
211
353
  throw new Error("client factory should not be called in this test");
212
354
  },
@@ -234,6 +376,10 @@ describe("AcpAgentProcess auth_required retry", () => {
234
376
  options.resumeSessionRejections ?? [],
235
377
  {},
236
378
  );
379
+ const setSessionConfigOption = failThenSucceed(
380
+ options.setConfigOptionRejections ?? [],
381
+ { configOptions: [] },
382
+ );
237
383
  const prompt = failThenSucceed(options.promptRejections ?? [], {
238
384
  stopReason: "end_turn",
239
385
  });
@@ -252,6 +398,7 @@ describe("AcpAgentProcess auth_required retry", () => {
252
398
  newSession,
253
399
  loadSession,
254
400
  resumeSession,
401
+ setSessionConfigOption,
255
402
  prompt,
256
403
  authenticate,
257
404
  };
@@ -264,18 +411,112 @@ describe("AcpAgentProcess auth_required retry", () => {
264
411
  newSession,
265
412
  loadSession,
266
413
  resumeSession,
414
+ setSessionConfigOption,
267
415
  prompt,
268
416
  authenticate,
269
417
  internals,
270
418
  };
271
419
  }
272
420
 
421
+ // An adapter that throws a plain Error reaches the client as a generic
422
+ // "Internal error" carrying the real sentence in `data.details`, which is
423
+ // the shape these fixtures reproduce.
424
+ test("setConfigOption surfaces the adapter's error answer as a refusal", async () => {
425
+ const { proc } = await setupAuthProcess({
426
+ setConfigOptionRejections: [
427
+ new acp.RequestError(-32603, "Internal error", {
428
+ details: "Invalid value for config option model: nope",
429
+ }),
430
+ ],
431
+ });
432
+
433
+ const refusal = await proc
434
+ .setConfigOption("session-1", "model", "nope")
435
+ .catch((err: unknown) => err);
436
+
437
+ expect(refusal).toBeInstanceOf(AcpConfigOptionRefusedError);
438
+ // The adapter's sentence, not the message the SDK framed it in.
439
+ expect((refusal as Error).message).toBe(
440
+ "Invalid value for config option model: nope",
441
+ );
442
+ });
443
+
444
+ test("setConfigOption serializes a payload that names no reason", async () => {
445
+ const { proc } = await setupAuthProcess({
446
+ setConfigOptionRejections: [
447
+ new acp.RequestError(-32603, "Internal error", { code: 7 }),
448
+ ],
449
+ });
450
+
451
+ await expect(
452
+ proc.setConfigOption("session-1", "model", "nope"),
453
+ ).rejects.toThrow('{"code":7}');
454
+ });
455
+
456
+ test("setConfigOption falls back to the message when there is no payload", async () => {
457
+ const { proc } = await setupAuthProcess({
458
+ setConfigOptionRejections: [
459
+ new acp.RequestError(-32603, "Session not found"),
460
+ ],
461
+ });
462
+
463
+ await expect(
464
+ proc.setConfigOption("session-1", "model", "nope"),
465
+ ).rejects.toThrow("Session not found");
466
+ });
467
+
468
+ test.each(["claude-agent-acp", "/usr/local/bin/claude-agent-acp"])(
469
+ "setConfigOption leaves Claude's message-shaped auth failure untyped (%s)",
470
+ async (command) => {
471
+ const expired = new acp.RequestError(-32603, "Internal error", {
472
+ details: "Not logged in",
473
+ });
474
+ const { proc } = await setupAuthProcess({
475
+ command,
476
+ setConfigOptionRejections: [expired],
477
+ });
478
+
479
+ await expect(
480
+ proc.setConfigOption("session-1", "model", "opus"),
481
+ ).rejects.toBe(expired);
482
+ },
483
+ );
484
+
485
+ test("setConfigOption reads Claude's login wording from another adapter as a refusal", async () => {
486
+ const { proc } = await setupAuthProcess({
487
+ command: "codex-acp",
488
+ setConfigOptionRejections: [
489
+ new acp.RequestError(-32603, "Internal error", {
490
+ details: "Not logged in",
491
+ }),
492
+ ],
493
+ });
494
+
495
+ const refusal = await proc
496
+ .setConfigOption("session-1", "model", "gpt-5")
497
+ .catch((err: unknown) => err);
498
+
499
+ expect(refusal).toBeInstanceOf(AcpConfigOptionRefusedError);
500
+ expect((refusal as Error).message).toBe("Not logged in");
501
+ });
502
+
503
+ test("setConfigOption leaves a transport failure untyped", async () => {
504
+ const closed = new Error("ACP connection closed");
505
+ const { proc } = await setupAuthProcess({
506
+ setConfigOptionRejections: [closed],
507
+ });
508
+
509
+ await expect(
510
+ proc.setConfigOption("session-1", "model", "opus"),
511
+ ).rejects.toBe(closed);
512
+ });
513
+
273
514
  test("createSession does not authenticate when newSession succeeds", async () => {
274
515
  const { proc, newSession, authenticate } = await setupAuthProcess({
275
516
  env: { [OPENAI_VAR]: "sk-test" },
276
517
  });
277
518
 
278
- const sessionId = await proc.createSession("/tmp/project");
519
+ const { sessionId } = await proc.createSession("/tmp/project");
279
520
 
280
521
  expect(sessionId).toBe("session-1");
281
522
  expect(newSession).toHaveBeenCalledTimes(1);
@@ -288,7 +529,7 @@ describe("AcpAgentProcess auth_required retry", () => {
288
529
  newSessionRejections: [authRequiredError],
289
530
  });
290
531
 
291
- const sessionId = await proc.createSession("/tmp/project");
532
+ const { sessionId } = await proc.createSession("/tmp/project");
292
533
 
293
534
  expect(sessionId).toBe("session-1");
294
535
  expect(authenticate).toHaveBeenCalledTimes(1);
@@ -387,6 +628,19 @@ describe("AcpAgentProcess auth_required retry", () => {
387
628
  expect(resumeSession).toHaveBeenCalledTimes(2);
388
629
  });
389
630
 
631
+ test("setConfigOption authenticates and retries on auth_required", async () => {
632
+ const { proc, setSessionConfigOption, authenticate } =
633
+ await setupAuthProcess({
634
+ env: { [OPENAI_VAR]: "sk-test" },
635
+ setConfigOptionRejections: [authRequiredError],
636
+ });
637
+
638
+ await proc.setConfigOption("session-1", "model", "opus");
639
+
640
+ expect(authenticate).toHaveBeenCalledWith({ methodId: "openai-api-key" });
641
+ expect(setSessionConfigOption).toHaveBeenCalledTimes(2);
642
+ });
643
+
390
644
  test("prompt authenticates and retries on auth_required, returning the response", async () => {
391
645
  const { proc, prompt, authenticate } = await setupAuthProcess({
392
646
  env: { [OPENAI_VAR]: "sk-test" },
@@ -6,6 +6,8 @@
6
6
 
7
7
  import { describe, expect, test } from "bun:test";
8
8
 
9
+ import { RequestError } from "@agentclientprotocol/sdk";
10
+
9
11
  import { AcpAuthRequiredEventSchema } from "../../api/events/acp-auth-required.js";
10
12
  import { AcpSessionErrorEventSchema } from "../../api/events/acp-session-error.js";
11
13
  import {
@@ -15,6 +17,7 @@ import {
15
17
  CLAUDE_ACP_COMMAND,
16
18
  isAcpAuthRequired,
17
19
  isClaudeAuthFailureMessage,
20
+ requestErrorReason,
18
21
  } from "../auth-required.js";
19
22
 
20
23
  describe("isAcpAuthRequired", () => {
@@ -89,6 +92,99 @@ describe("isClaudeAuthFailureMessage", () => {
89
92
  });
90
93
  });
91
94
 
95
+ describe("requestErrorReason", () => {
96
+ test("decodes the sentence the SDK moved into the payload", () => {
97
+ const rejection = new RequestError(-32603, "Internal error", {
98
+ details: "Failed to authenticate. Please run /login",
99
+ });
100
+
101
+ expect(requestErrorReason(rejection)).toBe(
102
+ "Failed to authenticate. Please run /login",
103
+ );
104
+ // The point of decoding: the generic message never matches.
105
+ expect(isClaudeAuthFailureMessage(rejection.message)).toBe(false);
106
+ expect(isClaudeAuthFailureMessage(requestErrorReason(rejection))).toBe(
107
+ true,
108
+ );
109
+ });
110
+
111
+ test("reads the message off a rejection that carries no payload", () => {
112
+ // Straight off the wire: a plain object, not an instance of any class we
113
+ // control, and with the auth text where the SDK leaves a framed one.
114
+ const rejection = {
115
+ code: -32603,
116
+ message: "Failed to authenticate. Please run /login",
117
+ };
118
+
119
+ expect(requestErrorReason(rejection)).toBe(
120
+ "Failed to authenticate. Please run /login",
121
+ );
122
+ expect(isClaudeAuthFailureMessage(requestErrorReason(rejection))).toBe(
123
+ true,
124
+ );
125
+ });
126
+
127
+ test("keeps the rejection's own message when the payload names no reason", () => {
128
+ expect(requestErrorReason(new RequestError(-32603, "boom", { c: 7 }))).toBe(
129
+ "boom",
130
+ );
131
+ expect(
132
+ requestErrorReason(
133
+ RequestError.methodNotFound("session/set_config_option"),
134
+ ),
135
+ ).toBe('"Method not found": session/set_config_option');
136
+ expect(requestErrorReason(new Error("Not logged in"))).toBe(
137
+ "Not logged in",
138
+ );
139
+ });
140
+
141
+ test("keeps a specific rejection message ahead of supplemental details", () => {
142
+ expect(
143
+ requestErrorReason(
144
+ new RequestError(-32603, "Model unavailable", {
145
+ details: "The selected deployment is temporarily unavailable",
146
+ }),
147
+ ),
148
+ ).toBe("Model unavailable");
149
+ });
150
+
151
+ test("serializes the payload behind a bare Internal error that names no reason", () => {
152
+ expect(
153
+ requestErrorReason(new RequestError(-32603, "Internal error", { c: 7 })),
154
+ ).toBe('{"c":7}');
155
+ });
156
+
157
+ test("serializes the payload behind a generic Invalid params", () => {
158
+ // How the agent-side SDK answers a request its schema rejects.
159
+ expect(
160
+ requestErrorReason(
161
+ RequestError.invalidParams({ _errors: ["model: Invalid option"] }),
162
+ ),
163
+ ).toBe('{"_errors":["model: Invalid option"]}');
164
+ });
165
+
166
+ test("decodes claude-agent-acp's prompt-time 401 from its message", () => {
167
+ // RequestError.internalError({ errorKind }, cliText) as the adapter raises
168
+ // it: the CLI's text rides the message and the payload only names a kind.
169
+ const rejection = new RequestError(
170
+ -32603,
171
+ "Internal error: Failed to authenticate. API Error: 401 OAuth access token has expired.",
172
+ { errorKind: "authentication_failed" },
173
+ );
174
+
175
+ expect(requestErrorReason(rejection)).toBe(rejection.message);
176
+ expect(isClaudeAuthFailureMessage(requestErrorReason(rejection))).toBe(
177
+ true,
178
+ );
179
+ });
180
+
181
+ test("stringifies a rejection that is not an object at all", () => {
182
+ expect(requestErrorReason("plain string")).toBe("plain string");
183
+ expect(requestErrorReason(-32603)).toBe("-32603");
184
+ expect(requestErrorReason(null)).toBe("null");
185
+ });
186
+ });
187
+
92
188
  describe("AcpAuthRequiredError", () => {
93
189
  test("is an Error that carries the agent id and its message", () => {
94
190
  const err = new AcpAuthRequiredError("claude", "needs a reconnect");
@@ -1,9 +1,13 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import type { SessionNotification } from "@agentclientprotocol/sdk";
3
+ import type {
4
+ SessionConfigOption,
5
+ SessionNotification,
6
+ } from "@agentclientprotocol/sdk";
4
7
 
5
8
  import type { AssistantEvent } from "../../api/index.js";
6
9
  import { VellumAcpClientHandler } from "../client-handler.js";
10
+ import { modelOption } from "./helpers/acp-model-option.js";
7
11
 
8
12
  const ACP_SESSION_ID = "acp-session-abc";
9
13
  const PARENT_CONVERSATION_ID = "conv-xyz";
@@ -11,16 +15,21 @@ const PARENT_CONVERSATION_ID = "conv-xyz";
11
15
  function makeHandler(): {
12
16
  handler: VellumAcpClientHandler;
13
17
  sent: AssistantEvent[];
18
+ configOptionUpdates: SessionConfigOption[][];
14
19
  } {
15
20
  const sent: AssistantEvent[] = [];
21
+ const configOptionUpdates: SessionConfigOption[][] = [];
16
22
  const handler = new VellumAcpClientHandler(
17
23
  ACP_SESSION_ID,
18
24
  (msg) => {
19
25
  sent.push(msg);
20
26
  },
21
27
  PARENT_CONVERSATION_ID,
28
+ (configOptions) => {
29
+ configOptionUpdates.push(configOptions);
30
+ },
22
31
  );
23
- return { handler, sent };
32
+ return { handler, sent, configOptionUpdates };
24
33
  }
25
34
 
26
35
  describe("VellumAcpClientHandler.sessionUpdate", () => {
@@ -782,3 +791,82 @@ describe("VellumAcpClientHandler seq + enriched fields", () => {
782
791
  expect(msg.toolTitle).toContain("<redacted");
783
792
  });
784
793
  });
794
+
795
+ describe("VellumAcpClientHandler config_option_update", () => {
796
+ const MODEL_OPTION = modelOption("sonnet");
797
+
798
+ const THOUGHT_OPTION: SessionConfigOption = {
799
+ type: "boolean",
800
+ id: "thinking",
801
+ name: "Extended thinking",
802
+ currentValue: true,
803
+ };
804
+
805
+ function configOptionUpdate(
806
+ configOptions: SessionConfigOption[],
807
+ ): SessionNotification {
808
+ return {
809
+ sessionId: ACP_SESSION_ID,
810
+ update: { sessionUpdate: "config_option_update", configOptions },
811
+ };
812
+ }
813
+
814
+ test("invokes the callback with the full option array", async () => {
815
+ const { handler, sent, configOptionUpdates } = makeHandler();
816
+
817
+ await handler.sessionUpdate(
818
+ configOptionUpdate([MODEL_OPTION, THOUGHT_OPTION]),
819
+ );
820
+
821
+ expect(configOptionUpdates).toEqual([[MODEL_OPTION, THOUGHT_OPTION]]);
822
+ expect(sent).toHaveLength(0);
823
+ });
824
+
825
+ test("callback fires during replay suppression", async () => {
826
+ const { handler, sent, configOptionUpdates } = makeHandler();
827
+
828
+ handler.beginReplaySuppression();
829
+ await handler.sessionUpdate(configOptionUpdate([MODEL_OPTION]));
830
+
831
+ expect(configOptionUpdates).toEqual([[MODEL_OPTION]]);
832
+ expect(sent).toHaveLength(0);
833
+ });
834
+
835
+ test("a handler without the callback tolerates the update", async () => {
836
+ const sent: AssistantEvent[] = [];
837
+ const handler = new VellumAcpClientHandler(
838
+ ACP_SESSION_ID,
839
+ (msg) => {
840
+ sent.push(msg);
841
+ },
842
+ PARENT_CONVERSATION_ID,
843
+ );
844
+
845
+ await handler.sessionUpdate(configOptionUpdate([MODEL_OPTION]));
846
+
847
+ expect(sent).toHaveLength(0);
848
+ });
849
+
850
+ test("the remaining unhandled update types stay unforwarded", async () => {
851
+ const { handler, sent, configOptionUpdates } = makeHandler();
852
+
853
+ await handler.sessionUpdate({
854
+ sessionId: ACP_SESSION_ID,
855
+ update: {
856
+ sessionUpdate: "available_commands_update",
857
+ availableCommands: [{ name: "create_plan", description: "Plan work" }],
858
+ },
859
+ });
860
+ await handler.sessionUpdate({
861
+ sessionId: ACP_SESSION_ID,
862
+ update: { sessionUpdate: "current_mode_update", currentModeId: "ask" },
863
+ });
864
+ await handler.sessionUpdate({
865
+ sessionId: ACP_SESSION_ID,
866
+ update: { sessionUpdate: "session_info_update", title: "Refactor" },
867
+ });
868
+
869
+ expect(sent).toHaveLength(0);
870
+ expect(configOptionUpdates).toHaveLength(0);
871
+ });
872
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Shared test fixture: the `SessionConfigOption` an adapter advertises for
3
+ * model selection, shaped the way claude-agent-acp reports one (a `select`
4
+ * with id and category `model`).
5
+ */
6
+
7
+ import type { SessionConfigOption } from "@agentclientprotocol/sdk";
8
+
9
+ /** A model selector currently sitting on `currentValue`. */
10
+ export function modelOption(currentValue: string): SessionConfigOption {
11
+ return {
12
+ type: "select",
13
+ id: "model",
14
+ name: "Model",
15
+ category: "model",
16
+ currentValue,
17
+ options: [
18
+ { value: "sonnet", name: "Sonnet" },
19
+ { value: "opus", name: "Opus", description: "Most capable" },
20
+ ],
21
+ };
22
+ }
23
+
24
+ /**
25
+ * A model selector sitting on nothing. `deriveModelInfo` reads an empty
26
+ * `currentValue` as "no model reported", which is the only shape that leaves a
27
+ * resumed session with a selector to pin through.
28
+ */
29
+ export function modelOptionWithoutCurrent(): SessionConfigOption {
30
+ return modelOption("");
31
+ }
32
+
33
+ /**
34
+ * A selector that is not the model one, so a set carrying only this reads as
35
+ * an adapter that advertises no model selection.
36
+ */
37
+ export function nonModelOption(): SessionConfigOption {
38
+ return {
39
+ type: "select",
40
+ id: "mode",
41
+ name: "Mode",
42
+ currentValue: "default",
43
+ options: [{ value: "default", name: "Default" }],
44
+ };
45
+ }
46
+
47
+ /** `modelOption`'s options as `deriveModelInfo` flattens them. */
48
+ export const MODEL_OPTION_MODELS = [
49
+ { value: "sonnet", label: "Sonnet" },
50
+ { value: "opus", label: "Opus", description: "Most capable" },
51
+ ];
@@ -66,7 +66,11 @@ function buildSessionWithFakeProcess(opts: {
66
66
  kill: () => {},
67
67
  spawn: () => {},
68
68
  initialize: () => Promise.resolve(),
69
- createSession: () => Promise.resolve(opts.protocolSessionId),
69
+ createSession: () =>
70
+ Promise.resolve({
71
+ sessionId: opts.protocolSessionId,
72
+ configOptions: [],
73
+ }),
70
74
  cancel: () => Promise.resolve(),
71
75
  markStderr: () => 0,
72
76
  stderrSince: () => "",