@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
@@ -21,7 +21,9 @@ import {
21
21
  const testDataDir = process.env.VELLUM_WORKSPACE_DIR!;
22
22
 
23
23
  import {
24
+ describeQdrantStartFailure,
24
25
  QdrantManager,
26
+ QdrantStartError,
25
27
  resolveQdrantReleaseAsset,
26
28
  } from "../persistence/embeddings/qdrant-manager.js";
27
29
 
@@ -395,6 +397,136 @@ describe("QdrantManager", () => {
395
397
  // THEN the error carries the stdout explanation, not just the noise
396
398
  await expect(mgr.start()).rejects.toThrow("Failed to load local shard");
397
399
  }, 10_000);
400
+
401
+ test("classifies a crash as an exit with its code and captured output", async () => {
402
+ // The reporter must not have to parse the message: the step, the exit
403
+ // code and the raw streams travel as fields.
404
+ placeFakeBinary(
405
+ '#!/bin/sh\necho "Panic occurred: no such shard"\nexit 101',
406
+ );
407
+
408
+ const mgr = new QdrantManager({
409
+ url: `http://127.0.0.1:${getTestPort()}`,
410
+ ...FAST_TIMEOUTS,
411
+ });
412
+
413
+ let caught: unknown;
414
+ await mgr.start().catch((err) => {
415
+ caught = err;
416
+ });
417
+
418
+ expect(caught).toBeInstanceOf(QdrantStartError);
419
+ const err = caught as QdrantStartError;
420
+ expect(err.kind).toBe("exited");
421
+ expect(err.exitCode).toBe(101);
422
+ expect(err.stdout).toContain("no such shard");
423
+ }, 10_000);
424
+
425
+ test("classifies a readiness timeout as not_ready with no exit code", async () => {
426
+ placeFakeBinary("#!/bin/sh\nexec sleep 300");
427
+
428
+ const mgr = new QdrantManager({
429
+ url: `http://127.0.0.1:${getTestPort()}`,
430
+ ...FAST_TIMEOUTS,
431
+ });
432
+
433
+ let caught: unknown;
434
+ await mgr.start().catch((err) => {
435
+ caught = err;
436
+ });
437
+
438
+ expect(caught).toBeInstanceOf(QdrantStartError);
439
+ expect((caught as QdrantStartError).kind).toBe("not_ready");
440
+ expect((caught as QdrantStartError).exitCode).toBeNull();
441
+ }, 10_000);
442
+ });
443
+
444
+ describe("describeQdrantStartFailure", () => {
445
+ const dataDir = "/home/example/workspace-data";
446
+ const failure = (stdout: string, kind: "exited" | "not_ready" = "exited") =>
447
+ new QdrantStartError(
448
+ "boom",
449
+ kind,
450
+ kind === "exited" ? 101 : null,
451
+ stdout,
452
+ "",
453
+ );
454
+
455
+ test("takes the reason from the last panic marker, after the backtrace", () => {
456
+ const stdout =
457
+ " 0: backtrace frame\n 1: another frame\n" +
458
+ "ERROR qdrant::startup: Panic occurred in file mod.rs at line 301: " +
459
+ "Failed to load local shard";
460
+
461
+ expect(describeQdrantStartFailure(failure(stdout), dataDir)).toEqual({
462
+ kind: "exited",
463
+ exit_code: 101,
464
+ panic:
465
+ "Panic occurred in file mod.rs at line 301: Failed to load local shard",
466
+ });
467
+ });
468
+
469
+ test("replaces the data directory wherever it appears", () => {
470
+ const stdout = `Panic occurred: cannot open ${dataDir}/qdrant/collections/x and ${dataDir}/qdrant/wal`;
471
+
472
+ expect(describeQdrantStartFailure(failure(stdout), dataDir).panic).toBe(
473
+ "Panic occurred: cannot open <data>/qdrant/collections/x and <data>/qdrant/wal",
474
+ );
475
+ });
476
+
477
+ test("falls back to the last stdout line when Qdrant died without panicking", () => {
478
+ const stdout =
479
+ "INFO starting\nERROR Address already in use (os error 48)\n";
480
+
481
+ expect(describeQdrantStartFailure(failure(stdout), dataDir).panic).toBe(
482
+ "ERROR Address already in use (os error 48)",
483
+ );
484
+ });
485
+
486
+ test("reports no reason for a silent timeout", () => {
487
+ expect(
488
+ describeQdrantStartFailure(failure("", "not_ready"), dataDir),
489
+ ).toEqual({ kind: "not_ready", exit_code: null, panic: null });
490
+ });
491
+
492
+ /**
493
+ * The telemetry server's measure: JSON with every code unit above 0x7E
494
+ * escaped to six ASCII bytes (`jsonByteLength` in
495
+ * `telemetry-wire.generated.ts`), minus the enclosing quotes.
496
+ */
497
+ const serverBytes = (s: string) =>
498
+ JSON.stringify(s).replace(/[^\x00-\x7e]/g, "\\uxxxx").length - 2;
499
+
500
+ test("caps an ASCII reason at the byte budget", () => {
501
+ const { panic } = describeQdrantStartFailure(
502
+ failure("Panic occurred: " + "x".repeat(5_000)),
503
+ dataDir,
504
+ );
505
+
506
+ expect(panic?.length).toBe(1_024);
507
+ expect(serverBytes(panic!)).toBe(1_024);
508
+ });
509
+
510
+ test("caps a non-ASCII reason by the server's escaped size, not by characters", () => {
511
+ // Each CJK character serializes to six bytes on the server, so a
512
+ // character cap would overshoot the budget six-fold and the event would
513
+ // be dropped at ingest.
514
+ const { panic } = describeQdrantStartFailure(
515
+ failure("Panic occurred: " + "測".repeat(2_000)),
516
+ dataDir,
517
+ );
518
+
519
+ expect(serverBytes(panic!)).toBeLessThanOrEqual(1_024);
520
+ expect(serverBytes(panic! + "測")).toBeGreaterThan(1_024);
521
+ });
522
+
523
+ test("never drops an unclassified failure", () => {
524
+ expect(describeQdrantStartFailure(new Error("nope"), dataDir)).toEqual({
525
+ kind: "unknown",
526
+ exit_code: null,
527
+ panic: null,
528
+ });
529
+ });
398
530
  });
