@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
@@ -5,6 +5,7 @@ import { minimatch } from "minimatch";
5
5
  import { RE2JS } from "re2js";
6
6
 
7
7
  import { RiskLevel } from "../../permissions/types.js";
8
+ import { safeStringSlice } from "../../util/unicode.js";
8
9
  import { declareDaemonActivityField } from "../schema-transforms.js";
9
10
  import {
10
11
  isDeniedBasename,
@@ -246,7 +247,7 @@ export const codeSearchTool = {
246
247
  // emitting a multi-megabyte output line. Normal lines pass through verbatim.
247
248
  const truncateForDisplay = (text: string): string =>
248
249
  text.length > MAX_DISPLAY_LINE_LENGTH
249
- ? `${text.slice(0, MAX_DISPLAY_LINE_LENGTH)} …[line truncated]`
250
+ ? `${safeStringSlice(text, 0, MAX_DISPLAY_LINE_LENGTH)} …[line truncated]`
250
251
  : text;
251
252
 
252
253
  // Append an output line, tracking its byte cost. Returns false once the
@@ -71,8 +71,9 @@ describe("host_file_edit cross-client guards", () => {
71
71
  );
72
72
  expect(result.isError).toBe(true);
73
73
  expect(result.content).toContain(
74
- "no client with host_file capability is connected",
74
+ "The Vellum desktop app is needed to access files on your computer",
75
75
  );
76
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
76
77
  });
77
78
 
78
79
  test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
@@ -4,7 +4,7 @@ import { supportsHostProxy } from "../../channels/types.js";
4
4
  import { HostFileProxy } from "../../daemon/host-file-proxy.js";
5
5
  import { RiskLevel } from "../../permissions/types.js";
6
6
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
7
- import { desktopClientName } from "../client-os.js";
7
+ import { formatDesktopAppRequired } from "../capability-offer.js";
8
8
  import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
9
9
  import { formatEditDiff } from "../shared/filesystem/format-diff.js";
10
10
  import { hostPolicy } from "../shared/filesystem/path-policy.js";
@@ -115,7 +115,7 @@ export const hostFileEditTool = {
115
115
  !HostFileProxy.instance.isAvailable()
116
116
  ) {
117
117
  return {
118
- content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
118
+ content: formatDesktopAppRequired("files"),
119
119
  isError: true,
120
120
  };
121
121
  }
@@ -67,8 +67,9 @@ describe("host_file_read cross-client guards", () => {
67
67
  );
68
68
  expect(result.isError).toBe(true);
69
69
  expect(result.content).toContain(
70
- "no client with host_file capability is connected",
70
+ "The Vellum desktop app is needed to access files on your computer",
71
71
  );
72
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
72
73
  });
73
74
 
74
75
  test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
@@ -6,7 +6,7 @@ import { supportsHostProxy } from "../../channels/types.js";
6
6
  import { HostFileProxy } from "../../daemon/host-file-proxy.js";
7
7
  import { RiskLevel } from "../../permissions/types.js";
