@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
@@ -1,6 +1,10 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- import { BadRequestError, ForbiddenError } from "../runtime/routes/errors.js";
3
+ import {
4
+ BadRequestError,
5
+ ForbiddenError,
6
+ InternalError,
7
+ } from "../runtime/routes/errors.js";
4
8
  import type { CredentialMetadata } from "../tools/credentials/metadata-store.js";
5
9
 
6
10
  // ---------------------------------------------------------------------------
@@ -15,6 +19,8 @@ let disconnectedProviders: string[];
15
19
  let credentialIdCounter: number;
16
20
  let scrubbedValues: string[];
17
21
  let scrubRejects: boolean;
22
+ let liveRecordsUnreachable: boolean;
23
+ let secretGetUnreachable: boolean;
18
24
 
19
25
  function metaKey(service: string, field: string): string {
20
26
  return `${service}:${field}`;
@@ -49,7 +55,7 @@ mock.module("../security/secure-keys.js", () => ({
49
55
  getSecureKeyAsync: mock(async (key: string) => secureStore.get(key)),
50
56
  getSecureKeyResultAsync: mock(async (key: string) => ({
51
57
  value: secureStore.get(key),
52
- unreachable: false,
58
+ unreachable: secretGetUnreachable,
53
59
  })),
54
60
  deleteSecureKeyAsync: mock(async (key: string) =>
55
61
  secureStore.delete(key) ? "deleted" : "not-found",
@@ -60,7 +66,10 @@ mock.module("../security/secure-keys.js", () => ({
60
66
 
61
67
  mock.module("../tools/credentials/metadata-store.js", () => ({
62
68
  assertMetadataWritable: () => {},
63
- listCredentialMetadata: mock(() => Array.from(metadataStore.values())),
69
+ listCredentialRecordsLive: mock(async () => ({
70
+ records: Array.from(metadataStore.values()),
71
+ unreachable: liveRecordsUnreachable,
72
+ })),
64
73
  getCredentialMetadata: mock((service: string, field: string) =>
65
74
  metadataStore.get(metaKey(service, field)),
66
75
  ),
@@ -201,6 +210,8 @@ describe("credentials routes", () => {
201
210
  credentialIdCounter = 0;
202
211
  scrubbedValues = [];
203
212
  scrubRejects = false;
213
+ liveRecordsUnreachable = false;
214
+ secretGetUnreachable = false;
204
215
  _resetRevealSuccessRegistryForTest();
205
216
  resetForChatMintRegistryForTest();
206
217
  chatCredentialRevealFlag = false;
@@ -521,6 +532,28 @@ describe("credentials routes", () => {
521
532
  );
522
533
  });
523
534
 
535
+ test("stores a pasted Claude token without touching leftover refresh material", async () => {
536
+ secureStore.set("acp:claude_oauth_refresh_token", "stale-refresh");
537
+ secureStore.set("acp:claude_oauth_expires_at", "111");
538
+
539
+ await setRoute!.handler({
540
+ body: {
541
+ service: "acp",
542
+ field: "claude_oauth_token",
543
+ value: "sk-ant-oat01-pasted-token",
544
+ },
545
+ });
546
+
547
+ expect(secureStore.get("acp:claude_oauth_token")).toBe(
548
+ "sk-ant-oat01-pasted-token",
549
+ );
550
+ expect(secureStore.get("acp:claude_oauth_refresh_token")).toBe(
551
+ "stale-refresh",
552
+ );
553
+ expect(secureStore.get("acp:claude_oauth_expires_at")).toBe("111");
554
+ });
555
+
556
+
524
557
  test("a successful set scrubs the normalized value from transcripts exactly once", async () => {
525
558
  /**
526
559
  * The pasted plaintext may already sit in recent transcripts, so a
@@ -799,6 +832,204 @@ describe("credentials routes", () => {
799
832
  expect(result.credentials).toHaveLength(1);
800
833
  expect(result.credentials[0].service).toBe("github");
801
834
  });
835
+
836
+ test("fails immediately when the credential vault is unreachable", async () => {
837
+ liveRecordsUnreachable = true;
838
+ await setRoute!.handler({
839
+ body: { service: "vercel", field: "api_token", value: SECRET_VALUE },
840
+ });
841
+
842
+ await expect(listRoute!.handler({ body: {} })).rejects.toThrow(
843
+ InternalError,
844
+ );
845
+ await expect(listRoute!.handler({ body: {} })).rejects.toThrow(
846
+ /Credential store is unreachable/,
847
+ );
848
+ });
849
+
850
+ test("fails immediately when a secret fetch reports the vault unreachable", async () => {
851
+ await setRoute!.handler({
852
+ body: { service: "vercel", field: "api_token", value: SECRET_VALUE },
853
+ });
854
+ secretGetUnreachable = true;
855
+
856
+ await expect(listRoute!.handler({ body: {} })).rejects.toThrow(
857
+ InternalError,
858
+ );
859
+ });
860
+ });
861
+
862
+ describe("platform-managed credentials", () => {
863
+ const PLATFORM_KEY = "vk-platform-provisioned-key";
864
+ const PLATFORM_BASE_URL = "https://platform.example";
865
+
866
+ /**
867
+ * The platform provisions the pod's own credentials by POSTing them to
868
+ * `/v1/secrets` as `credential` entries, which land in the same secure
869
+ * store and metadata table as a user's own credentials.
870
+ */
871
+ function seedPlatformCredential(field: string, value: string): string {
872
+ const key = `vellum:${field}`;
873
+ secureStore.set(key, value);
874
+ const now = Date.now();
875
+ metadataStore.set(key, {
876
+ credentialId: `cred-${++credentialIdCounter}`,
877
+ service: "vellum",
878
+ field,
879
+ allowedTools: [],
880
+ allowedDomains: [],
881
+ createdAt: now,
882
+ updatedAt: now,
883
+ });
884
+ return metadataStore.get(key)!.credentialId;
885
+ }
886
+
887
+ test("the listing omits platform credentials and keeps the user's own", async () => {
888
+ // GIVEN the platform's provisioned credentials alongside a user's
889
+ seedPlatformCredential("assistant_api_key", PLATFORM_KEY);
890
+ seedPlatformCredential("platform_base_url", PLATFORM_BASE_URL);
891
+ await setRoute!.handler({
892
+ body: { service: "vercel", field: "api_token", value: SECRET_VALUE },
893
+ });
894
+
895
+ // WHEN the credentials are listed
896
+ const result = (await listRoute!.handler({ body: {} })) as ListResponse;
897
+
898
+ // THEN only the user's credential is a row, and no platform value leaks
899
+ expect(result.credentials.map((c) => `${c.service}:${c.field}`)).toEqual([
900
+ "vercel:api_token",
901
+ ]);
902
+ expect(JSON.stringify(result)).not.toContain(PLATFORM_KEY);
903
+ });
904
+
905
+ test("reveal refuses the platform API key for every principal", async () => {
906
+ /**
907
+ * Both farming paths land on this handler: the assistant's own tool
908
+ * shell arrives as `local`, a Settings click as `user`.
909
+ */
910
+ seedPlatformCredential("assistant_api_key", PLATFORM_KEY);
911
+
912
+ for (const principal of ["local", "user", "svc_gateway"]) {
913
+ await expect(
914
+ revealRoute!.handler({
915
+ body: { service: "vellum", field: "assistant_api_key" },
916
+ headers: { "x-vellum-principal-type": principal },
917
+ }),
918
+ ).rejects.toThrow(ForbiddenError);
919
+ }
920
+ });
921
+
922
+ test("reveal refuses a platform credential looked up by UUID", async () => {
923
+ const credentialId = seedPlatformCredential(
924
+ "assistant_api_key",
925
+ PLATFORM_KEY,
926
+ );
927
+
928
+ await expect(
929
+ revealRoute!.handler({
930
+ body: { id: credentialId },
931
+ headers: { "x-vellum-principal-type": "local" },
932
+ }),
933
+ ).rejects.toThrow(/owned by the Vellum platform/);
934
+ });
935
+
936
+ test("inspect refuses platform credentials", async () => {
937
+ seedPlatformCredential("assistant_api_key", PLATFORM_KEY);
938
+ seedPlatformCredential("platform_base_url", PLATFORM_BASE_URL);
939
+
940
+ for (const field of ["assistant_api_key", "platform_base_url"]) {
941
+ await expect(
942
+ inspectRoute!.handler({
943
+ body: { service: "vellum", field },
944
+ }),
945
+ ).rejects.toThrow(ForbiddenError);
946
+ }
947
+ });
948
+
949
+ test("set refuses platform credentials", async () => {
950
+ /**
951
+ * A write is a read by another name: repointing `platform_base_url`
952
+ * sends the next platform call, bearing the API key, to a host the
953
+ * writer chose. The platform's own provisioning does not come through
954
+ * this route; it writes over `POST /v1/secrets`.
955
+ */
956
+ for (const field of ["assistant_api_key", "platform_base_url"]) {
957
+ await expect(
958
+ setRoute!.handler({
959
+ body: { service: "vellum", field, value: "https://attacker.test" },
960
+ }),
961
+ ).rejects.toThrow(ForbiddenError);
962
+ }
963
+
964
+ expect(secureStore.has("vellum:assistant_api_key")).toBe(false);
965
+ expect(secureStore.has("vellum:platform_base_url")).toBe(false);
966
+ });
967
+
968
+ test("set does not overwrite an already-provisioned platform credential", async () => {
969
+ seedPlatformCredential("platform_base_url", PLATFORM_BASE_URL);
970
+
971
+ await expect(
972
+ setRoute!.handler({
973
+ body: {
974
+ service: "vellum",
975
+ field: "platform_base_url",
976
+ value: "https://attacker.test",
977
+ },
978
+ }),
979
+ ).rejects.toThrow(/owned by the Vellum platform/);
980
+
981
+ expect(secureStore.get("vellum:platform_base_url")).toBe(
982
+ PLATFORM_BASE_URL,
983
+ );
984
+ });
985
+
986
+ test("delete refuses platform credentials", async () => {
987
+ seedPlatformCredential("assistant_api_key", PLATFORM_KEY);
988
+ seedPlatformCredential("platform_base_url", PLATFORM_BASE_URL);
989
+
990
+ for (const field of ["assistant_api_key", "platform_base_url"]) {
991
+ await expect(
992
+ deleteRoute!.handler({ body: { service: "vellum", field } }),
993
+ ).rejects.toThrow(ForbiddenError);
994
+ }
995
+
996
+ expect(secureStore.get("vellum:assistant_api_key")).toBe(PLATFORM_KEY);
997
+ expect(secureStore.get("vellum:platform_base_url")).toBe(
998
+ PLATFORM_BASE_URL,
999
+ );
1000
+ });
1001
+
1002
+ test("a user credential is still writable and deletable", async () => {
1003
+ const stored = (await setRoute!.handler({
1004
+ body: { service: "vercel", field: "api_token", value: SECRET_VALUE },
1005
+ })) as SetResponse;
1006
+ expect(stored.service).toBe("vercel");
1007
+ expect(secureStore.get("vercel:api_token")).toBe(SECRET_VALUE);
1008
+
1009
+ const deleted = (await deleteRoute!.handler({
1010
+ body: { service: "vercel", field: "api_token" },
1011
+ })) as DeleteResponse;
1012
+ expect(deleted.field).toBe("api_token");
1013
+ expect(secureStore.has("vercel:api_token")).toBe(false);
1014
+ });
1015
+
1016
+ test("a user credential still inspects and reveals", async () => {
1017
+ seedPlatformCredential("assistant_api_key", PLATFORM_KEY);
1018
+ await setRoute!.handler({
1019
+ body: { service: "vercel", field: "api_token", value: SECRET_VALUE },
1020
+ });
1021
+
1022
+ const inspected = (await inspectRoute!.handler({
1023
+ body: { service: "vercel", field: "api_token" },
1024
+ })) as { scrubbedValue: string };
1025
+ expect(inspected.scrubbedValue).toBe("supe****");
1026
+
1027
+ const revealed = (await revealRoute!.handler({
1028
+ body: { service: "vercel", field: "api_token" },
1029
+ headers: { "x-vellum-principal-type": "user" },
1030
+ })) as { value: string };
1031
+ expect(revealed.value).toBe(SECRET_VALUE);
1032
+ });
802
1033
  });
803
1034
 
804
1035
  describe("platform-managed credentials", () => {
@@ -1092,5 +1323,21 @@ describe("credentials routes", () => {
1092
1323
  // THEN it rejects with a BadRequestError
1093
1324
  await expect(call).rejects.toBeInstanceOf(BadRequestError);
1094
1325
  });
1326
+
1327
+ test("deletes only the requested Claude access-token field", async () => {
1328
+ secureStore.set("acp:claude_oauth_token", "sk-ant-oat01-connected");
1329
+ secureStore.set("acp:claude_oauth_refresh_token", "refresh-leftover");
1330
+ secureStore.set("acp:claude_oauth_expires_at", "111");
1331
+
1332
+ await deleteRoute!.handler({
1333
+ body: { service: "acp", field: "claude_oauth_token" },
1334
+ });
1335
+
1336
+ expect(secureStore.has("acp:claude_oauth_token")).toBe(false);
1337
+ expect(secureStore.get("acp:claude_oauth_refresh_token")).toBe(
1338
+ "refresh-leftover",
1339
+ );
1340
+ expect(secureStore.get("acp:claude_oauth_expires_at")).toBe("111");
1341
+ });
1095
1342
  });
1096
1343
  });
@@ -190,7 +190,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
190
190
  "oauth/credential-token-resolver.ts", // centralized access-token key resolution for OAuth and manual-token providers
191
191
  "oauth/connection-resolver.ts", // resolve OAuthConnection from oauth-store (access_token lookup)
192
192
  "runtime/routes/secret-routes.ts", // HTTP secret management routes (set/delete secrets)
193
- "acp/acp-claude-oauth.ts", // Connect Claude OAuth token vault-store helper (stores sk-ant-oat token via setSecureKeyAsync)
193
+ "acp/acp-claude-oauth.ts", // Connect Claude OAuth token vault-store helper (stores access token plus broker-owned refresh/expiry companions via setSecureKeyAsync)
194
194
  "runtime/routes/acp-claude-auth-routes.ts", // Connect Claude Code daemon OAuth flow (stores OAuth token in vault)
195
195
  "runtime/routes/migration-routes.ts", // migration import credential restore
196
196
  "daemon/conversation-messaging.ts", // credential storage during session messaging
@@ -204,6 +204,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
204
204
  "providers/registry.ts", // provider registry API key lookup for initialization
205
205
  "providers/inference/resolve-auth.ts", // provider_connection auth resolver (api_key path reads vault, mirrors registry.ts)
206
206
  "providers/inference/codex-token-refresh.ts", // Codex OAuth token refresh (reads/writes access_token, refresh_token, expires_at)
207
+ "providers/inference/service-account-token.ts", // Google service-account JWT exchange (reads service-account JSON, writes token_cache blob)
207
208
  "providers/inference/chatgpt-subscription-credentials.ts", // ChatGPT subscription sign-in token storage, shared by the daemon routes and the CLI (setSecureKeyAsync only; no reads)
208
209
  "providers/provider-availability.ts", // provider availability API key check
209
210
  "media/image-credentials.ts", // shared image-gen credential resolver (provider API key lookup)
@@ -211,7 +212,8 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
211
212
  "persistence/llm-request-log-source-clickhouse.ts", // ClickHouse read source — lazy lookup of clickhouse:url + clickhouse:password + vellum:platform_assistant_id for self-scoped mirror reads
212
213
  "persistence/llm-request-log-sink-clickhouse.ts", // ClickHouse write sink — lazy lookup of clickhouse:url + clickhouse:password + vellum:platform_assistant_id for self-scoped log writes
213
214
  "persistence/compaction-log-store-clickhouse.ts", // ClickHouse compaction log writer — lazy lookup of clickhouse:url + clickhouse:password + vellum:platform_assistant_id for self-scoped event writes
214
- "config/platform-rehydration.ts", // startup rehydration of platform base URL + IDs from credential store (daemon and schedule worker)
215
+ "config/platform-identity.ts", // assistant API key lookup for platform identity validate
216
+ "config/platform-rehydration.ts", // startup rehydration of platform base URL from the credential store and ids from platform validate
215
217
  "workspace/migrations/006-services-config.ts", // services config migration reads provider API keys
216
218
  "workspace/migrations/018-rekey-compound-credential-keys.ts", // re-key compound credential storage keys
217
219
  "daemon/conversation-process.ts", // masked provider key display
@@ -224,7 +226,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
224
226
  "runtime/routes/sanity-routes.ts", // Sanity connect/discover routes (reads stored api_token from credential store)
225
227
  "runtime/routes/platform-routes.ts", // CLI platform connect/disconnect/status routes (CLI-migrated to IPC)
226
228
  "runtime/routes/roadmap-routes.ts", // signs public-roadmap calls with the assistant API key (outbound Authorization header only; the value never reaches a response, a log, or the CLI process)
227
- "inbound/platform-callback-registration.ts", // managed credential lookup for platform base URL, assistant ID, and API key
229
+ "inbound/platform-callback-registration.ts", // managed credential lookup for platform base URL and API key
228
230
  "tts/providers/elevenlabs-provider.ts", // ElevenLabs TTS API key lookup
229
231
  "tts/providers/deepgram-provider.ts", // Deepgram TTS API key lookup
230
232
  "tts/providers/xai-provider.ts", // xAI TTS API key lookup
@@ -224,7 +224,10 @@ describe("surfaceProxyResolver — CU tool routing", () => {
224
224
  );
225
225
 
226
226
  expect(result.isError).toBe(true);
227
- expect(result.content).toContain("no desktop client connected");
227
+ expect(result.content).toContain(
228
+ "The Vellum desktop app is needed to view or control your screen",
229
+ );
230
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
228
231
  });
229
232
 
230
233
  test("returns error when proxy exists but client not connected", async () => {
@@ -237,7 +240,10 @@ describe("surfaceProxyResolver — CU tool routing", () => {
237
240
  });
238
241
 
239
242
  expect(result.isError).toBe(true);
240
- expect(result.content).toContain("no desktop client connected");
243
+ expect(result.content).toContain(
244
+ "The Vellum desktop app is needed to view or control your screen",
245
+ );
246
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
241
247
  proxyObj.dispose();
242
248
  });
243
249
 
@@ -479,8 +485,12 @@ describe("surfaceProxyResolver — CU tool routing", () => {
479
485
  // Proxy state is clean after done
480
486
  expect(proxy.stepCount).toBe(0);
481
487
  expect(proxy.actionHistory).toHaveLength(0);
482
- // Only 2 messages sent to client (screenshot + click; done is terminal)
483
- expect(sentMessages).toHaveLength(2);
488
+ // screenshot + click, then done tells the client the task ended so the
489
+ // helper returns the pointer; done itself never reaches the client
490
+ expect(sentMessages).toHaveLength(3);
491
+ expect((sentMessages[2] as Record<string, unknown>).type).toBe(
492
+ "host_cu_cancel",
493
+ );
484
494
  });
485
495
  });
486
496
 
@@ -143,6 +143,31 @@ describe("document write routes publish documents:list", () => {
143
143
  expectDocumentsChanged(events);
144
144
  });
145
145
 
146
+ test("an existing document with a missing conversation can save before relinking", async () => {
147
+ await saveViaRoute();
148
+ const db = getDb();
149
+ db.run("PRAGMA foreign_keys = OFF");
150
+ try {
151
+ db.run("DELETE FROM conversations");
152
+ } finally {
153
+ db.run("PRAGMA foreign_keys = ON");
154
+ }
155
+
156
+ await expect(
157
+ saveViaRoute({ title: "Renamed notes", content: "Edited body" }),
158
+ ).resolves.toMatchObject({ success: true, surfaceId: "doc-sync" });
159
+ createConversation({ id: OTHER_CONVERSATION_ID });
160
+ await expect(
161
+ linkViaRoute("doc-sync", OTHER_CONVERSATION_ID),
162
+ ).resolves.toMatchObject({ success: true });
163
+ await expect(
164
+ invoke("getDocument", { pathParams: { id: "doc-sync" } }),
165
+ ).resolves.toMatchObject({
166
+ title: "Renamed notes",
167
+ content: "Edited body",
168
+ });
169
+ });
170
+
146
171
  test("a content-only save publishes", async () => {
147
172
  // The save moves `updated_at` and the word count, which order and fill the
148
173
  // Library and the assets list on every other client. The saving client
@@ -79,8 +79,6 @@ function makeFakeConversation(
79
79
  getTurnInterfaceContext: () => null,
80
80
  setTurnInterfaceContext: () => {},
81
81
  setTransportHints: () => {},
82
- setTrustContext: () => {},
83
- ensureActorScopedHistory: async () => {},
84
82
  emitActivityState: () => {
85
83
  if (activityFailures > 0) {
86
84
  activityFailures -= 1;
@@ -78,12 +78,6 @@ function makeFakeConversation(options: {
78
78
  setTransportHints: () => {
79
79
  mutationCalls.push("setTransportHints");
80
80
  },
81
- setTrustContext: () => {
82
- mutationCalls.push("setTrustContext");
83
- },
84
- ensureActorScopedHistory: async () => {
85
- mutationCalls.push("ensureActorScopedHistory");
86
- },
87
81
  emitActivityState: () => {
88
82
  mutationCalls.push("emitActivityState");
89
83
  },
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `evictConversationsForReload` drops in-memory conversations after a
3
- * config/prompt/skills reload so the next turn rebuilds them against the new
3
+ * config/prompt reload so the next turn rebuilds them against the new
4
4
  * config. Queued messages live only on the instance being disposed, so the
5
5
  * same "not idle while a queue is pending" rule the periodic evictor applies
6
6
  * has to hold here: `isProcessing()` reads false in the window between a turn
@@ -0,0 +1,47 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ collectDirtyPathsFromPorcelain,
5
+ parsePorcelainZ,
6
+ } from "../workspace/git-porcelain.js";
7
+
8
+ describe("parsePorcelainZ", () => {
9
+ test("parses ordinary modified and untracked records", () => {
10
+ expect(parsePorcelainZ("M tracked.txt\0?? new.txt\0")).toEqual([
11
+ { status: "M ", path: "tracked.txt" },
12
+ { status: "??", path: "new.txt" },
13
+ ]);
14
+ });
15
+
16
+ test("captures rename origin on the same record", () => {
17
+ expect(parsePorcelainZ("R dest.txt\0src.txt\0")).toEqual([
18
+ { status: "R ", path: "dest.txt", origin: "src.txt" },
19
+ ]);
20
+ });
21
+
22
+ test("captures copy origin on the same record", () => {
23
+ expect(parsePorcelainZ("C dest.txt\0src.txt\0")).toEqual([
24
+ { status: "C ", path: "dest.txt", origin: "src.txt" },
25
+ ]);
26
+ });
27
+
28
+ test("skips short leftover NUL entries", () => {
29
+ expect(parsePorcelainZ("M a.txt\0\0")).toEqual([
30
+ { status: "M ", path: "a.txt" },
31
+ ]);
32
+ });
33
+ });
34
+
35
+ describe("collectDirtyPathsFromPorcelain", () => {
36
+ test("flattens rename origin so both paths are staged", () => {
37
+ expect(
38
+ collectDirtyPathsFromPorcelain("R dest.txt\0src.txt\0?? extra.txt\0"),
39
+ ).toEqual(["dest.txt", "src.txt", "extra.txt"]);
40
+ });
41
+
42
+ test("dedupes a path that appears as both origin and destination", () => {
43
+ expect(collectDirtyPathsFromPorcelain("R same.txt\0same.txt\0")).toEqual([
44
+ "same.txt",
45
+ ]);
46
+ });
47
+ });
@@ -557,52 +557,52 @@ describe("HeartbeatService", () => {
557
557
  expect(processMessageCalls).toHaveLength(1);
558
558
  });
559
559
 
560
- test("active hours handles overnight window", async () => {
561
- setHeartbeatConfig({ activeHoursStart: 22, activeHoursEnd: 6 });
560
+ test("active hours handles overnight window", async () => {
561
+ setHeartbeatConfig({ activeHoursStart: 22, activeHoursEnd: 6 });
562
562
 
563
- // 23:00 should be within the window
564
- const service = createService({ getCurrentHour: () => 23 });
565
- await service.runOnce();
566
- expect(processMessageCalls).toHaveLength(1);
563
+ // 23:00 should be within the window
564
+ const service = createService({ getCurrentHour: () => 23 });
565
+ await service.runOnce();
566
+ expect(processMessageCalls).toHaveLength(1);
567
567
 
568
- // 10:00 should be outside the window
569
- processMessageCalls.length = 0;
570
- createdConversations.length = 0;
571
- const service2 = createService({ getCurrentHour: () => 10 });
572
- await service2.runOnce();
573
- expect(processMessageCalls).toHaveLength(0);
574
- });
568
+ // 10:00 should be outside the window
569
+ processMessageCalls.length = 0;
570
+ createdConversations.length = 0;
571
+ const service2 = createService({ getCurrentHour: () => 10 });
572
+ await service2.runOnce();
573
+ expect(processMessageCalls).toHaveLength(0);
574
+ });
575
575
 
576
- test("active hours guard uses user timezone in interval mode", async () => {
577
- setConfig("ui", { detectedTimezone: "America/Los_Angeles" });
578
- setHeartbeatConfig({
579
- cronExpression: null,
580
- timezone: null,
581
- activeHoursStart: 8,
582
- activeHoursEnd: 22,
583
- });
576
+ test("active hours guard uses user timezone in interval mode", async () => {
577
+ setConfig("ui", { detectedTimezone: "America/Los_Angeles" });
578
+ setHeartbeatConfig({
579
+ cronExpression: null,
580
+ timezone: null,
581
+ activeHoursStart: 8,
582
+ activeHoursEnd: 22,
583
+ });
584
584
 
585
- // 23:00 UTC is 16:00 Pacific (inside 8-22) and 23:00 host-local
586
- // (outside 8-22). If the guard still used the host clock this would skip.
587
- const inside = createService({
588
- now: () => new Date("2026-09-08T23:00:00Z"),
589
- getCurrentHour: () => 23,
590
- });
591
- expect(await inside.runOnce()).toBe(true);
592
- expect(processMessageCalls).toHaveLength(1);
585
+ // 23:00 UTC is 16:00 Pacific (inside 8-22) and 23:00 host-local
586
+ // (outside 8-22). If the guard still used the host clock this would skip.
587
+ const inside = createService({
588
+ now: () => new Date("2026-09-08T23:00:00Z"),
589
+ getCurrentHour: () => 23,
590
+ });
591
+ expect(await inside.runOnce()).toBe(true);
592
+ expect(processMessageCalls).toHaveLength(1);
593
593
 
594
- processMessageCalls.length = 0;
595
- createdConversations.length = 0;
594
+ processMessageCalls.length = 0;
595
+ createdConversations.length = 0;
596
596
 
597
- // 06:00 UTC is 23:00 Pacific, outside 8-22, even though 6 is inside
598
- // a naive UTC 8-22 window.
599
- const outside = createService({
600
- now: () => new Date("2026-09-09T06:00:00Z"),
601
- getCurrentHour: () => 6,
602
- });
603
- expect(await outside.runOnce()).toBe(false);
604
- expect(processMessageCalls).toHaveLength(0);
597
+ // 06:00 UTC is 23:00 Pacific, outside 8-22, even though 6 is inside
598
+ // a naive UTC 8-22 window.
599
+ const outside = createService({
600
+ now: () => new Date("2026-09-09T06:00:00Z"),
601
+ getCurrentHour: () => 6,
605
602
  });
603
+ expect(await outside.runOnce()).toBe(false);
604
+ expect(processMessageCalls).toHaveLength(0);
605
+ });
606
606
 
607
607
  test("overlap prevention works", async () => {
608
608
  let resolveFirst: () => void;
@@ -1004,7 +1004,23 @@ describe("HeartbeatService", () => {
1004
1004
  "slack",
1005
1005
  ]);
1006
1006
 
1007
- expect(prompt).toContain("google, slack");
1007
+ expect(prompt).toContain(
1008
+ "google (integration, acts as the connected person), slack (integration, acts as the connected person)",
1009
+ );
1010
+ });
1011
+
1012
+ test("prompt tells an integration apart from the channel bot of the same name", () => {
1013
+ const service = createService();
1014
+ const { prompt } = service.buildPrompt("- Check things", ["slack"]);
1015
+
1016
+ expect(prompt).toContain(
1017
+ "slack (integration, acts as the connected person)",
1018
+ );
1019
+ expect(prompt).not.toContain("slack_channel");
1020
+ expect(prompt).toContain("A channel bot is the assistant's own identity");
1021
+
1022
+ const bot = service.buildPrompt("- Check things", ["slack_channel"]);
1023
+ expect(bot.prompt).toContain("slack_channel (the slack channel bot)");
1008
1024
  });
1009
1025
  });
1010
1026
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Seed `$VELLUM_WORKSPACE_DIR/mcp.json` for tests that go through the
3
+ * real workspace MCP loader.
4
+ */
5
+
6
+ import type { McpConfig } from "../../config/schemas/mcp.js";
7
+ import { saveWorkspaceMcpConfig } from "../../mcp/workspace-mcp-config.js";
8
+
9
+ export function setWorkspaceMcp(servers: McpConfig["servers"]): void {
10
+ saveWorkspaceMcpConfig({ servers });
11
+ }