@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
@@ -51,7 +51,12 @@ mock.module("../../../persistence/embeddings/embedding-backend.js", () => ({
51
51
  }));
52
52
 
53
53
  import { BACKUP_PROFILE_KEYS } from "../../../config/default-profile-names.js";
54
- import { getConfig, loadRawConfig } from "../../../config/loader.js";
54
+ import {
55
+ getConfig,
56
+ loadConfig,
57
+ loadRawConfig,
58
+ } from "../../../config/loader.js";
59
+ import { AssistantConfigSchema } from "../../../config/schema.js";
55
60
  import { LLMConfigBase } from "../../../config/schemas/llm.js";
56
61
  import type { ConversationCreateType } from "../../../persistence/conversation-types.js";
57
62
  import {
@@ -1940,3 +1945,105 @@ describe("ingress URL writes through the generic config routes", () => {
1940
1945
  expect(ingress.lastTunnel).toEqual(LAST_TUNNEL);
1941
1946
  });
1942
1947
  });
1948
+
1949
+ describe("config writes to a per-agent acp entry", () => {
1950
+ const patchRoute = ROUTES.find((r) => r.operationId === "config_patch")!;
1951
+ const setRoute = ROUTES.find((r) => r.operationId === "config_set")!;
1952
+
1953
+ beforeEach(() => {
1954
+ rawConfigFixture = {
1955
+ acp: {
1956
+ agents: {
1957
+ claude: { command: "claude-agent-acp", args: [], model: "opus" },
1958
+ },
1959
+ },
1960
+ };
1961
+ seedRawConfig();
1962
+ });
1963
+
1964
+ function agentEntry(
1965
+ raw: Record<string, unknown>,
1966
+ id = "claude",
1967
+ ): Record<string, unknown> {
1968
+ const acp = raw.acp as Record<string, unknown>;
1969
+ const agents = acp.agents as Record<string, Record<string, unknown>>;
1970
+ return agents[id]!;
1971
+ }
1972
+
1973
+ test("a nulled per-agent model is dropped on both write paths", async () => {
1974
+ await patchRoute.handler({
1975
+ body: { acp: { agents: { claude: { model: null } } } },
1976
+ });
1977
+
1978
+ const patched = agentEntry(loadRawConfig());
1979
+ expect("model" in patched).toBe(false);
1980
+ expect(patched.command).toBe("claude-agent-acp");
1981
+ expect(AssistantConfigSchema.safeParse(loadRawConfig()).success).toBe(true);
1982
+
1983
+ seedRawConfig();
1984
+ await setRoute.handler({
1985
+ body: { path: "acp.agents.claude.model", value: null },
1986
+ });
1987
+
1988
+ expect("model" in agentEntry(loadRawConfig())).toBe(false);
1989
+ expect(AssistantConfigSchema.safeParse(loadRawConfig()).success).toBe(true);
1990
+ expect(loadConfig().acp.agents.claude?.model).toBeUndefined();
1991
+ });
1992
+
1993
+ test("a nulled per-agent command is dropped on both write paths", async () => {
1994
+ await setRoute.handler({
1995
+ body: { path: "acp.agents.claude.command", value: null },
1996
+ });
1997
+
1998
+ expect("command" in agentEntry(loadRawConfig())).toBe(false);
1999
+ expect(AssistantConfigSchema.safeParse(loadRawConfig()).success).toBe(true);
2000
+ expect(loadConfig().acp.agents.claude?.command).toBeUndefined();
2001
+
2002
+ seedRawConfig();
2003
+ await patchRoute.handler({
2004
+ body: { acp: { agents: { claude: { command: null } } } },
2005
+ });
2006
+
2007
+ const patched = agentEntry(loadRawConfig());
2008
+ expect("command" in patched).toBe(false);
2009
+ expect(patched.model).toBe("opus");
2010
+ expect(AssistantConfigSchema.safeParse(loadRawConfig()).success).toBe(true);
2011
+ expect(loadConfig().acp.agents.claude?.command).toBeUndefined();
2012
+ });
2013
+
2014
+ test("a nulled command on an id with no bundled profile is dropped too", async () => {
2015
+ rawConfigFixture = {
2016
+ acp: { agents: { mine: { command: "my-acp", args: [] } } },
2017
+ };
2018
+ seedRawConfig();
2019
+
2020
+ await setRoute.handler({
2021
+ body: { path: "acp.agents.mine.command", value: null },
2022
+ });
2023
+
2024
+ expect("command" in agentEntry(loadRawConfig(), "mine")).toBe(false);
2025
+ const result = AssistantConfigSchema.safeParse(loadRawConfig());
2026
+ expect(result.success).toBe(false);
2027
+ if (result.success) {
2028
+ return;
2029
+ }
2030
+ expect(result.error.issues.map((issue) => issue.path)).toEqual([
2031
+ ["acp", "agents", "mine", "command"],
2032
+ ]);
2033
+ expect(result.error.issues[0]?.message).toContain("acp.agents.mine");
2034
+ });
2035
+
2036
+ test("a model set on a bare bundled entry survives the next load", async () => {
2037
+ rawConfigFixture = {};
2038
+ seedRawConfig();
2039
+
2040
+ await setRoute.handler({
2041
+ body: { path: "acp.agents.claude.model", value: "sonnet" },
2042
+ });
2043
+
2044
+ const entry = agentEntry(loadRawConfig());
2045
+ expect("command" in entry).toBe(false);
2046
+ expect(AssistantConfigSchema.safeParse(loadRawConfig()).success).toBe(true);
2047
+ expect(loadConfig().acp.agents.claude?.model).toBe("sonnet");
2048
+ });
2049
+ });
@@ -377,7 +377,7 @@ describe("GET config/llm/default-provider", () => {
377
377
  expect(availability(result).message).toContain("Vellum-managed");
378
378
  });
