@vellumai/assistant 0.12.1 → 0.12.2-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (548) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/openapi.yaml +327 -52
  6. package/package.json +3 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  254. package/src/desktop/desktop-chrome-policy.ts +47 -0
  255. package/src/desktop/desktop-chrome-session.ts +37 -0
  256. package/src/desktop/desktop-dependencies.test.ts +102 -0
  257. package/src/desktop/desktop-dependencies.ts +308 -0
  258. package/src/desktop/desktop-feature.test.ts +18 -0
  259. package/src/desktop/desktop-feature.ts +19 -0
  260. package/src/desktop/desktop-panel-config.test.ts +135 -0
  261. package/src/desktop/desktop-panel-config.ts +149 -0
  262. package/src/desktop/desktop-session-manager.test.ts +730 -0
  263. package/src/desktop/desktop-session-manager.ts +850 -0
  264. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  265. package/src/desktop/desktop-stream-bridge.ts +214 -0
  266. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  267. package/src/desktop/desktop-wallpaper.ts +113 -0
  268. package/src/desktop/desktop-window-drag.ts +177 -0
  269. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  270. package/src/desktop/desktop-window-manager-config.ts +238 -0
  271. package/src/desktop/desktop-window-theme.ts +170 -0
  272. package/src/heartbeat/heartbeat-service.ts +21 -3
  273. package/src/home/__tests__/feed-writer.test.ts +22 -0
  274. package/src/home/feed-writer.ts +11 -2
  275. package/src/hooks/types.ts +33 -0
  276. package/src/i18n/AGENTS.md +22 -0
  277. package/src/i18n/__tests__/i18n.test.ts +108 -0
  278. package/src/i18n/index.ts +16 -0
  279. package/src/i18n/locales.ts +93 -0
  280. package/src/i18n/messages.ts +61 -0
  281. package/src/i18n/title.ts +25 -0
  282. package/src/inbound/platform-callback-registration.ts +4 -5
  283. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  284. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  285. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  286. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  287. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  288. package/src/live-voice/live-voice-photo.ts +25 -6
  289. package/src/live-voice/live-voice-session.ts +213 -693
  290. package/src/live-voice/protocol.ts +0 -2
  291. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  292. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  293. package/src/mcp/effective-config.ts +3 -3
  294. package/src/mcp/mcp-header-store.ts +29 -45
  295. package/src/mcp/spec-schema.ts +111 -0
  296. package/src/mcp/startup.ts +8 -4
  297. package/src/mcp/workspace-mcp-config.ts +188 -0
  298. package/src/messaging/provider.ts +10 -3
  299. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  300. package/src/messaging/providers/callback-routing.ts +18 -0
  301. package/src/messaging/providers/channel-transport.ts +56 -0
  302. package/src/messaging/providers/discord/transport.ts +17 -0
  303. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  304. package/src/messaging/providers/slack/adapter.ts +7 -44
  305. package/src/messaging/providers/slack/transport.ts +16 -0
  306. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  307. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  308. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  309. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  310. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  311. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  312. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  313. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  314. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  315. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  316. package/src/notifications/adapters/macos.ts +3 -0
  317. package/src/notifications/assistant-reply-producer.ts +8 -3
  318. package/src/notifications/conversation-pairing.ts +36 -16
  319. package/src/notifications/delivered-post-record.ts +11 -1
  320. package/src/notifications/guardian-feed-projection.ts +125 -5
  321. package/src/notifications/home-feed-side-effect.ts +55 -7
  322. package/src/notifications/schedule-result-producer.ts +66 -22
  323. package/src/oauth/AGENTS.md +2 -0
  324. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  325. package/src/oauth/byo-connection.test.ts +105 -0
  326. package/src/oauth/byo-connection.ts +36 -6
  327. package/src/oauth/connection-resolver.test.ts +73 -0
  328. package/src/oauth/connection-resolver.ts +95 -2
  329. package/src/oauth/scope-utils.ts +24 -0
  330. package/src/oauth/seed-providers.ts +204 -25
  331. package/src/outbound-proxy/certs.ts +3 -5
  332. package/src/permissions/checker.test.ts +11 -6
  333. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  334. package/src/persistence/conversation-crud.ts +71 -63
  335. package/src/persistence/conversation-lineage.ts +12 -4
  336. package/src/persistence/conversation-queries.ts +2 -2
  337. package/src/persistence/conversation-title-placeholders.ts +23 -0
  338. package/src/persistence/conversation-title-service.ts +9 -26
  339. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  340. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  341. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  342. package/src/persistence/message-cursor.ts +78 -0
  343. package/src/persistence/message-reads.ts +30 -0
  344. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  345. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  346. package/src/persistence/schema/memory-core.ts +5 -0
  347. package/src/platform/client.test.ts +4 -2
  348. package/src/platform/client.ts +2 -10
  349. package/src/plugin-api/constants.ts +2 -0
  350. package/src/plugin-api/index.ts +1 -0
  351. package/src/plugin-api/types.ts +2 -0
  352. package/src/plugins/defaults/index.ts +2 -0
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  358. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  359. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  360. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  361. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  362. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  363. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  364. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  365. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  366. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  367. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  368. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  369. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  370. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  371. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  372. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  374. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  375. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  376. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  377. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  378. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  379. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  380. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  381. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  382. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  383. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  384. package/src/plugins/defaults/memory/startup.ts +28 -2
  385. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  386. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  387. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  388. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  389. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  390. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  391. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  392. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  393. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  394. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  395. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  396. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  397. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  398. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  399. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  400. package/src/plugins/defaults/memory/worker.ts +6 -0
  401. package/src/plugins/mcp-servers.ts +15 -75
  402. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  403. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  404. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  405. package/src/providers/anthropic/client.ts +0 -47
  406. package/src/providers/gemini/client.ts +9 -23
  407. package/src/providers/gemini-thought-signature.test.ts +63 -0
  408. package/src/providers/gemini-thought-signature.ts +44 -0
  409. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  410. package/src/providers/inference/auth.ts +2 -3
  411. package/src/providers/inference/connection-availability.ts +27 -8
  412. package/src/providers/inference/endpoint-probe.ts +10 -1
  413. package/src/providers/inference/resolve-auth.ts +22 -4
  414. package/src/providers/inference/service-account-token.ts +248 -0
  415. package/src/providers/model-catalog.ts +34 -0
  416. package/src/providers/model-intents.ts +10 -5
  417. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  418. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  419. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  420. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  421. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  422. package/src/providers/openai/chat-completions-provider.ts +212 -79
  423. package/src/providers/openai/codex-models.ts +0 -4
  424. package/src/providers/openai/google-thought-signature.ts +320 -0
  425. package/src/providers/opencode/client.test.ts +73 -4
  426. package/src/providers/opencode/client.ts +60 -3
  427. package/src/providers/openrouter/client.ts +20 -0
  428. package/src/providers/outbound-request-sanitize.ts +96 -0
  429. package/src/providers/retry.ts +30 -19
  430. package/src/providers/unparseable-tool-args.ts +5 -1
  431. package/src/runtime/AGENTS.md +3 -1
  432. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  433. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  434. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  435. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  436. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  437. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  438. package/src/runtime/agent-wake.ts +2 -1
  439. package/src/runtime/assistant-event-hub.ts +21 -3
  440. package/src/runtime/auth/same-actor.ts +38 -3
  441. package/src/runtime/channel-send.ts +316 -0
  442. package/src/runtime/host-observe.ts +11 -1
  443. package/src/runtime/http-server.ts +160 -151
  444. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  445. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  446. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  447. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  448. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  449. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  450. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  451. package/src/runtime/routes/acp-routes.test.ts +15 -0
  452. package/src/runtime/routes/acp-routes.ts +72 -9
  453. package/src/runtime/routes/app-management-routes.ts +73 -0
  454. package/src/runtime/routes/app-routes.ts +1 -1
  455. package/src/runtime/routes/channel-send-routes.ts +85 -0
  456. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  457. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  458. package/src/runtime/routes/conversation-routes.ts +16 -3
  459. package/src/runtime/routes/credential-routes.ts +19 -10
  460. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  461. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  462. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  463. package/src/runtime/routes/events-routes.ts +4 -0
  464. package/src/runtime/routes/host-cu-routes.ts +6 -0
  465. package/src/runtime/routes/index.ts +4 -2
  466. package/src/runtime/routes/log-export-routes.ts +2 -0
  467. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  468. package/src/runtime/routes/migration-routes.ts +6 -20
  469. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  470. package/src/runtime/routes/platform-routes.ts +18 -15
  471. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  472. package/src/runtime/routes/workspace-routes.ts +46 -63
  473. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  474. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  475. package/src/runtime/routes/workspace-utils.ts +17 -0
  476. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  477. package/src/runtime/services/conversation-serializer.ts +16 -3
  478. package/src/runtime/sync/resource-sync-events.ts +2 -1
  479. package/src/schedule/run-script.ts +2 -1
  480. package/src/schedule/schedule-store.ts +9 -2
  481. package/src/schedule/worker.ts +1 -1
  482. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  483. package/src/security/ces-http-record-backend.ts +120 -0
  484. package/src/security/ces-rpc-record-backend.ts +7 -0
  485. package/src/security/untrusted-content.ts +3 -1
  486. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  487. package/src/skills/workspace-skill-md-signature.ts +50 -0
  488. package/src/subagent/consult-context.ts +12 -25
  489. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  490. package/src/tools/acp/list-agents.test.ts +3 -1
  491. package/src/tools/acp/spawn.test.ts +146 -11
  492. package/src/tools/acp/spawn.ts +21 -3
  493. package/src/tools/acp/status.test.ts +94 -0
  494. package/src/tools/acp/status.ts +40 -3
  495. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/browser/browser-execution.ts +1 -1
  497. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  498. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  499. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  500. package/src/tools/browser/network-recorder.ts +2 -1
  501. package/src/tools/capability-offer.test.ts +22 -2
  502. package/src/tools/capability-offer.ts +16 -1
  503. package/src/tools/computer-use/definitions.ts +121 -2
  504. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  505. package/src/tools/credentials/metadata-store.ts +100 -0
  506. package/src/tools/filesystem/search.ts +2 -1
  507. package/src/tools/host-filesystem/edit.test.ts +2 -1
  508. package/src/tools/host-filesystem/edit.ts +2 -2
  509. package/src/tools/host-filesystem/read.test.ts +2 -1
  510. package/src/tools/host-filesystem/read.ts +2 -2
  511. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  512. package/src/tools/host-filesystem/transfer.ts +2 -2
  513. package/src/tools/host-filesystem/write.test.ts +2 -1
  514. package/src/tools/host-filesystem/write.ts +2 -2
  515. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  516. package/src/tools/host-terminal/host-shell.ts +4 -20
  517. package/src/tools/network/web-search.ts +6 -1
  518. package/src/tools/policy-context.ts +6 -7
  519. package/src/tools/skills/scaffold-managed.ts +61 -3
  520. package/src/tools/subagent/spawn.ts +5 -8
  521. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  522. package/src/tools/terminal/sanitized-bash.ts +105 -0
  523. package/src/tools/watch/watch-retro-report.ts +0 -8
  524. package/src/types/host-capabilities.ts +5 -3
  525. package/src/util/__tests__/language-subtag.test.ts +23 -1
  526. package/src/util/ca-bundle.ts +13 -0
  527. package/src/util/language-subtag.ts +16 -0
  528. package/src/util/platform.ts +8 -0
  529. package/src/util/unicode.ts +15 -1
  530. package/src/watch/watch-timeline.ts +2 -2
  531. package/src/workflows/run-manager.ts +2 -1
  532. package/src/workspace/git-porcelain.ts +66 -0
  533. package/src/workspace/git-service.ts +6 -55
  534. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  535. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  536. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  537. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  538. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  539. package/src/workspace/migrations/registry.ts +6 -0
  540. package/src/__tests__/app-source-watcher.test.ts +0 -218
  541. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  542. package/src/__tests__/speaker-identification.test.ts +0 -56
  543. package/src/calls/speaker-identification.ts +0 -233
  544. package/src/daemon/app-source-watcher.ts +0 -218
  545. package/src/messaging/providers/whatsapp/client.ts +0 -29
  546. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  547. package/src/util/fs-watcher-error.ts +0 -36
  548. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * CES HTTP backend for non-secret credential records (identity + policy).