399
531
 
400
532
  // ── Binary Detection ─────────────────────────────────────────
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Tests for resolving an app query by name, slug, or id.
3
+ */
4
+
5
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
9
+
10
+ import { createApp } from "../apps/app-store.js";
11
+ import { resolveAppQuery } from "../apps/resolve-app.js";
12
+ import { getWorkspacePluginsDir } from "../util/platform.js";
13
+
14
+ let workspaceDir: string;
15
+ let previousWorkspaceDir: string | undefined;
16
+
17
+ function freshWorkspace(): string {
18
+ return join(
19
+ tmpdir(),
20
+ `vellum-resolve-app-${Date.now()}-${Math.random().toString(36).slice(2)}`,
21
+ );
22
+ }
23
+
24
+ function installPlugin(name: string): string {
25
+ const pluginDir = join(getWorkspacePluginsDir(), name);
26
+ mkdirSync(pluginDir, { recursive: true });
27
+ writeFileSync(
28
+ join(pluginDir, "package.json"),
29
+ JSON.stringify({ name, version: "1.0.0" }),
30
+ );
31
+ return pluginDir;
32
+ }
33
+
34
+ function bundleApp(pluginDir: string, app: string): void {
35
+ const appDir = join(pluginDir, "apps", app);
36
+ mkdirSync(appDir, { recursive: true });
37
+ writeFileSync(join(appDir, "index.html"), "<h1>Plugin app</h1>");
38
+ }
39
+
40
+ beforeEach(() => {
41
+ previousWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
42
+ workspaceDir = freshWorkspace();
43
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
44
+ });
45
+
46
+ afterEach(() => {
47
+ rmSync(workspaceDir, { recursive: true, force: true });
48
+ if (previousWorkspaceDir === undefined) {
49
+ delete process.env.VELLUM_WORKSPACE_DIR;
50
+ } else {
51
+ process.env.VELLUM_WORKSPACE_DIR = previousWorkspaceDir;
52
+ }
53
+ });
54
+
55
+ describe("resolveAppQuery", () => {
56
+ test("resolves a workspace app by display name, slug, and id", () => {
57
+ const created = createApp({
58
+ name: "Budget",
59
+ schemaJson: "{}",
60
+ htmlDefinition: "<h1>Budget</h1>",
61
+ });
62
+
63
+ const byName = resolveAppQuery("Budget");
64
+ expect(byName.ok).toBe(true);
65
+ if (byName.ok) {
66
+ expect(byName.app.id).toBe(created.id);
67
+ }
68
+
69
+ const byNameCase = resolveAppQuery("budget");
70
+ expect(byNameCase.ok).toBe(true);
71
+ if (byNameCase.ok) {
72
+ expect(byNameCase.app.id).toBe(created.id);
73
+ }
74
+
75
+ const slug = created.dirName ?? "";
76
+ expect(slug.length).toBeGreaterThan(0);
77
+ const bySlug = resolveAppQuery(slug);
78
+ expect(bySlug.ok).toBe(true);
79
+ if (bySlug.ok) {
80
+ expect(bySlug.app.id).toBe(created.id);
81
+ }
82
+
83
+ const byId = resolveAppQuery(created.id);
84
+ expect(byId.ok).toBe(true);
85
+ if (byId.ok) {
86
+ expect(byId.app.id).toBe(created.id);
87
+ }
88
+ });
89
+
90
+ test("returns not_found for an unknown name", () => {
91
+ const result = resolveAppQuery("missing");
92
+ expect(result).toEqual({
93
+ ok: false,
94
+ reason: "not_found",
95
+ query: "missing",
96
+ });
97
+ });
98
+
99
+ test("returns ambiguous when two apps share a display name", () => {
100
+ const first = createApp({
101
+ name: "Notes",
102
+ schemaJson: "{}",
103
+ htmlDefinition: "<h1>One</h1>",
104
+ });
105
+ const second = createApp({
106
+ name: "Notes",
107
+ schemaJson: "{}",
108
+ htmlDefinition: "<h1>Two</h1>",
109
+ });
110
+
111
+ const result = resolveAppQuery("Notes");
112
+ expect(result.ok).toBe(false);
113
+ if (!result.ok && result.reason === "ambiguous") {
114
+ const ids = result.matches.map((m) => m.id).sort();
115
+ expect(ids).toEqual([first.id, second.id].sort());
116
+ } else {
117
+ throw new Error("expected ambiguous resolution");
118
+ }
119
+ });
120
+
121
+ test("resolves a plugin app by directory name", () => {
122
+ const pluginDir = installPlugin("charts");
123
+ bundleApp(pluginDir, "viewer");
124
+
125
+ const result = resolveAppQuery("viewer");
126
+ expect(result.ok).toBe(true);
127
+ if (result.ok) {
128
+ expect(result.app.origin).toEqual({
129
+ kind: "plugin",
130
+ pluginName: "charts",
131
+ });
132
+ expect(result.app.id).toBe("plugins~charts~viewer");
133
+ }
134
+ });
135
+ });
@@ -1103,6 +1103,7 @@ describe("scaffold_managed_skill tool", () => {
1103
1103
  activation_hints: HINTS,
1104
1104
  overwrite: true,
1105
1105
  files: [{ path: "references/failure-modes.md", content: "gotchas" }],
1106
+ change_summary: "Added the failure modes reference.",
1106
1107
  },
1107
1108
  makeRetrospectiveContext(),
1108
1109
  );