8
8
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
9
- import { desktopClientName } from "../client-os.js";
9
+ import { formatDesktopAppRequired } from "../capability-offer.js";
10
10
  import {
11
11
  AUDIO_EXTENSIONS,
12
12
  readAudioFile,
@@ -121,7 +121,7 @@ export const hostFileReadTool = {
121
121
  !HostFileProxy.instance.isAvailable()
122
122
  ) {
123
123
  return {
124
- content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
124
+ content: formatDesktopAppRequired("files"),
125
125
  isError: true,
126
126
  };
127
127
  }
@@ -335,8 +335,9 @@ describe("host_file_transfer cross-client guards", () => {
335
335
 
336
336
  expect(result.isError).toBe(true);
337
337
  expect(result.content).toContain(
338
- "no client with host_file capability is connected",
338
+ "The Vellum desktop app is needed to access files on your computer",
339
339
  );
340
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
340
341
  expect(toSandboxCalls.length).toBe(0);
341
342
  });
342
343
 
@@ -6,7 +6,7 @@ import { supportsHostProxy } from "../../channels/types.js";
6
6
  import { HostTransferProxy } from "../../daemon/host-transfer-proxy.js";
7
7
  import { RiskLevel } from "../../permissions/types.js";
8
8
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
9
- import { desktopClientName } from "../client-os.js";
9
+ import { formatDesktopAppRequired } from "../capability-offer.js";
10
10
  import { declareDaemonActivityField } from "../schema-transforms.js";
11
11
  import { sandboxPolicy } from "../shared/filesystem/path-policy.js";
12
12
  import type {
@@ -126,7 +126,7 @@ export const hostFileTransferTool = {
126
126
  !HostTransferProxy.instance.isAvailable()
127
127
  ) {
128
128
  return {
129
- content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
129
+ content: formatDesktopAppRequired("files"),
130
130
  isError: true,
131
131
  };
132
132
  }
@@ -67,8 +67,9 @@ describe("host_file_write cross-client guards", () => {
67
67
  );
68
68
  expect(result.isError).toBe(true);
69
69
  expect(result.content).toContain(
70
- "no client with host_file capability is connected",
70
+ "The Vellum desktop app is needed to access files on your computer",
71
71
  );
72
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
72
73
  });
73
74
 
74
75
  test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
@@ -4,7 +4,7 @@ import { supportsHostProxy } from "../../channels/types.js";
4
4
  import { HostFileProxy } from "../../daemon/host-file-proxy.js";
5
5
  import { RiskLevel } from "../../permissions/types.js";
6
6
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
7
- import { desktopClientName } from "../client-os.js";
7
+ import { formatDesktopAppRequired } from "../capability-offer.js";
8
8
  import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
9
9
  import { formatWriteSummary } from "../shared/filesystem/format-diff.js";
10
10
  import { hostPolicy } from "../shared/filesystem/path-policy.js";
@@ -90,7 +90,7 @@ export const hostFileWriteTool = {
90
90
  !HostFileProxy.instance.isAvailable()
91
91
  ) {
92
92
  return {
93
- content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
93
+ content: formatDesktopAppRequired("files"),
94
94
  isError: true,
95
95
  };
96
96
  }
@@ -170,6 +170,22 @@ afterEach(() => {
170
170
  mockProxyAvailable = false;
171
171
  });
172
172
 
173
+ describe("host_bash unavailable desktop-app recovery", () => {
174
+ test("offers the desktop app download when a web turn has no host connection", async () => {
175
+ const result = await hostShellTool.execute(
176
+ { command: "pwd" },
177
+ makeContext({ transportInterface: "web" }),
178
+ );
179
+
180
+ expect(result.isError).toBe(true);
181
+ expect(result.content).toContain(
182
+ "The Vellum desktop app is needed to run commands on your computer",
183
+ );
184
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
185
+ expect(result.content).toContain("Open it, then retry.");
186
+ });
187
+ });
188
+
173
189
  // ---------------------------------------------------------------------------
174
190
  // Proxy path — background: true
175
191
  // ---------------------------------------------------------------------------
@@ -18,10 +18,7 @@ import { getConfig } from "../../config/loader.js";
18
18
  import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
19
19
  import { RiskLevel } from "../../permissions/types.js";
20
20
  import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
21
- import {
22
- assistantEventHub,
23
- broadcastMessage,
24
- } from "../../runtime/assistant-event-hub.js";
21
+ import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
25
22
  import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
26
23
  import { redactSecrets } from "../../security/secret-scanner.js";
27
24
  import {
@@ -39,7 +36,7 @@ import {
39
36
  registerBackgroundTool,
40
37
  removeBackgroundTool,
41
38
  } from "../background-tool-registry.js";
42
- import { desktopClientName } from "../client-os.js";
39
+ import { formatDesktopAppRequired } from "../capability-offer.js";
43
40
  import {
44
41
  attachBoundedStdio,
45
42
  MAX_OUTPUT_LENGTH,
@@ -142,7 +139,7 @@ export const hostShellInputSchema = z.looseObject({
142
139
  target_client_id: z
143
140
  .string()
144
141
  .describe(
145
- "ID of the specific client to execute this command on. Required when multiple clients support host_bash; omit when only one client is connected. Obtain IDs from `assistant clients list --capability host_bash`.",
142
+ "Optional ID of the specific client to execute this command on. Without it, the most recently active eligible client is used. Obtain IDs from `assistant clients list --capability host_bash`.",
146
143
  )
147
144
  .optional()
148
145
  .catch(undefined),
@@ -203,20 +200,7 @@ export const hostShellTool = {
203
200
  const config = getConfig();
204
201
  const { shellDefaultTimeoutSec, shellMaxTimeoutSec } = config.timeouts;
205
202
 
206
- // Guard: non-host-proxy interfaces need an explicit target when multiple
207
- // capable clients are connected to avoid ambiguous untargeted broadcasts.
208
203
  const transportInterface = context.transportInterface;
209
- if (
210
- targetClientId == null &&
211
- transportInterface != null &&
212
- !supportsHostProxy(transportInterface) &&
213
- assistantEventHub.listClientsByCapability("host_bash").length > 1
214
- ) {
215
- return {
216
- content: `Error: multiple clients support host_bash. Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_bash\` to see client IDs and labels.`,
217
- isError: true,
218
- };
219
- }
220
204
 
221
205
  // Guard: non-host-proxy interfaces with no capable clients connected.
222
206
  if (
@@ -226,7 +210,7 @@ export const hostShellTool = {
226
210
  !HostBashProxy.instance.isAvailable()
227
211
  ) {
228
212
  return {
229
- content: `Error: no client with host_bash capability is connected. Connect a ${desktopClientName(context)} client to use host_bash from a non-desktop interface.`,
213
+ content: formatDesktopAppRequired("shell"),
230
214
  isError: true,
231
215
  };
232
216
  }
@@ -15,6 +15,7 @@ import {
15
15
  getHttpRetryDelay,
16
16
  sleep,
17
17
  } from "../../util/retry.js";
18
+ import { safeStringSlice } from "../../util/unicode.js";
18
19
  import type {
19
20
  ToolContext,
20
21
  ToolDefinition,
@@ -509,7 +510,11 @@ const KEENABLE_SNIPPET_MAX_LENGTH = 500;
509
510
  * present rather than nullish. */
510
511
  function keenableSnippet(result: KeenableSearchResult): string {
511
512
  const text = result.snippet || result.description || "";
512
- return text.replace(/\s+/g, " ").trim().slice(0, KEENABLE_SNIPPET_MAX_LENGTH);
513
+ return safeStringSlice(
514
+ text.replace(/\s+/g, " ").trim(),
515
+ 0,
516
+ KEENABLE_SNIPPET_MAX_LENGTH,
517
+ );
513
518
  }
514
519
 
515
520
  function formatKeenableResults(
@@ -1,5 +1,5 @@
1
1
  import { getConfig } from "../config/loader.js";
2
- import { isV3TierActive } from "../config/memory-v3-gate.js";
2
+ import { isSkillImprovementActive } from "../config/memory-v3-gate.js";
3
3
  import type { ExecutionContext } from "../permissions/approval-policy.js";
4
4
  import type { ChannelPermissionCoordinates } from "../permissions/channel-permission-query.js";
5
5
  import type { PolicyContext } from "../permissions/types.js";
@@ -63,12 +63,11 @@ export function buildPolicyContext(
63
63
  requestOrigin: context?.requestOrigin,
64
64
  ...channelCoordinatesFromToolContext(context),
65
65
  requesterContactId: context?.requesterContactId,
66
- // Precompute the proc-to-skills gate — the v3 tier being active, i.e.
67
- // memory on AND v3 live — here so the permission checker, a leaf module
68
- // that must not read config, can deny the memory-retrospective
69
- // skill-authoring grant whenever the feature is inactive just by reading
70
- // this boolean.
71
- procToSkillsActive: isV3TierActive(getConfig()),
66
+ // Precompute the retrospective skill-improvement gate here so the
67
+ // permission checker, a leaf module that must not read config, can deny
68
+ // the memory-retrospective skill-authoring grant whenever the feature is
69
+ // inactive just by reading this boolean.
70
+ procToSkillsActive: isSkillImprovementActive(getConfig()),
72
71
  };
73
72
 
74
73
  const ownerKind = getToolOwner(tool.name)?.kind;
@@ -5,6 +5,7 @@ import type { SkillSource } from "../../config/skills.js";
5
5
  import { loadSkillCatalog } from "../../config/skills.js";
6
6
  import { refreshSkillCapabilityMemories } from "../../daemon/skill-memory-refresh.js";
7
7
  import { emitNotificationSignal } from "../../notifications/emit-signal.js";
8
+ import { sanitizeMultilineMessagePreview } from "../../notifications/notification-utils.js";
8
9
  import { getConversation } from "../../persistence/conversation-crud.js";
9
10
  import { upsertSkillCardInsertJob } from "../../persistence/jobs-store.js";
10
11
  import { MEMORY_RETROSPECTIVE_ORIGIN } from "../../plugins/defaults/memory/memory-retrospective-constants.js";
@@ -37,6 +38,14 @@ function sanitizeFrontmatterValue(value: string): string {
37
38
  const MISSING_ACTIVATION_HINTS =
38
39
  'activation_hints is required: pass 1-4 short trigger phrases stating the intent this skill serves (for example "user asks to deploy staging") so it can be found later by intent, not just by name.';
39
40
 
41
+ /**
42
+ * Self-correcting error for a background overwrite of an existing skill with
43
+ * no change summary. The schema cannot express the condition (a create and a
44
+ * user-directed edit need none), so the executor is the enforcement point.
45
+ */
46
+ const MISSING_CHANGE_SUMMARY =
47
+ '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.").';
48
+
40
49
  /**
41
50
  * Validate + normalize a string-array input (sanitize, drop blanks, dedupe).
42
51
  * Returns `{ error }` on the first invalid element, or `{ value }` holding
@@ -90,6 +99,12 @@ function normalizeOptionalStringArray(
90
99
  * scheduled jobs, heartbeat), where a user already looks to see what the
91
100
  * assistant did on its own.
92
101
  *
102
+ * `changeSummary` is the pass's own account of what it changed and is the
103
+ * notice's body: the feed item is the only place the change surfaces, and a
104
+ * reader triaging it there has no diff to look at, so a notice that only
105
+ * names the skill sends them into the skill to find out what happened. The
106
+ * executor refuses the overwrite without one, so this never runs without it.
107
+ *
93
108
  * `sourceContextId` is a conversation id so the feed item's "Go to Convo"
94
109
  * target resolves (see `home-feed-side-effect.ts`, which looks it up via
95
110
  * `getConversation`): the source conversation when lineage resolved, else the
@@ -102,6 +117,7 @@ function notifyBackgroundSkillUpdate(args: {
102
117
  skillId: string;
103
118
  name: string;
104
119
  conversationId: string;
120
+ changeSummary: string;
105
121
  }): void {
106
122
  const day = new Date().toISOString().slice(0, 10);
107
123
  void emitNotificationSignal({
@@ -121,13 +137,13 @@ function notifyBackgroundSkillUpdate(args: {
121
137
  // background, no interruption) leaves the feed writer with no summary
122
138
  // and it skips the item entirely, so the quiet case would surface
123
139
  // nothing at all.
124
- summary: `Updated the skill "${args.name}" from something learned in an earlier conversation.`,
140
+ summary: args.changeSummary,
125
141
  // Named, not just "Skill updated": the feed sits several rows deep and
126
142
  // a generic title is unscannable next to entries that name their
127
143
  // subject (`Background job failed: memory.v2.sweep`). The word "Skill"
128
144
  // stays because a bare skill name does not always read as one.
129
145
  title: `Skill updated: ${args.name}`,
130
- body: `Updated the skill "${args.name}" from something learned in an earlier conversation.`,
146
+ body: args.changeSummary,
131
147
  skillId: args.skillId,
132
148
  },
133
149
  attentionHints: {
@@ -313,6 +329,24 @@ export async function executeScaffoldManagedSkill(
313
329
  }
314
330
  }
315
331
 
332
+ // The update notice's body. Model-authored text bound for a notification
333
+ // surface, so it gets the same control-character strip and preview clamp as
334
+ // any other producer-supplied body; blank collapses to absent so the
335
+ // requirement below treats it as missing rather than posting an empty notice.
336
+ let changeSummary: string | undefined;
337
+ if (input.change_summary !== undefined) {
338
+ if (typeof input.change_summary !== "string") {
339
+ return {
340
+ content: "Error: change_summary must be a string",
341
+ isError: true,
342
+ };
343
+ }
344
+ const sanitized = sanitizeMultilineMessagePreview(input.change_summary);
345
+ if (sanitized) {
346
+ changeSummary = sanitized;
347
+ }
348
+ }
349
+
316
350
  const id = skillId.trim();
317
351
  const fromRetrospective =
318
352
  context.requestOrigin === MEMORY_RETROSPECTIVE_ORIGIN;
@@ -366,6 +400,24 @@ export async function executeScaffoldManagedSkill(
366
400
  }
367
401
  }
368
402
 
403
+ // A background overwrite announces itself through the notice built in
404
+ // notifyBackgroundSkillUpdate, whose body is this summary, so the write is
405
+ // refused without one. The error returns to the pass in the same turn and
406
+ // it retries with the field, the way a missing activation_hints does.
407
+ // Checked after the ownership backstop so a pass that may not touch the
408
+ // skill hears that first, and only for a call that asked to overwrite: a
409
+ // call without the flag is blocked by the managed store's own overwrite
410
+ // error, and hearing about the summary instead would cost it a retry that
411
+ // still cannot succeed. Before any write, so nothing is lost.
412
+ if (
413
+ fromRetrospective &&
414
+ managedSkillExistedBefore &&
415
+ input.overwrite === true &&
416
+ !changeSummary
417
+ ) {
418
+ return { content: `Error: ${MISSING_CHANGE_SUMMARY}`, isError: true };
419
+ }
420
+
369
421
  // Conversation lineage (retrospective origin only). The retrospective runs
370
422
  // in a background fork of the conversation it distilled the procedure from,
371
423
  // so the fork's parent is this skill's durable source conversation.
@@ -450,11 +502,17 @@ export async function executeScaffoldManagedSkill(
450
502
  // skill.
451
503
  const notifyConversationId =
452
504
  sourceConversationId ?? retrospectiveConversationId;
453
- if (fromRetrospective && managedSkillExistedBefore && notifyConversationId) {
505
+ if (
506
+ fromRetrospective &&
507
+ managedSkillExistedBefore &&
508
+ notifyConversationId &&
509
+ changeSummary
510
+ ) {
454
511
  notifyBackgroundSkillUpdate({
455
512
  skillId: id,
456
513
  name: normalizedName,
457
514
  conversationId: notifyConversationId,
515
+ changeSummary,
458
516
  });
459
517
  }
460
518
 
@@ -484,9 +484,9 @@ function spawnCancelledResult(label: string): ToolExecutionResult {
484
484
  * repeat-spawn guard folds, so a consult has to record it the same way every
485
485
  * other spawn does or the guard could never match one consult against another.
486
486
  *
487
- * The context pack is best effort: the parent conversation is looked up only
488
- * for its warm skill catalog, so an unresolvable one (e.g. evicted) costs the
489
- * skills section of the pack and nothing else.
487
+ * The context pack is best effort: each section independently falls back to
488
+ * null on failure, so a missing workspace or empty catalog costs only that
489
+ * section, never the consult.
490
490
  *
491
491
  * The budget is the advisor's alone. It is the only spawn the agent may issue
492
492
  * unprompted and the only one that runs on the premium profile, so it is the
@@ -502,21 +502,18 @@ async function buildAdvisorFields(
502
502
  maxRuntimeMs: number;
503
503
  maxToolCalls: number;
504
504
  }> {
505
- const parentConversation = findConversation(context.conversationId);
506
505
  // Situational awareness for the advisor: the parent's live tool set, the
507
506
  // full skill catalog, and its workspace. Assembled off the per-turn
508
507
  // ToolContext snapshot (trust, channel) so the personal-memory sections are
509
508
  // gated exactly like the runtime injectors. A null pack just means the
510
- // consult runs on the brief alone.
509
+ // consult runs on the brief alone. Skills come from a fresh
510
+ // `loadSkillCatalog()` inside the context pack, not a conversation snapshot.
511
511
  const situationalContext = await buildAdvisorContext({
512
512
  conversationId: context.conversationId,
513
513
  workingDir: context.workingDir,
514
514
  allowedToolNames: context.allowedToolNames,
515
515
  trustClass: context.trustClass,
516
516
  enabledPluginSet: context.enabledPluginSet,
517
- // The parent's warm per-turn catalog keeps the synchronous on-disk catalog
518
- // scan out of the spawn path.
519
- skillCatalog: parentConversation?.skillProjectionCache?.catalog,
520
517
  });
521
518
  return {
522
519
  requestText: advisorRequestText(objective, situationalContext),
@@ -0,0 +1,57 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+
3
+ import { runSanitizedBash } from "../sanitized-bash.js";
4
+
5
+ describe("runSanitizedBash", () => {
6
+ const priorToken = process.env.CES_SERVICE_TOKEN;
7
+ const priorSocket = process.env.CES_LOCAL_SOCKET;
8
+
9
+ afterEach(() => {
10
+ if (priorToken == null) {
11
+ delete process.env.CES_SERVICE_TOKEN;
12
+ } else {
13
+ process.env.CES_SERVICE_TOKEN = priorToken;
14
+ }
15
+ if (priorSocket == null) {
16
+ delete process.env.CES_LOCAL_SOCKET;
17
+ } else {
18
+ process.env.CES_LOCAL_SOCKET = priorSocket;
19
+ }
20
+ });
21
+
22
+ test("runs a command and returns stdout", async () => {
23
+ const result = await runSanitizedBash("echo hello", 5_000);
24
+ expect(result.error).toBeUndefined();
25
+ expect(result.timedOut).toBe(false);
26
+ expect(result.exitCode).toBe(0);
27
+ expect(result.stdout.trim()).toBe("hello");
28
+ });
29
+
30
+ test("forwards CES_SERVICE_TOKEN to the child", async () => {
31
+ process.env.CES_SERVICE_TOKEN = "vault-bearer";
32
+ const result = await runSanitizedBash("printenv CES_SERVICE_TOKEN", 5_000);
33
+ expect(result.error).toBeUndefined();
34
+ expect(result.exitCode).toBe(0);
35
+ expect(result.stdout.trim()).toBe("vault-bearer");
36
+ });
37
+
38
+ test("forwards CES_LOCAL_SOCKET to the child", async () => {
39
+ process.env.CES_LOCAL_SOCKET = "/tmp/ces.sock";
40
+ const result = await runSanitizedBash("printenv CES_LOCAL_SOCKET", 5_000);
41
+ expect(result.error).toBeUndefined();
42
+ expect(result.exitCode).toBe(0);
43
+ expect(result.stdout.trim()).toBe("/tmp/ces.sock");
44
+ });
45
+
46
+ test("times out a hung command", async () => {
47
+ const result = await runSanitizedBash("sleep 5", 200);
48
+ expect(result.error).toBeUndefined();
49
+ expect(result.timedOut).toBe(true);
50
+ });
51
+
52
+ test("rejects an empty command", async () => {
53
+ const result = await runSanitizedBash("");
54
+ expect(result.error).toBe("command is required");
55
+ expect(result.exitCode).toBeNull();
56
+ });
57
+ });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Spawn a shell command with the same sanitized environment the bash tool
3
+ * forwards to tool-call subprocesses.
4
+ *
5
+ * Used by `assistant bash` so a CLI invocation sees the same allowlisted
6
+ * env, workspace cwd, and platform shell as a bash tool call, without
7
+ * round-tripping through the assistant HTTP or IPC API.
8
+ */
9
+
10
+ import { spawn } from "node:child_process";
11
+
12
+ import {
13
+ buildShellInvocation,
14
+ terminateProcessTree,
15
+ } from "../../util/host-process.js";
16
+ import { getWorkspaceDir } from "../../util/platform.js";
17
+ import { buildSanitizedEnv } from "./safe-env.js";
18
+
19
+ export const DEFAULT_SANITIZED_BASH_TIMEOUT_MS = 30_000;
20
+
21
+ export interface SanitizedBashResult {
22
+ stdout: string;
23
+ stderr: string;
24
+ exitCode: number | null;
25
+ timedOut: boolean;
26
+ error?: string;
27
+ }
28
+
29
+ export function runSanitizedBash(
30
+ command: string,
31
+ timeoutMs: number = DEFAULT_SANITIZED_BASH_TIMEOUT_MS,
32
+ ): Promise<SanitizedBashResult> {
33
+ if (!command) {
34
+ return Promise.resolve({
35
+ stdout: "",
36
+ stderr: "",
37
+ exitCode: null,
38
+ timedOut: false,
39
+ error: "command is required",
40
+ });
41
+ }
42
+
43
+ const effectiveTimeout =
44
+ typeof timeoutMs === "number" && timeoutMs > 0
45
+ ? timeoutMs
46
+ : DEFAULT_SANITIZED_BASH_TIMEOUT_MS;
47
+
48
+ return new Promise<SanitizedBashResult>((resolve) => {
49
+ const stdoutChunks: Buffer[] = [];
50
+ const stderrChunks: Buffer[] = [];
51
+ let timedOut = false;
52
+ let settled = false;
53
+
54
+ const finish = (result: SanitizedBashResult) => {
55
+ if (settled) {
56
+ return;
57
+ }
58
+ settled = true;
59
+ resolve(result);
60
+ };
61
+
62
+ const wrapped = buildShellInvocation(command);
63
+ const child = spawn(wrapped.command, wrapped.args, {
64
+ cwd: getWorkspaceDir(),
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ detached: true,
67
+ env: buildSanitizedEnv(),
68
+ windowsHide: true,
69
+ });
70
+
71
+ const timer = setTimeout(() => {
72
+ timedOut = true;
73
+ terminateProcessTree(child);
74
+ }, effectiveTimeout);
75
+
76
+ child.stdout.on("data", (data: Buffer) => {
77
+ stdoutChunks.push(data);
78
+ });
79
+
80
+ child.stderr.on("data", (data: Buffer) => {
81
+ stderrChunks.push(data);
82
+ });
83
+
84
+ child.on("close", (code) => {
85
+ clearTimeout(timer);
86
+ finish({
87
+ stdout: Buffer.concat(stdoutChunks).toString(),
88
+ stderr: Buffer.concat(stderrChunks).toString(),
89
+ exitCode: code,
90
+ timedOut,
91
+ });
92
+ });
93
+
94
+ child.on("error", (err) => {
95
+ clearTimeout(timer);
96
+ finish({
97
+ stdout: "",
98
+ stderr: "",
99
+ exitCode: null,
100
+ timedOut: false,
101
+ error: err.message,
102
+ });
103
+ });
104
+ });
105
+ }
@@ -14,14 +14,6 @@ import type {
14
14
  /**
15
15
  * How a watch retrospective hands its report to the daemon.
16
16
  *
17
- * **The card cannot come from `ui_show`.** A retrospective runs as a
18
- * `clientless` wake, which pins the turn non-interactive, and
19
- * `conversation-tool-setup` gates the whole `ui_surface` tool family on a
20
- * client being present (`return channelCapabilities?.supportsDynamicUi ??
21
- * !hasNoClient`). So `ui_show` is not merely denied in this turn, it is absent
22
- * from the tool set, and a retrospective told to call it can only report that
23
- * it cannot. This tool is an ordinary one and passes that gate untouched.
24
- *
25
17
  * **It records; it does not render.** The executor validates the payload and
26
18
  * returns, and nothing here writes to the conversation. What makes the card is
27
19
  * `watch-retro.ts` reading this call back out of the turn's own history once
@@ -16,6 +16,7 @@ export const HOST_PROXY_CAPABILITIES = [
16
16
  "host_file",
17
17
  "host_cu",
18
18
  "host_cu_window_capture",
19
+ "host_cu_sequence",
19
20
  "host_cu_annotate",
20
21
  "host_browser",
21
22
  "host_app_control",
@@ -24,11 +25,12 @@ export const HOST_PROXY_CAPABILITIES = [
24
25
 
25
26
  export type HostProxyCapability = (typeof HOST_PROXY_CAPABILITIES)[number];
26
27
 
27
- /** Every host capability except the three macOS-only ones. */
28
+ /** Every host capability except the macOS-only ones. */
28
29
  const DESKTOP_SHARED_CAPABILITIES = HOST_PROXY_CAPABILITIES.filter(
29
30
  (capability) =>
30
31
  capability !== "host_app_control" &&
31
32
  capability !== "host_cu_window_capture" &&
33
+ capability !== "host_cu_sequence" &&
32
34
  capability !== "host_cu_annotate",
33
35
  );
34
36
 
@@ -47,8 +49,8 @@ export type HostProxyInterfaceId = "macos" | "windows" | "linux";
47
49
  * (web, ios, android, and every messaging transport) provide none.
48
50
  *
49
51
  * Windows and Linux run the same host proxy as macOS minus app control. The
50
- * two window-scoped CU capabilities ride the host_cu transport and are
51
- * negotiated per connection; only the macOS native helper answers them, so a
52
+ * negotiated CU capabilities ride the host_cu transport and are claimed per
53
+ * connection; only the macOS native helper answers them, so a
52
54
  * request elsewhere would reach that helper as an unknown action.
53
55
  *
54
56
  * macOS additionally provisions its `host_browser` proxy via the assistant