379
379
 
380
- test("service_account connection unsupported_auth even with a stored credential", async () => {
380
+ test("service_account connection with invalid credential missing_credential", async () => {
381
381
  seedConnection({
382
382
  name: "gemini-personal",
383
383
  provider: "gemini",
@@ -390,7 +390,7 @@ describe("GET config/llm/default-provider", () => {
390
390
  setConfig("llm", { defaultProvider: { provider: "gemini" } });
391
391
 
392
392
  const result = await get();
393
- expect(availability(result).status).toBe("unsupported_auth");
393
+ expect(availability(result).status).toBe("missing_credential");
394
394
  expect(availability(result).message).toContain("service-account");
395
395
  });
396
396
 
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The dictation cleanup pass rewrites what the user actually said, so a
3
+ * rewrite the provider cut off at the token cap must never become the
4
+ * payload: the raw transcript is sent instead.
5
+ */
6
+
7
+ import { describe, expect, test } from "bun:test";
8
+
9
+ import {
10
+ computeMaxTokens,
11
+ resolveCleanedDictation,
12
+ } from "../diagnostics-routes.js";
13
+
14
+ const SPOKEN =
15
+ "Hello Quill can you generate a list of 20 open HR coordinator or HR " +
16
+ "related roles that are remote and pay about 50 to 60 K per year";
17
+
18
+ describe("resolveCleanedDictation", () => {
19
+ test("accepts an ordinary cleanup", () => {
20
+ const cleaned =
21
+ "Hello Quill, can you generate a list of 20 open HR coordinator or HR " +
22
+ "related roles that are remote and pay about 50 to 60K per year?";
23
+ expect(resolveCleanedDictation(SPOKEN, cleaned, "end_turn")).toEqual({
24
+ text: cleaned,
25
+ rejected: null,
26
+ });
27
+ });
28
+
29
+ test("trims surrounding whitespace off an accepted cleanup", () => {
30
+ const { text } = resolveCleanedDictation(
31
+ "so um I think we should ship the thing on Tuesday you know",
32
+ " I think we should ship the thing on Tuesday. ",
33
+ "end_turn",
34
+ );
35
+ expect(text).toBe("I think we should ship the thing on Tuesday.");
36
+ });
37
+
38
+ test("rejects a rewrite cut off by max_tokens and keeps the raw words", () => {
39
+ expect(
40
+ resolveCleanedDictation(
41
+ SPOKEN,
42
+ "Hello Quill, can you create a list",
43
+ "max_tokens",
44
+ ),
45
+ ).toEqual({ text: SPOKEN, rejected: "truncated" });
46
+ });
47
+
48
+ test("rejects a max_tokens rewrite even when it kept most of the words", () => {
49
+ const nearlyComplete = SPOKEN.slice(0, SPOKEN.length - 4);
50
+ const { rejected } = resolveCleanedDictation(
51
+ SPOKEN,
52
+ nearlyComplete,
53
+ "max_tokens",
54
+ );
55
+ expect(rejected).toBe("truncated");
56
+ });
57
+
58
+ test("rejects the token-cap stop under every provider's name for it", () => {
59
+ for (const stopReason of ["length", "max_output_tokens", "MAX_TOKENS "]) {
60
+ expect(
61
+ resolveCleanedDictation(SPOKEN, "Hello Quill, can you", stopReason)
62
+ .rejected,
63
+ ).toBe("truncated");
64
+ }
65
+ });
66
+
67
+ test("a missing stop reason is not treated as a truncation", () => {
68
+ const cleaned = "Hello Quill, can you generate a list?";
69
+ expect(resolveCleanedDictation(SPOKEN, cleaned, undefined)).toEqual({
70
+ text: cleaned,
71
+ rejected: null,
72
+ });
73
+ });
74
+
75
+ test("a much shorter rewrite is the model's call, not a rejection", () => {
76
+ // The prompt asks for fillers and vague phrasing to go, so a concise
77
+ // rewrite of a rambling utterance is a correct cleanup.
78
+ const spoken =
79
+ "um you know I just kind of wanted to say like thank you so much you know";
80
+ expect(
81
+ resolveCleanedDictation(spoken, "Thank you so much.", "end_turn"),
82
+ ).toEqual({ text: "Thank you so much.", rejected: null });
83
+ });
84
+
85
+ test("an empty rewrite falls back to raw without flagging a rejection", () => {
86
+ // The model returned nothing usable; there is no rewrite to distrust.
87
+ expect(resolveCleanedDictation(SPOKEN, " ", "end_turn")).toEqual({
88
+ text: SPOKEN,
89
+ rejected: null,
90
+ });
91
+ });
92
+ });
93
+
94
+ describe("computeMaxTokens", () => {
95
+ test("budgets room for the transcript, reasoning, and JSON scaffolding", () => {
96
+ // The tool call carries the whole transcript back in `text`, so the
97
+ // budget has to clear the input's own token count with room to spare.
98
+ const estimatedInputTokens = Math.ceil(SPOKEN.length / 3);
99
+ expect(computeMaxTokens(SPOKEN.length)).toBeGreaterThan(
100
+ estimatedInputTokens * 2,
101
+ );
102
+ });
103
+
104
+ test("holds a floor for very short transcripts", () => {
105
+ expect(computeMaxTokens(5)).toBe(512);
106
+ });
107
+ });
@@ -1,19 +1,24 @@
1
1
  /**
2
- * `internal_mcp_add` writes only the transport. Policy fields (enabled,
3
- * risk, tool caps, allow/block lists) are not persisted.
2
+ * `internal_mcp_add` writes only the transport into workspace mcp.json.
3
+ * Policy fields (enabled, risk, tool caps, allow/block lists) are not
4
+ * persisted.
4
5
  */
5
6
 
6
7
  import { beforeEach, describe, expect, mock, test } from "bun:test";
7
8
 
8
- let raw: Record<string, unknown> = {};
9
- let saved: Record<string, unknown> | undefined;
9
+ let stored: { servers: Record<string, { transport: Record<string, unknown> }> } =
10
+ { servers: {} };
11
+ let saved:
12
+ | { servers: Record<string, { transport: Record<string, unknown> }> }
13
+ | undefined;
10
14
 
11
- const actualLoader = await import("../../../config/loader.js");
12
- mock.module("../../../config/loader.js", () => ({
13
- ...actualLoader,
14
- loadRawConfig: () => raw,
15
- saveRawConfig: (next: Record<string, unknown>) => {
15
+ mock.module("../../../mcp/workspace-mcp-config.js", () => ({
16
+ loadWorkspaceMcpConfig: () => stored,
17
+ saveWorkspaceMcpConfig: (next: {
18
+ servers: Record<string, { transport: Record<string, unknown> }>;
19
+ }) => {
16
20
  saved = next;
21
+ stored = next;
17
22
  },
18
23
  }));
19
24
 
@@ -26,13 +31,12 @@ const { ROUTES } = await import("../mcp-auth-routes.js");
26
31
  const addRoute = ROUTES.find((r) => r.operationId === "internal_mcp_add")!;
27
32
 
28
33
  function addedEntry(): Record<string, unknown> {
29
- const mcp = saved?.mcp as { servers: Record<string, unknown> };
30
- return mcp.servers["srv"] as Record<string, unknown>;
34
+ return saved!.servers["srv"] as Record<string, unknown>;
31
35
  }
32
36
 
33
37
  describe("internal_mcp_add persisted shape", () => {
34
38
  beforeEach(() => {
35
- raw = {};
39
+ stored = { servers: {} };
36
40
  saved = undefined;
37
41
  });
38
42
 
@@ -18,7 +18,7 @@
18
18
  * has landed.
19
19
  * POST /v1/acp/claude/auth/exchange — complete a manual/cloud flow: accept the
20
20
  * pasted `code#state` (or a raw code + state), exchange it, and store the
21
- * Claude OAuth token.
21
+ * Claude OAuth token set (access token plus any refresh token and expiry).
22
22
  *
23
23
  * `handleStartAuth` picks loopback vs. manual: a containerized host, or a client
24
24
  * that sets `preferManual` because its browser can't reach the daemon's
@@ -126,7 +126,11 @@ async function handleStartLocalAuth(): Promise<StartResponse> {
126
126
  // can react. Runs in the background — the web client opens `authorize_url`.
127
127
  void flow.completion
128
128
  .then(async (result) => {
129
- await storeAcpClaudeToken(result.tokens.accessToken);
129
+ await storeAcpClaudeToken({
130
+ accessToken: result.tokens.accessToken,
131
+ refreshToken: result.tokens.refreshToken,
132
+ expiresIn: result.tokens.expiresIn,
133
+ });
130
134
  pendingFlows.mark(flow.state, "connected");
131
135
  log.info("ACP Claude local OAuth flow connected");
132
136
  })
@@ -216,7 +220,11 @@ async function handleExchange(args: RouteHandlerArgs): Promise<{ ok: true }> {
216
220
  pending.codeVerifier,
217
221
  state,
218
222
  );
219
- await storeAcpClaudeToken(result.tokens.accessToken);
223
+ await storeAcpClaudeToken({
224
+ accessToken: result.tokens.accessToken,
225
+ refreshToken: result.tokens.refreshToken,
226
+ expiresIn: result.tokens.expiresIn,
227
+ });
220
228
  } catch (err) {
221
229
  const message = err instanceof Error ? err.message : String(err);
222
230
  pendingFlows.mark(state, "error", { error: message });
@@ -262,6 +262,21 @@ describe("POST /v1/acp/spawn", () => {
262
262
  // Guard runs before the approval gate — no prompt is surfaced.
263
263
  expect(confirmationRequests).toHaveLength(0);
264
264
  });
265
+
266
+ test("rejects a non-string model before the approval gate", async () => {
267
+ const handler = getSpawnHandler();
268
+ await expect(
269
+ handler({
270
+ body: {
271
+ agent: "claude",
272
+ task: "do a thing",
273
+ conversationId: "conv-1",
274
+ model: 42,
275
+ },
276
+ }),
277
+ ).rejects.toThrow("model must be a string when provided");
278
+ expect(confirmationRequests).toHaveLength(0);
279
+ });
265
280
  });
266
281
 
267
282
  // ---------------------------------------------------------------------------
@@ -24,8 +24,11 @@ import {
24
24
  AcpResumeError,
25
25
  AcpSessionNotFoundError,
26
26
  } from "../../acp/session-manager.js";
27
- import type { AcpSessionState } from "../../acp/types.js";
28
- import type { AssistantEvent } from "../../api/index.js";
27
+ import { type AcpSessionState, isLiveAcpStatus } from "../../acp/types.js";
28
+ import {
29
+ AcpSessionModelUpdateEventSchema,
30
+ type AssistantEvent,
31
+ } from "../../api/index.js";
29
32
  import { getConfig } from "../../config/loader.js";
30
33
  import { createGuardianRequestForConfirmation } from "../../permissions/confirmation-guardian-request.js";
31
34
  import type { UserDecision } from "../../permissions/types.js";
@@ -53,6 +56,10 @@ const log = getLogger("acp-routes");
53
56
  const DEFAULT_SESSION_LIMIT = 50;
54
57
  const MAX_SESSION_LIMIT = 500;
55
58
 
59
+ /** The option shape the `acp_session_model_update` event already publishes. */
60
+ const acpModelOptionsSchema =
61
+ AcpSessionModelUpdateEventSchema.shape.availableModels;
62
+
56
63
  const sessionEntrySchema = z.object({
57
64
  id: z.string(),
58
65
  agentId: z.string(),
@@ -68,6 +75,15 @@ const sessionEntrySchema = z.object({
68
75
  /** Credential failure that ended the run, when one did. Drives the inline
69
76
  * Connect card on reopen; cleared when a replacement token is stored. */
70
77
  authErrorCode: z.string().optional(),
78
+ /** Model a live session is running on. Absent for history rows, which have
79
+ * no live process to ask. */
80
+ model: z.string().optional(),
81
+ /** Models a live session could run on. Absent for history rows. */
82
+ availableModels: acpModelOptionsSchema.optional(),
83
+ /** Scopes live model revisions to one assistant process. */
84
+ modelRevisionEpoch: z.string().uuid().optional(),
85
+ /** Orders live model state within `modelRevisionEpoch`. */
86
+ modelRevision: z.number().int().nonnegative().optional(),
71
87
  usedTokens: z.number().optional(),
72
88
  contextSize: z.number().optional(),
73
89
  costAmount: z.number().optional(),
@@ -214,17 +230,26 @@ async function spawnSession({ body, abortSignal }: RouteHandlerArgs) {
214
230
  const task = body?.task as string | undefined;
215
231
  const conversationId = body?.conversationId as string | undefined;
216
232
  const cwd = (body?.cwd as string | undefined) ?? process.cwd();
233
+ const model = body?.model ?? undefined;
217
234
 
218
235
  if (!agent || !task || !conversationId) {
219
236
  throw new BadRequestError("agent, task, and conversationId are required");
220
237
  }
238
+ if (model !== undefined && typeof model !== "string") {
239
+ throw new BadRequestError("model must be a string when provided");
240
+ }
221
241
 
222
242
  // High-risk approval gate. Block BEFORE any side effects — resolution can
223
243
  // trigger a `bun` global install (auto-install.ts) and `manager.spawn`
224
244
  // launches the host subprocess — so an unapproved request mutates nothing.
225
245
  const decision = await awaitRouteApproval({
226
246
  toolName: "acp_spawn",
227
- input: { agent, task, cwd },
247
+ input: {
248
+ agent,
249
+ task,
250
+ cwd,
251
+ ...(model !== undefined ? { model } : {}),
252
+ },
228
253
  conversationId,
229
254
  signal: abortSignal,
230
255
  });
@@ -262,17 +287,33 @@ async function spawnSession({ body, abortSignal }: RouteHandlerArgs) {
262
287
  );
263
288
 
264
289
  const manager = getAcpSessionManager();
265
- const { acpSessionId, protocolSessionId } = await manager.spawn(
290
+ const {
291
+ acpSessionId,
292
+ protocolSessionId,
293
+ requestedModel,
294
+ effectiveModel,
295
+ modelWarning,
296
+ } = await manager.spawn(
266
297
  agent,
267
298
  agentConfig,
268
299
  task,
269
300
  cwd,
270
301
  conversationId,
271
302
  broadcastMessage,
303
+ { model },
272
304
  );
273
305
 
274
306
  log.info({ acpSessionId, protocolSessionId, agent }, "ACP spawn succeeded");
275
- return { acpSessionId, protocolSessionId, agent };
307
+ // A refused model is a warning, not a failed spawn: the session is live on
308
+ // the agent's own model.
309
+ return {
310
+ acpSessionId,
311
+ protocolSessionId,
312
+ agent,
313
+ requestedModel: requestedModel ?? null,
314
+ effectiveModel: effectiveModel ?? null,
315
+ ...(modelWarning ? { modelWarning } : {}),
316
+ };
276
317
  }
277
318
 
278
319
  async function steerSession({ pathParams, body }: RouteHandlerArgs) {
@@ -588,10 +629,7 @@ function deleteSession({ pathParams }: RouteHandlerArgs) {
588
629
 
589
630
  try {
590
631
  const state = manager.getStatus(id);
591
- if (
592
- !Array.isArray(state) &&
593
- (state.status === "running" || state.status === "initializing")
594
- ) {
632
+ if (!Array.isArray(state) && isLiveAcpStatus(state.status)) {
595
633
  throw new ConflictError(
596
634
  `ACP session "${id}" is still ${state.status}. Cancel or close it before deleting.`,
597
635
  );
@@ -640,11 +678,32 @@ export const ROUTES: RouteDefinition[] = [
640
678
  task: z.string().describe("Task description"),
641
679
  conversationId: z.string(),
642
680
  cwd: z.string().describe("Working directory").optional(),
681
+ model: z
682
+ .string()
683
+ .optional()
684
+ .describe("Optional model id or alias to request for the session."),
643
685
  }),
644
686
  responseBody: z.object({
645
687
  acpSessionId: z.string(),
646
688
  protocolSessionId: z.string(),
647
689
  agent: z.string(),
690
+ requestedModel: z
691
+ .string()
692
+ .nullable()
693
+ .describe("The model explicitly requested for this spawn, if any."),
694
+ effectiveModel: z
695
+ .string()
696
+ .nullable()
697
+ .describe(
698
+ "The top-level session model reported by the ACP adapter, if any.",
699
+ ),
700
+ modelWarning: z
701
+ .string()
702
+ .optional()
703
+ .describe(
704
+ "Why the requested model was not applied. The session is running " +
705
+ "on the agent's own model.",
706
+ ),
648
707
  }),
649
708
  },
650
709
  {
@@ -852,6 +911,10 @@ function listMergedSessions(opts: { limit: number; conversationId?: string }): {
852
911
  parentToolUseId: s.parentToolUseId,
853
912
  authErrorCode: s.authErrorCode,
854
913
  authErrorCredential: s.authErrorCredential,
914
+ model: s.model,
915
+ availableModels: s.availableModels,
916
+ modelRevisionEpoch: s.modelRevisionEpoch,
917
+ modelRevision: s.modelRevision,
855
918
  usedTokens: s.latestUsage?.usedTokens,
856
919
  contextSize: s.latestUsage?.contextSize,
857
920
  costAmount: s.latestUsage?.costAmount,
@@ -51,6 +51,7 @@ import { compileApp, runCompile } from "../../bundler/app-compiler.js";
51
51
  import { scanBundle } from "../../bundler/bundle-scanner.js";
52
52
  import type { SignatureJson } from "../../bundler/bundle-signer.js";
53
53
  import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
54
+ import { notifyAppSurfacesChanged } from "../../daemon/app-change-notify.js";
54
55
  import { compareSemver } from "../../daemon/handlers/shared.js";
55
56
  import { computeContentId } from "../../util/content-id.js";
56
57
  import { getLogger } from "../../util/logger.js";
@@ -128,6 +129,17 @@ const appListItemSchema = z.object({
128
129
 
129
130
  type AppListItem = z.infer<typeof appListItemSchema>;
130
131
 
132
+ const compileDiagnosticSchema = z.object({
133
+ text: z.string(),
134
+ location: z
135
+ .object({
136
+ file: z.string(),
137
+ line: z.number(),
138
+ column: z.number(),
139
+ })
140
+ .optional(),
141
+ });
142
+
131
143
  /**
132
144
  * The pin route's body. `RouteDefinition.requestBody` is a codegen signal and
133
145
  * does not validate, so the handler parses against this too; declaring it once
@@ -169,7 +181,7 @@ function workspaceAppItem(a: AppDefinition): AppListItem {
169
181
  description: a.description,
170
182
  icon: a.icon,
171
183
  createdAt: a.createdAt,
172
- updatedAt: a.updatedAt,
184
+ updatedAt: appContentUpdatedAt(getAppDirPath(a.id), a.updatedAt),
173
185
  version: a.version ?? "1.0.0",
174
186
  contentId: computeContentId(a.name),
175
187
  origin: "workspace",
@@ -191,13 +203,28 @@ function pluginAppItem(app: EnumeratedApp): AppListItem {
191
203
  id: app.id,
192
204
  name: app.name,
193
205
  createdAt: mtime,
194
- updatedAt: mtime,
206
+ updatedAt: appContentUpdatedAt(app.sourcePath, mtime),
195
207
  version: "1.0.0",
196
208
  contentId: computeContentId(app.name),
197
209
  origin: `plugin:${pluginName}`,
198
210
  };
199
211
  }
200
212
 
213
+ /** The newest persisted metadata or compiled-output revision for an app. */
214
+ function appContentUpdatedAt(
215
+ sourcePath: string,
216
+ metadataUpdatedAt: number,
217
+ ): number {
218
+ try {
219
+ return Math.max(
220
+ metadataUpdatedAt,
221
+ statSync(join(sourcePath, "dist")).mtimeMs,
222
+ );
223
+ } catch {
224
+ return metadataUpdatedAt;
225
+ }
226
+ }
227
+
201
228
  function getAppDataResult(
202
229
  method: string,
203
230
  appId: string,
@@ -896,6 +923,44 @@ async function handleOpenApp({ pathParams }: RouteHandlerArgs) {
896
923
  return { ...result, html };
897
924
  }
898
925
 
926
+ /**
927
+ * Compile an app's source into dist/ and refresh open surfaces.
928
+ * Runs in the assistant process so open surfaces, client broadcasts, and
929
+ * published-app redeploy see the new build. Workspace and plugin apps both
930
+ * compile in place so local plugin edits can be rebuilt without waiting for
931
+ * the plugin source watcher.
932
+ */
933
+ async function handleRefreshApp({ pathParams }: RouteHandlerArgs) {
934
+ const appId = pathParams?.id;
935
+ if (!appId) {
936
+ throw new BadRequestError("App id is required.");
937
+ }
938
+
939
+ const source = resolveAppSource(appId);
940
+ if (!source) {
941
+ throw new NotFoundError(
942
+ `App "${appId}" not found. Run 'assistant apps list' to see available apps.`,
943
+ );
944
+ }
945
+
946
+ const compileResult = await compileApp(source.sourceDir);
947
+ notifyAppSurfacesChanged(source.id, { fileChange: true });
948
+
949
+ return {
950
+ ok: true as const,
951
+ appId: source.id,
952
+ name: source.name,
953
+ compiled: compileResult.ok,
954
+ compile_duration_ms: compileResult.durationMs,
955
+ ...(compileResult.ok
956
+ ? {}
957
+ : {
958
+ compile_errors: compileResult.errors,
959
+ compile_warnings: compileResult.warnings,
960
+ }),
961
+ };
962
+ }
963
+
899
964
  /**
900
965
  * Reject a mutation targeting a plugin-bundled app. Plugin apps are owned by
901
966
  * their plugin and are read-only over this surface — their source is not
@@ -1253,6 +1318,29 @@ export const ROUTES: RouteDefinition[] = [
1253
1318
  origin: z.string(),
1254
1319
  }),
1255
1320
  },
1321
+ {
1322
+ operationId: "apps_refresh",
1323
+ endpoint: "apps/:id/refresh",
1324
+ method: "POST",
1325
+ policy: {
1326
+ requiredScopes: ["settings.write"],
1327
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
1328
+ },
1329
+ handler: handleRefreshApp,
1330
+ summary: "Compile an app",
1331
+ description:
1332
+ "Compile workspace or plugin app source into dist/ and refresh open surfaces.",
1333
+ tags: ["apps"],
1334
+ responseBody: z.object({
1335
+ ok: z.literal(true),
1336
+ appId: z.string(),
1337
+ name: z.string(),
1338
+ compiled: z.boolean(),
1339
+ compile_duration_ms: z.number(),
1340
+ compile_errors: z.array(compileDiagnosticSchema).optional(),
1341
+ compile_warnings: z.array(compileDiagnosticSchema).optional(),
1342
+ }),
1343
+ },
1256
1344
  {
1257
1345
  operationId: "apps_delete",
1258
1346
  endpoint: "apps/:id/delete",
@@ -92,7 +92,7 @@ function serveCompiledApp(
92
92
  );
93
93
  const body = isLegacySingleFileDir(appDir)
94
94
  ? UNSUPPORTED_LEGACY_APP_HTML
95
- : `<p>App has not been compiled yet. Edit a source file to trigger a build.</p>`;
95
+ : `<p>App has not been compiled yet. Run <code>assistant apps refresh</code> to build.</p>`;
96
96
  return (
97
97
  `<!DOCTYPE html><html><head><title>${escapedName}</title></head>` +
98
98
  `<body>${body}</body></html>`