@@ -1550,6 +1551,7 @@ describe("scaffold_managed_skill tool", () => {
1550
1551
  body_markdown: "V2 procedure.",
1551
1552
  activation_hints: HINTS,
1552
1553
  overwrite: true,
1554
+ change_summary: "Refined the procedure.",
1553
1555
  },
1554
1556
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1555
1557
  lineageSeam(),
@@ -1702,6 +1704,7 @@ describe("background skill update notification", () => {
1702
1704
  body_markdown: "1. Refined steps.",
1703
1705
  activation_hints: HINTS,
1704
1706
  overwrite: true,
1707
+ change_summary: "Refined the steps.",
1705
1708
  },
1706
1709
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1707
1710
  lineage(),
@@ -1715,9 +1718,7 @@ describe("background skill update notification", () => {
1715
1718
  // must be the conversation the work came from, not the hidden fork.
1716
1719
  expect(signal.sourceContextId).toBe("source-conv");
1717
1720
  expect(signal.contextPayload?.skillId).toBe("weekly-export");
1718
- expect(String(signal.contextPayload?.summary)).toContain(
1719
- "Weekly Report Export",
1720
- );
1721
+ expect(signal.contextPayload?.summary).toBe("Refined the steps.");
1721
1722
  // The home feed falls back to `title`/`body` when no channel copy was
1722
1723
  // rendered, which is the intended quiet shape for this signal. Without
1723
1724
  // them a suppressed delivery would leave the feed item unwritten.
@@ -1725,15 +1726,236 @@ describe("background skill update notification", () => {
1725
1726
  expect(signal.contextPayload?.title).toBe(
1726
1727
  "Skill updated: Weekly Report Export",
1727
1728
  );
1728
- expect(String(signal.contextPayload?.body)).toContain(
1729
- "Weekly Report Export",
1730
- );
1729
+ expect(signal.contextPayload?.body).toBe("Refined the steps.");
1731
1730
  // Deduped per skill per day so repeated refinements cannot flood the feed.
1732
1731
  expect(signal.dedupeKey).toBe(
1733
1732
  `skill-updated:weekly-export:${new Date().toISOString().slice(0, 10)}`,
1734
1733
  );
1735
1734
  });
1736
1735
 
1736
+ test("the notice body is the pass's change_summary, so the feed says what changed", async () => {
1737
+ await seedAssistantSkill("weekly-export", "Old body.");
1738
+
1739
+ const result = await executeScaffoldManagedSkill(
1740
+ {
1741
+ skill_id: "weekly-export",
1742
+ name: "Weekly Report Export",
1743
+ description: "export the weekly usage report",
1744
+ body_markdown: "1. Refined steps.",
1745
+ activation_hints: HINTS,
1746
+ overwrite: true,
1747
+ change_summary:
1748
+ "Added the retry after an expired session and the export endpoint that held steady.",
1749
+ },
1750
+ makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1751
+ lineage(),
1752
+ );
1753
+
1754
+ expect(result.isError).toBe(false);
1755
+ expect(emittedSignals).toHaveLength(1);
1756
+ const payload = emittedSignals[0]!.contextPayload;
1757
+ // The title still names the skill; the body is the change itself.
1758
+ expect(payload?.title).toBe("Skill updated: Weekly Report Export");
1759
+ expect(payload?.body).toBe(
1760
+ "Added the retry after an expired session and the export endpoint that held steady.",
1761
+ );
1762
+ expect(payload?.summary).toBe(payload?.body);
1763
+ });
1764
+
1765
+ test("a background overwrite without a change_summary is refused before the write", async () => {
1766
+ await seedAssistantSkill("weekly-export", "Old body.");
1767
+ const skillFile = join(TEST_DIR, "skills", "weekly-export", "SKILL.md");
1768
+ const before = readFileSync(skillFile, "utf-8");
1769
+
1770
+ for (const input of [
1771
+ {},
1772
+ // Whitespace-only reads as missing, not as an empty notice.
1773
+ { change_summary: " \n " },
1774
+ ]) {
1775
+ const result = await executeScaffoldManagedSkill(
1776
+ {
1777
+ skill_id: "weekly-export",
1778
+ name: "Weekly Report Export",
1779
+ description: "export the weekly usage report",
1780
+ body_markdown: "1. Refined steps.",
1781
+ activation_hints: HINTS,
1782
+ overwrite: true,
1783
+ ...input,
1784
+ },
1785
+ makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1786
+ lineage(),
1787
+ );
1788
+
1789
+ // Self-correcting: the error returns to the pass in the same turn, so
1790
+ // the retry carries the field and nothing was lost meanwhile.
1791
+ expect(result.isError).toBe(true);
1792
+ expect(result.content).toBe(
1793
+ 'Error: change_summary is required when updating an existing skill: pass one or two short sentences (under 200 characters) for the person who reads the "Skill updated" notice, naming what you changed and what in the trace prompted it (for example "Added the retry after an expired session and the export endpoint that held steady.").',
1794
+ );
1795
+ expect(readFileSync(skillFile, "utf-8")).toBe(before);
1796
+ expect(emittedSignals).toHaveLength(0);
1797
+ }
1798
+ });
1799
+
1800
+ test("a background call on an existing skill without overwrite hears the overwrite error, not the summary requirement", async () => {
1801
+ await seedAssistantSkill("weekly-export", "Old body.");
1802
+
1803
+ const result = await executeScaffoldManagedSkill(
1804
+ {
1805
+ skill_id: "weekly-export",
1806
+ name: "Weekly Report Export",
1807
+ description: "export the weekly usage report",
1808
+ body_markdown: "1. Refined steps.",
1809
+ activation_hints: HINTS,
1810
+ },
1811
+ makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1812
+ lineage(),
1813
+ );
1814
+
1815
+ // The flag is what actually blocks this call, so that is the error it
1816
+ // gets: a retry that only adds change_summary would still fail.
1817
+ expect(result.isError).toBe(true);
1818
+ expect(result.content).toContain("Set overwrite=true to replace it.");
1819
+ expect(result.content).not.toContain("change_summary");
1820
+ expect(emittedSignals).toHaveLength(0);
1821
+ });
1822
+
1823
+ test("the ownership backstop speaks before the change_summary requirement", async () => {
1824
+ await executeScaffoldManagedSkill(
1825
+ {
1826
+ skill_id: "user-owned",
1827
+ name: "User Owned",
1828
+ description: "a person wrote this",
1829
+ body_markdown: "V1.",
1830
+ activation_hints: HINTS,
1831
+ },
1832
+ makeContext(),
1833
+ );
1834
+
1835
+ const result = await executeScaffoldManagedSkill(
1836
+ {
1837
+ skill_id: "user-owned",
1838
+ name: "User Owned",
1839
+ description: "a person wrote this",
1840
+ body_markdown: "V2.",
1841
+ activation_hints: HINTS,
1842
+ overwrite: true,
1843
+ },
1844
+ makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1845
+ lineage(),
1846
+ );
1847
+
1848
+ expect(result.isError).toBe(true);
1849
+ expect(result.content).toContain("not verifiably assistant-authored");
1850
+ });
1851
+
1852
+ test("a user-directed overwrite needs no change_summary: it never notifies", async () => {
1853
+ await executeScaffoldManagedSkill(
1854
+ {
1855
+ skill_id: "user-skill",
1856
+ name: "User Skill",
1857
+ description: "asked for",
1858
+ body_markdown: "V1.",
1859
+ activation_hints: HINTS,
1860
+ },
1861
+ makeContext(),
1862
+ );
1863
+
1864
+ const result = await executeScaffoldManagedSkill(
1865
+ {
1866
+ skill_id: "user-skill",
1867
+ name: "User Skill",
1868
+ description: "asked for",
1869
+ body_markdown: "V2.",
1870
+ activation_hints: HINTS,
1871
+ overwrite: true,
1872
+ },
1873
+ makeContext(),
1874
+ );
1875
+
1876
+ expect(result.isError).toBe(false);
1877
+ });
1878
+
1879
+ test("change_summary is sanitized like any other notification body", async () => {
1880
+ await seedAssistantSkill("weekly-export", "Old body.");
1881
+
1882
+ await executeScaffoldManagedSkill(
1883
+ {
1884
+ skill_id: "weekly-export",
1885
+ name: "Weekly Report Export",
1886
+ description: "export the weekly usage report",
1887
+ body_markdown: "1. Refined steps.",
1888
+ activation_hints: HINTS,
1889
+ overwrite: true,
1890
+ change_summary: `Added\u0007 the retry.\r\n\n\n\nDropped the login step. ${"x".repeat(300)}`,
1891
+ },
1892
+ makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1893
+ lineage(),
1894
+ );
1895
+
1896
+ const body = String(emittedSignals[0]!.contextPayload?.body);
1897
+ // Control characters go, horizontal whitespace collapses, blank-line runs
1898
+ // collapse to one paragraph break, and the whole thing is clamped to the
1899
+ // shared notification preview budget.
1900
+ expect(
1901
+ body.startsWith("Added the retry.\n\nDropped the login step. xxx"),
1902
+ ).toBe(true);
1903
+ expect(body).not.toMatch(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/);
1904
+ expect(body.length).toBe(200);
1905
+ expect(body.endsWith("\u2026")).toBe(true);
1906
+ });
1907
+
1908
+ test("a non-string change_summary is a self-correcting error", async () => {
1909
+ await seedAssistantSkill("weekly-export", "Old body.");
1910
+
1911
+ const result = await executeScaffoldManagedSkill(
1912
+ {
1913
+ skill_id: "weekly-export",
1914
+ name: "Weekly Report Export",
1915
+ description: "export the weekly usage report",
1916
+ body_markdown: "1. Refined steps.",
1917
+ activation_hints: HINTS,
1918
+ overwrite: true,
1919
+ change_summary: ["not", "a", "string"],
1920
+ },
1921
+ makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1922
+ lineage(),
1923
+ );
1924
+
1925
+ expect(result.isError).toBe(true);
1926
+ expect(result.content).toBe("Error: change_summary must be a string");
1927
+ expect(emittedSignals).toHaveLength(0);
1928
+ });
1929
+
1930
+ test("the registered tool's schema declares change_summary as an optional string", () => {
1931
+ // A call through the registered tool is validated against TOOLS.json
1932
+ // first, and the validator rejects undeclared keys, so the field has to
1933
+ // be in the schema for the executor to ever see it. It stays optional
1934
+ // there: the requirement holds only for a background overwrite of an
1935
+ // existing skill, which the schema cannot express, so the executor
1936
+ // enforces it.
1937
+ const scaffoldTool = readScaffoldToolEntry();
1938
+ expect(scaffoldTool.input_schema.properties.change_summary).toMatchObject({
1939
+ type: "string",
1940
+ });
1941
+ expect(scaffoldTool.input_schema.required).not.toContain("change_summary");
1942
+
1943
+ const result = validateInputAgainstSchema(
1944
+ "scaffold_managed_skill",
1945
+ {
1946
+ skill_id: "s",
1947
+ name: "N",
1948
+ description: "D",
1949
+ body_markdown: "B",
1950
+ activation_hints: HINTS,
1951
+ overwrite: true,
1952
+ change_summary: "Added the retry step.",
1953
+ },
1954
+ scaffoldTool.input_schema,
1955
+ );
1956
+ expect(result.ok).toBe(true);
1957
+ });
1958
+
1737
1959
  test("it falls back to the run conversation when fork lineage does not resolve", async () => {
1738
1960
  await seedAssistantSkill("weekly-export", "Old body.");
1739
1961
 
@@ -1745,6 +1967,7 @@ describe("background skill update notification", () => {
1745
1967
  body_markdown: "1. Refined steps.",
1746
1968
  activation_hints: HINTS,
1747
1969
  overwrite: true,
1970
+ change_summary: "Refined the steps.",
1748
1971
  },
1749
1972
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1750
1973
  { getConversation: () => null },
@@ -8,6 +8,7 @@ import {
8
8
  getCAPath,
9
9
  issueLeafCert,
10
10
  } from "../outbound-proxy/index.js";
11
+ import { writeCombinedCABundle } from "../util/ca-bundle.js";
11
12
 
12
13
  let dataDir: string;
13
14
 
@@ -19,6 +20,40 @@ afterAll(async () => {
19
20
  await rm(dataDir, { recursive: true, force: true });
20
21
  });
21
22
 
23
+ test("combined trust verifies certificates from both CA sources", async () => {
24
+ const roots = [join(dataDir, "system"), join(dataDir, "extra")];
25
+ const leaves: string[] = [];
26
+ for (const root of roots) {
27
+ await ensureLocalCA(root);
28
+ const caDir = join(root, "proxy-ca");
29
+ await issueLeafCert(caDir, "example.com");
30
+ leaves.push(join(caDir, "issued", "example.com.pem"));
31
+ }
32
+ const bundle = join(dataDir, "combined.pem");
33
+ await writeCombinedCABundle(
34
+ getCAPath(roots[0]!),
35
+ getCAPath(roots[1]!),
36
+ bundle,
37
+ );
38
+ const verify = Bun.spawn(
39
+ ["openssl", "verify", "-CAfile", bundle, ...leaves],
40
+ {
41
+ stdout: "pipe",
42
+ stderr: "pipe",
43
+ windowsHide: true,
44
+ },
45
+ );
46
+ const [code, output, error] = await Promise.all([
47
+ verify.exited,
48
+ new Response(verify.stdout).text(),
49
+ new Response(verify.stderr).text(),
50
+ ]);
51
+ expect({ code, error }).toEqual({ code: 0, error: "" });
52
+ for (const leaf of leaves) {
53
+ expect(output).toContain(`${leaf}: OK`);
54
+ }
55
+ });
56
+
22
57
  describe("ensureLocalCA", () => {
23
58
  test("creates CA cert and key with correct permissions", async () => {
24
59
  await ensureLocalCA(dataDir);
@@ -1,6 +1,11 @@
1
1
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- import { ACP_OAUTH_TOKEN_FIELD, ACP_SERVICE } from "../acp/acp-credentials.js";
3
+ import {
4
+ ACP_OAUTH_EXPIRES_AT_FIELD,
5
+ ACP_OAUTH_REFRESH_TOKEN_FIELD,
6
+ ACP_OAUTH_TOKEN_FIELD,
7
+ ACP_SERVICE,
8
+ } from "../acp/acp-credentials.js";
4
9
  import { credentialKey } from "../security/credential-key.js";
5
10
 
6
11
  let secureKeyStore: Record<string, string | undefined> = {};
@@ -32,6 +37,11 @@ mock.module("../oauth/manual-token-connection.js", () => ({
32
37
  syncManualTokenConnection: async () => {},
33
38
  }));
34
39
 
40
+ mock.module("../runtime/routes/credential-in-use.js", () => ({
41
+ assertCredentialNotInUse: () => [],
42
+ invalidateConnectionsAfterCredentialDelete: () => {},
43
+ }));
44
+
35
45
  afterAll(() => {
36
46
  mock.restore();
37
47
  });
@@ -42,6 +52,9 @@ import { ROUTES } from "../runtime/routes/secret-routes.js";
42
52
  const addRoute = ROUTES.find(
43
53
  (r) => r.method === "POST" && r.endpoint === "secrets",
44
54
  )!;
55
+ const deleteRoute = ROUTES.find(
56
+ (r) => r.method === "DELETE" && r.endpoint === "secrets",
57
+ )!;
45
58
 
46
59
  function addAcpOauthToken(value: string) {
47
60
  return addRoute.handler({
@@ -74,4 +87,49 @@ describe("secret routes ACP OAuth-token format guard", () => {
74
87
  secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_TOKEN_FIELD)],
75
88
  ).toBe("sk-ant-oat01-valid-oauth-token");
76
89
  });
90
+
91
+ test("stores a pasted Claude token without touching leftover refresh material", async () => {
92
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_REFRESH_TOKEN_FIELD)] =
93
+ "stale-refresh";
94
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_EXPIRES_AT_FIELD)] =
95
+ "111";
96
+
97
+ await addAcpOauthToken("sk-ant-oat01-pasted-token");
98
+
99
+ expect(
100
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_TOKEN_FIELD)],
101
+ ).toBe("sk-ant-oat01-pasted-token");
102
+ expect(
103
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_REFRESH_TOKEN_FIELD)],
104
+ ).toBe("stale-refresh");
105
+ expect(
106
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_EXPIRES_AT_FIELD)],
107
+ ).toBe("111");
108
+ });
109
+
110
+ test("deletes only the requested Claude access-token field", async () => {
111
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_TOKEN_FIELD)] =
112
+ "sk-ant-oat01-connected";
113
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_REFRESH_TOKEN_FIELD)] =
114
+ "refresh-leftover";
115
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_EXPIRES_AT_FIELD)] =
116
+ "111";
117
+
118
+ await deleteRoute.handler({
119
+ body: {
120
+ type: "credential",
121
+ name: `${ACP_SERVICE}:${ACP_OAUTH_TOKEN_FIELD}`,
122
+ },
123
+ });
124
+
125
+ expect(
126
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_TOKEN_FIELD)],
127
+ ).toBeUndefined();
128
+ expect(
129
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_REFRESH_TOKEN_FIELD)],
130
+ ).toBe("refresh-leftover");
131
+ expect(
132
+ secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_EXPIRES_AT_FIELD)],
133
+ ).toBe("111");
134
+ });
77
135
  });