3
+ *
4
+ * Used when the CES RPC socket is down but the CES HTTP sidecar is
5
+ * reachable (containerized deployments with CES_CREDENTIAL_URL).
6
+ */
7
+
8
+ import {
9
+ type CesHttpCredentialClient,
10
+ createCesHttpCredentialClient,
11
+ } from "@vellumai/ces-client/http-credentials";
12
+ import type { CredentialRecord } from "@vellumai/service-contracts/credential-rpc";
13
+
14
+ import { getIsContainerized } from "../config/env-registry.js";
15
+ import { getLogger } from "../util/logger.js";
16
+ import type { CredentialRecordBackend } from "./ces-rpc-record-backend.js";
17
+
18
+ const log = getLogger("ces-http-record-backend");
19
+
20
+ type RecordHttpClient = Pick<
21
+ CesHttpCredentialClient,
22
+ | "getRecord"
23
+ | "setRecord"
24
+ | "deleteRecord"
25
+ | "listRecords"
26
+ | "bulkSetRecords"
27
+ >;
28
+
29
+ function resolveEnvHttpClient(): RecordHttpClient | undefined {
30
+ const baseUrl = process.env.CES_CREDENTIAL_URL?.trim();
31
+ const serviceToken = process.env.CES_SERVICE_TOKEN?.trim();
32
+ if (!baseUrl || !serviceToken) {
33
+ return undefined;
34
+ }
35
+ return createCesHttpCredentialClient({ baseUrl, serviceToken }, log);
36
+ }
37
+
38
+ export class CesHttpRecordBackend implements CredentialRecordBackend {
39
+ readonly name = "ces-http";
40
+
41
+ constructor(private readonly client?: RecordHttpClient) {}
42
+
43
+ private resolveClient(): RecordHttpClient | undefined {
44
+ return this.client ?? resolveEnvHttpClient();
45
+ }
46
+
47
+ isAvailable(): boolean {
48
+ return this.resolveClient() !== undefined;
49
+ }
50
+
51
+ async get(account: string): Promise<CredentialRecord | undefined> {
52
+ const client = this.resolveClient();
53
+ if (!client) {
54
+ return undefined;
55
+ }
56
+ const result = await client.getRecord(account);
57
+ return result.record;
58
+ }
59
+
60
+ async set(account: string, record: CredentialRecord): Promise<boolean> {
61
+ const client = this.resolveClient();
62
+ if (!client) {
63
+ return false;
64
+ }
65
+ return client.setRecord(account, record);
66
+ }
67
+
68
+ async delete(
69
+ account: string,
70
+ ): Promise<"deleted" | "not-found" | "error"> {
71
+ const client = this.resolveClient();
72
+ if (!client) {
73
+ return "error";
74
+ }
75
+ return client.deleteRecord(account);
76
+ }
77
+
78
+ async list(): Promise<Array<{
79
+ account: string;
80
+ record: CredentialRecord;
81
+ }> | null> {
82
+ const client = this.resolveClient();
83
+ if (!client) {
84
+ return null;
85
+ }
86
+ const result = await client.listRecords();
87
+ if (result.unreachable) {
88
+ return null;
89
+ }
90
+ return result.records ?? [];
91
+ }
92
+
93
+ async bulkSet(
94
+ records: Array<{ account: string; record: CredentialRecord }>,
95
+ ): Promise<Array<{ account: string; ok: boolean }>> {
96
+ const client = this.resolveClient();
97
+ if (!client) {
98
+ return records.map((entry) => ({ account: entry.account, ok: false }));
99
+ }
100
+ return client.bulkSetRecords(records);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * CES HTTP record backend when the assistant is containerized and the
106
+ * CES HTTP sidecar URL is configured. Returns null otherwise.
107
+ */
108
+ export function createCesHttpRecordBackendIfConfigured(): CredentialRecordBackend | null {
109
+ if (!getIsContainerized()) {
110
+ return null;
111
+ }
112
+ if (!process.env.CES_CREDENTIAL_URL?.trim()) {
113
+ return null;
114
+ }
115
+ const backend = new CesHttpRecordBackend();
116
+ if (!backend.isAvailable()) {
117
+ return null;
118
+ }
119
+ return backend;
120
+ }
@@ -11,6 +11,11 @@ import { getLogger } from "../util/logger.js";
11
11
  const log = getLogger("ces-rpc-record-backend");
12
12
 
13
13
  export interface CredentialRecordBackend {
14
+ /**
15
+ * Transport id. `"ces-rpc"` is the attached CES socket backend; live
16
+ * catalog listing fails over to CES HTTP when that transport is down.
17
+ */
18
+ readonly name?: string;
14
19
  isAvailable(): boolean;
15
20
  get(account: string): Promise<CredentialRecord | undefined>;
16
21
  set(account: string, record: CredentialRecord): Promise<boolean>;
@@ -29,6 +34,8 @@ export interface CredentialRecordBackend {
29
34
  }
30
35
 
31
36
  export class CesRpcRecordBackend implements CredentialRecordBackend {
37
+ readonly name = "ces-rpc";
38
+
32
39
  constructor(private readonly client: CesClient) {}
33
40
 
34
41
  isAvailable(): boolean {
@@ -11,6 +11,8 @@
11
11
  * 3. Enforces per-source character budgets to prevent context flooding.
12
12
  */
13
13
 
14
+ import { safeStringSlice } from "../util/unicode.js";
15
+
14
16
  // ---------------------------------------------------------------------------
15
17
  // Types
16
18
  // ---------------------------------------------------------------------------
@@ -156,7 +158,7 @@ export function escapeContentBoundaries(content: string): string {
156
158
 
157
159
  /** Sanitize a value for use as an XML attribute (no quotes, angle brackets, newlines). */
158
160
  function sanitizeAttr(value: string): string {
159
- return value.replace(/[<>"&\r\n]/g, "").slice(0, 200);
161
+ return safeStringSlice(value.replace(/[<>"&\r\n]/g, ""), 0, 200);
160
162
  }
161
163
 
162
164
  function parseExternalContentAttributes(
@@ -0,0 +1,60 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, test } from "bun:test";
5
+
6
+ import { readWorkspaceSkillMdSignature } from "../workspace-skill-md-signature.js";
7
+
8
+ const ROOT = mkdtempSync(join(tmpdir(), "skill-md-signature-"));
9
+
10
+ afterEach(() => {
11
+ rmSync(ROOT, { recursive: true, force: true });
12
+ mkdirSync(ROOT, { recursive: true });
13
+ });
14
+
15
+ function writeSkill(id: string, body = "body"): string {
16
+ const dir = join(ROOT, id);
17
+ mkdirSync(dir, { recursive: true });
18
+ const skillMd = join(dir, "SKILL.md");
19
+ writeFileSync(skillMd, `---\nname: ${id}\ndescription: ${id}\n---\n${body}\n`);
20
+ return skillMd;
21
+ }
22
+
23
+ describe("readWorkspaceSkillMdSignature", () => {
24
+ test("empty or missing skills dir is the empty signature", () => {
25
+ expect(readWorkspaceSkillMdSignature(join(ROOT, "missing"))).toBe("");
26
+ mkdirSync(ROOT, { recursive: true });
27
+ expect(readWorkspaceSkillMdSignature(ROOT)).toBe("");
28
+ });
29
+
30
+ test("includes SKILL.md mtimes and ignores TOOLS.json", () => {
31
+ const skillMd = writeSkill("alpha");
32
+ const first = readWorkspaceSkillMdSignature(ROOT);
33
+ expect(first).toMatch(/^alpha:\d+$/);
34
+
35
+ writeFileSync(join(ROOT, "alpha", "TOOLS.json"), '{"version":1,"tools":[]}');
36
+ expect(readWorkspaceSkillMdSignature(ROOT)).toBe(first);
37
+
38
+ const later = new Date(Date.now() + 5_000);
39
+ utimesSync(skillMd, later, later);
40
+ expect(readWorkspaceSkillMdSignature(ROOT)).not.toBe(first);
41
+ });
42
+
43
+ test("add and remove skill directories change the signature", () => {
44
+ writeSkill("alpha");
45
+ const one = readWorkspaceSkillMdSignature(ROOT);
46
+ writeSkill("bravo");
47
+ const two = readWorkspaceSkillMdSignature(ROOT);
48
+ expect(two).not.toBe(one);
49
+ expect(two.split("\n")).toHaveLength(2);
50
+
51
+ rmSync(join(ROOT, "bravo"), { recursive: true, force: true });
52
+ expect(readWorkspaceSkillMdSignature(ROOT)).toBe(one);
53
+ });
54
+
55
+ test("directories without SKILL.md are omitted", () => {
56
+ mkdirSync(join(ROOT, "not-a-skill"), { recursive: true });
57
+ writeFileSync(join(ROOT, "README.md"), "nope");
58
+ expect(readWorkspaceSkillMdSignature(ROOT)).toBe("");
59
+ });
60
+ });
@@ -0,0 +1,50 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { getWorkspaceSkillsDir } from "../util/platform.js";
5
+
6
+ /**
7
+ * Cheap change detector for workspace skill capability cards: the sorted set
8
+ * of immediate skill directories that contain a top-level `SKILL.md`, each
9
+ * paired with that file's mtime. Capability cards are frontmatter fields from
10
+ * `SKILL.md`. Body files, `TOOLS.json`, scripts, and `references/` are not part
11
+ * of this signature.
12
+ *
13
+ * Listing the skills directory (not only statting known paths) is what makes
14
+ * install and delete visible: a new or removed skill directory changes the
15
+ * signature even when no previously known `SKILL.md` mtime moved.
16
+ */
17
+ export function readWorkspaceSkillMdSignature(
18
+ skillsDir: string = getWorkspaceSkillsDir(),
19
+ ): string {
20
+ if (!existsSync(skillsDir)) {
21
+ return "";
22
+ }
23
+
24
+ let entries;
25
+ try {
26
+ entries = readdirSync(skillsDir, { withFileTypes: true });
27
+ } catch {
28
+ return "";
29
+ }
30
+
31
+ const parts: string[] = [];
32
+ for (const entry of entries) {
33
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) {
34
+ continue;
35
+ }
36
+ const skillMdPath = join(skillsDir, entry.name, "SKILL.md");
37
+ try {
38
+ const st = statSync(skillMdPath);
39
+ if (!st.isFile()) {
40
+ continue;
41
+ }
42
+ parts.push(`${entry.name}:${Math.trunc(st.mtimeMs)}`);
43
+ } catch {
44
+ // No readable SKILL.md: not a catalog skill.
45
+ }
46
+ }
47
+
48
+ parts.sort();
49
+ return parts.join("\n");
50
+ }
@@ -33,7 +33,6 @@
33
33
  import { readdir } from "node:fs/promises";
34
34
  import { join } from "node:path";
35
35
 
36
- import type { SkillSummary } from "../config/skills.js";
37
36
  import type { TrustContext } from "../daemon/trust-context-types.js";
38
37
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
39
38
  import { truncate as truncateText } from "../util/truncate.js";
@@ -56,15 +55,6 @@ export interface AdvisorContextSources {
56
55
  * from the catalog section, mirroring the `skill_load` gate.
57
56
  */
58
57
  enabledPluginSet?: ReadonlySet<string> | null;
59
- /**
60
- * Pre-resolved skill catalog, typically the parent conversation's warm
61
- * `skillProjectionCache.catalog`. Passing it keeps the synchronous on-disk
62
- * catalog scan out of the consult path (and matches the catalog view the
63
- * parent turn's tool projection used). When absent, the section falls back
64
- * to a fresh `loadSkillCatalog()` scan, the same call every agent turn's
65
- * projection already makes.
66
- */
67
- skillCatalog?: readonly SkillSummary[];
68
58
  }
69
59
 
70
60
  /** Cap a block so the assembled context never balloons the consult prompt. */
@@ -114,7 +104,6 @@ async function buildToolsSection(
114
104
  */
115
105
  async function buildSkillsSection(
116
106
  enabledPluginSet: ReadonlySet<string> | null | undefined,
117
- preResolvedCatalog: readonly SkillSummary[] | undefined,
118
107
  ): Promise<string | null> {
119
108
  try {
120
109
  const [
@@ -130,19 +119,17 @@ async function buildSkillsSection(
130
119
  ]);
131
120
  const config = getConfig();
132
121
  const pluginScope = enabledPluginSet ?? null;
133
- const catalog = (preResolvedCatalog ?? loadSkillCatalog()).filter(
134
- (skill) => {
135
- if (
136
- pluginScope !== null &&
137
- skill.owner?.kind === "plugin" &&
138
- !pluginScope.has(skill.owner.id)
139
- ) {
140
- return false;
141
- }
142
- const flagKey = skillFlagKey(skill);
143
- return !flagKey || isAssistantFeatureFlagEnabled(flagKey, config);
144
- },
145
- );
122
+ const catalog = loadSkillCatalog().filter((skill) => {
123
+ if (
124
+ pluginScope !== null &&
125
+ skill.owner?.kind === "plugin" &&
126
+ !pluginScope.has(skill.owner.id)
127
+ ) {
128
+ return false;
129
+ }
130
+ const flagKey = skillFlagKey(skill);
131
+ return !flagKey || isAssistantFeatureFlagEnabled(flagKey, config);
132
+ });
146
133
  if (catalog.length === 0) {
147
134
  return null;
148
135
  }
@@ -384,7 +371,7 @@ export async function buildAdvisorContext(
384
371
  const sections = await Promise.all(
385
372
  [
386
373
  buildToolsSection(sources.allowedToolNames),
387
- buildSkillsSection(sources.enabledPluginSet, sources.skillCatalog),
374
+ buildSkillsSection(sources.enabledPluginSet),
388
375
  buildWorkspaceSection(sources),
389
376
  ].map((section) => withSectionTimeout(section, sectionTimeoutMs)),
390
377
  );
@@ -158,6 +158,7 @@ const SKILL_TOOLS: GuardedTool[] = [
158
158
  proxied("computer_use_drag"),
159
159
  proxied("computer_use_open_app"),
160
160
  proxied("computer_use_run_applescript"),
161
+ proxied("computer_use_sequence"),
161
162
  proxied("computer_use_respond"),
162
163
 
163
164
  { name: "contact_merge", input: { keep_id: "c-1", merge_id: "c-2" } },
@@ -90,7 +90,9 @@ describe("executeAcpListAgents", () => {
90
90
  const codex = parsed.agents.find((a: { id: string }) => a.id === "codex");
91
91
  expect(codex.available).toBe(false);
92
92
  expect(codex.unavailableReason).toBe("'codex-acp' is not on PATH");
93
- expect(codex.setupHint).toBe("bun add -g @agentclientprotocol/codex-acp");
93
+ expect(codex.setupHint).toBe(
94
+ "bun add -g @agentclientprotocol/codex-acp@1.10.0",
95
+ );
94
96
 
95
97
  const claude = parsed.agents.find((a: { id: string }) => a.id === "claude");
96
98
  expect(claude.available).toBe(true);
@@ -124,11 +124,22 @@ const spawnMock = mock(
124
124
  _cwd: string,
125
125
  _parentConversationId: string,
126
126
  _sendToVellum: (msg: unknown) => void,
127
- _parentToolUseId?: string,
128
- ) => ({
129
- acpSessionId: "acp-session-test",
130
- protocolSessionId: "proto-session-test",
131
- }),
127
+ _options?: { parentToolUseId?: string; model?: string },
128
+ ): Promise<{
129
+ acpSessionId: string;
130
+ protocolSessionId: string;
131
+ requestedModel?: string;
132
+ effectiveModel?: string;
133
+ modelWarning?: string;
134
+ }> => {
135
+ const requestedModel = _options?.model?.trim() || undefined;
136
+ return {
137
+ acpSessionId: "acp-session-test",
138
+ protocolSessionId: "proto-session-test",
139
+ requestedModel,
140
+ effectiveModel: "opus",
141
+ };
142
+ },
132
143
  );
133
144
 
134
145
  // Spread the real module's exports so transitive importers that pull other
@@ -201,6 +212,8 @@ describe("executeAcpSpawn - happy path", () => {
201
212
  const payload = JSON.parse(result.content);
202
213
  expect(payload.acpSessionId).toBe("acp-session-test");
203
214
  expect(payload.status).toBe("running");
215
+ expect(payload.requestedModel).toBeNull();
216
+ expect(payload.effectiveModel).toBe("opus");
204
217
  // The real adapter binary is spawned (no `bun x` wrapper).
205
218
  const agentConfigArg = spawnMock.mock.calls[0][1] as { command: string };
206
219
  expect(agentConfigArg.command).toBe("claude-agent-acp");
@@ -214,8 +227,10 @@ describe("executeAcpSpawn - happy path", () => {
214
227
 
215
228
  expect(result.isError).toBe(false);
216
229
  expect(spawnMock).toHaveBeenCalledTimes(1);
217
- // parentToolUseId is the 7th positional arg to spawn().
218
- expect(spawnMock.mock.calls[0][6]).toBe("toolu_abc123");
230
+ // The options bag is the 7th positional arg to spawn().
231
+ expect(spawnMock.mock.calls[0][6]).toEqual({
232
+ parentToolUseId: "toolu_abc123",
233
+ });
219
234
  });
220
235
 
221
236
  test("default-profile fallback when user config is empty", async () => {
@@ -272,7 +287,7 @@ describe("executeAcpSpawn — input validation", () => {
272
287
  expect(result.isError).toBe(true);
273
288
  expect(result.content).toContain("claude-agent-acp is not on PATH");
274
289
  expect(result.content).toContain(
275
- "bun add -g @agentclientprotocol/claude-agent-acp",
290
+ "bun add -g @agentclientprotocol/claude-agent-acp@0.75.1",
276
291
  );
277
292
  expect(execFileMock).not.toHaveBeenCalled();
278
293
  expect(spawnMock).not.toHaveBeenCalled();
@@ -309,7 +324,7 @@ describe("executeAcpSpawn: sandboxed bun auto-install on missing binary", () =>
309
324
  expect(spawnMock).toHaveBeenCalledTimes(1);
310
325
  const payload = JSON.parse(result.content);
311
326
  expect(payload.message).toContain(
312
- "Installed @agentclientprotocol/claude-agent-acp automatically.",
327
+ "Installed @agentclientprotocol/claude-agent-acp@0.75.1 automatically.",
313
328
  );
314
329
  // The real binary was spawned with cwd = the project dir and token
315
330
  // injected (trusted-binary config, no resolution at spawn).
@@ -330,7 +345,7 @@ describe("executeAcpSpawn: sandboxed bun auto-install on missing binary", () =>
330
345
  expect(args).toEqual([
331
346
  "add",
332
347
  "--global",
333
- "@agentclientprotocol/claude-agent-acp",
348
+ "@agentclientprotocol/claude-agent-acp@0.75.1",
334
349
  ]);
335
350
  });
336
351
 
@@ -419,7 +434,7 @@ describe("executeAcpSpawn: sandboxed bun auto-install on missing binary", () =>
419
434
  expect(result.isError).toBe(true);
420
435
  expect(result.content).toContain("claude-agent-acp is not on PATH");
421
436
  expect(result.content).toContain(
422
- "bun add -g @agentclientprotocol/claude-agent-acp",
437
+ "bun add -g @agentclientprotocol/claude-agent-acp@0.75.1",
423
438
  );
424
439
  expect(result.content).toContain("auto-install failed");
425
440
  expect(result.content).toContain("EACCES");
@@ -458,6 +473,126 @@ describe("executeAcpSpawn — per-agent resume hint", () => {
458
473
  });
459
474
  });
460
475
 
476
+ describe("executeAcpSpawn - model selection", () => {
477
+ test("threads the requested model into manager.spawn's options", async () => {
478
+ const result = await executeAcpSpawn(
479
+ { agent: "claude", task: "do something", model: "opus" },
480
+ { ...makeContext(), toolUseId: "toolu_model" },
481
+ );
482
+
483
+ expect(result.isError).toBe(false);
484
+ expect(spawnMock.mock.calls[0][6]).toEqual({
485
+ parentToolUseId: "toolu_model",
486
+ model: "opus",
487
+ });
488
+ const payload = JSON.parse(result.content);
489
+ expect(payload.requestedModel).toBe("opus");
490
+ expect(payload.effectiveModel).toBe("opus");
491
+ expect(payload.message).toContain(
492
+ 'The top-level ACP session reports "opus" as its effective model.',
493
+ );
494
+ });
495
+
496
+ test("reports the manager-normalized requested model", async () => {
497
+ const result = await executeAcpSpawn(
498
+ { agent: "claude", task: "do something", model: " opus " },
499
+ makeContext(),
500
+ );
501
+
502
+ expect(spawnMock.mock.calls[0][6]).toEqual({
503
+ parentToolUseId: undefined,
504
+ model: " opus ",
505
+ });
506
+ expect(JSON.parse(result.content).requestedModel).toBe("opus");
507
+ });
508
+
509
+ test("a null model is treated as omitted", async () => {
510
+ const result = await executeAcpSpawn(
511
+ { agent: "claude", task: "do something", model: null },
512
+ makeContext(),
513
+ );
514
+
515
+ expect(result.isError).toBe(false);
516
+ const options = spawnMock.mock.calls[0][6] as { model?: string };
517
+ expect(options.model).toBeUndefined();
518
+ });
519
+
520
+ test("a modelWarning from the spawn is relayed in the result message", async () => {
521
+ spawnMock.mockImplementationOnce(async () => ({
522
+ acpSessionId: "acp-session-test",
523
+ protocolSessionId: "proto-session-test",
524
+ requestedModel: "nope",
525
+ effectiveModel: "opus",
526
+ modelWarning: "Unknown model: nope",
527
+ }));
528
+
529
+ const result = await executeAcpSpawn(
530
+ { agent: "claude", task: "do something", model: "nope" },
531
+ makeContext(),
532
+ );
533
+
534
+ expect(result.isError).toBe(false);
535
+ const payload = JSON.parse(result.content);
536
+ expect(payload.message).toContain(
537
+ "The requested model was not applied: Unknown model: nope",
538
+ );
539
+ expect(payload.requestedModel).toBe("nope");
540
+ expect(payload.effectiveModel).toBe("opus");
541
+ });
542
+
543
+ test("the note relays a no-selector warning without calling it a refusal", async () => {
544
+ spawnMock.mockImplementationOnce(async () => ({
545
+ acpSessionId: "acp-session-test",
546
+ protocolSessionId: "proto-session-test",
547
+ requestedModel: "opus",
548
+ modelWarning:
549
+ 'Agent "claude" does not support model selection, so the session is running on the agent\'s own model.',
550
+ }));
551
+
552
+ const result = await executeAcpSpawn(
553
+ { agent: "claude", task: "do something", model: "opus" },
554
+ makeContext(),
555
+ );
556
+
557
+ const payload = JSON.parse(result.content);
558
+ expect(payload.message).toContain("does not support model selection");
559
+ expect(payload.message).not.toContain("refused");
560
+ });
561
+
562
+ test("no model note when the spawn reports no warning", async () => {
563
+ const result = await executeAcpSpawn(
564
+ { agent: "claude", task: "do something", model: "opus" },
565
+ makeContext(),
566
+ );
567
+
568
+ expect(result.isError).toBe(false);
569
+ const payload = JSON.parse(result.content);
570
+ expect(payload.message).not.toContain(
571
+ "The requested model was not applied",
572
+ );
573
+ });
574
+
575
+ test("reports when the adapter does not identify the effective model", async () => {
576
+ spawnMock.mockImplementationOnce(async () => ({
577
+ acpSessionId: "acp-session-test",
578
+ protocolSessionId: "proto-session-test",
579
+ requestedModel: "sonnet",
580
+ }));
581
+
582
+ const result = await executeAcpSpawn(
583
+ { agent: "claude", task: "do something", model: "sonnet" },
584
+ makeContext(),
585
+ );
586
+
587
+ const payload = JSON.parse(result.content);
588
+ expect(payload.requestedModel).toBe("sonnet");
589
+ expect(payload.effectiveModel).toBeNull();
590
+ expect(payload.message).toContain(
591
+ "The top-level ACP session did not report an effective model.",
592
+ );
593
+ });
594
+ });
595
+
461
596
  // ---------------------------------------------------------------------------
462
597
  // executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN env injection + preflight
463
598
  //
@@ -37,6 +37,7 @@ export const acpSpawnInputSchema = z.looseObject({
37
37
  agent: nullAsOmitted(z.string()),
38
38
  task: nullAsOmitted(z.string()),
39
39
  cwd: nullAsOmitted(z.string()),
40
+ model: nullAsOmitted(z.string()),
40
41
  });
41
42
 
42
43
  /**
@@ -66,6 +67,7 @@ export async function executeAcpSpawn(
66
67
  }
67
68
  const agent = parsedInput.data.agent || "claude";
68
69
  const task = parsedInput.data.task;
70
+ const model = parsedInput.data.model;
69
71
 
70
72
  if (!task) {
71
73
  return { content: '"task" is required.', isError: true };
@@ -124,14 +126,20 @@ export async function executeAcpSpawn(
124
126
  // Recheck: the auto-install and the agent-env preparation above are both
125
127
  // awaits, and this is the point a long-lived subprocess starts.
126
128
  throwIfCancelled(context);
127
- const { acpSessionId, protocolSessionId } = await manager.spawn(
129
+ const {
130
+ acpSessionId,
131
+ protocolSessionId,
132
+ requestedModel,
133
+ effectiveModel,
134
+ modelWarning,
135
+ } = await manager.spawn(
128
136
  agent,
129
137
  agentConfig,
130
138
  task,
131
139
  cwd,
132
140
  context.conversationId,
133
141
  sendToClient,
134
- context.toolUseId,
142
+ { parentToolUseId: context.toolUseId, model },
135
143
  // The manager rechecks after its own protocol handshake and session
136
144
  // creation, so a turn stopped in that window tears the child process
137
145
  // down instead of handing it the task.
@@ -150,16 +158,26 @@ export async function executeAcpSpawn(
150
158
  const installNote = autoInstalledPackage
151
159
  ? ` Installed ${autoInstalledPackage} automatically.`
152
160
  : "";
161
+ // Relayed verbatim: the warning already distinguishes an adapter with no
162
+ // selector from one that refused the value.
163
+ const modelNote = modelWarning
164
+ ? ` The requested model was not applied: ${modelWarning}`
165
+ : "";
166
+ const effectiveModelNote = effectiveModel
167
+ ? ` The top-level ACP session reports "${effectiveModel}" as its effective model.`
168
+ : " The top-level ACP session did not report an effective model.";
153
169
  const payload = JSON.stringify({
154
170
  acpSessionId,
155
171
  protocolSessionId,
156
172
  agent,
157
173
  cwd,
174
+ requestedModel: requestedModel ?? null,
175
+ effectiveModel: effectiveModel ?? null,
158
176
  status: "running",
159
177
  message:
160
178
  `ACP agent "${agent}" spawned (session: ${protocolSessionId}). ` +
161
179
  `Results stream back via SSE. You will be notified when it completes.` +
162
- `${installNote}${resumeHint}`,
180
+ `${installNote}${modelNote}${effectiveModelNote}${resumeHint}`,
163
181
  });
164
182
 
165
183
  return { content: payload, isError: false };