@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,94 @@
1
+ /**
2
+ * Tests for the `acp_status` tool's projection of session state.
3
+ *
4
+ * The state the manager tracks feeds three surfaces with different budgets:
5
+ * the HTTP route, the SSE picker, and this tool, whose output is spent on
6
+ * LLM context. Only the fields an agent can act on belong here.
7
+ */
8
+
9
+ import { describe, expect, mock, test } from "bun:test";
10
+
11
+ import type { AcpSessionState } from "../../acp/types.js";
12
+ import type { ToolContext } from "../types.js";
13
+
14
+ const RUNNING_STATE: AcpSessionState = {
15
+ id: "acp-1",
16
+ agentId: "claude",
17
+ acpSessionId: "proto-1",
18
+ parentConversationId: "conv-1",
19
+ status: "running",
20
+ startedAt: 1234,
21
+ task: "refactor the parser",
22
+ model: "claude-opus-4-5",
23
+ availableModels: [
24
+ { value: "sonnet", label: "Sonnet" },
25
+ { value: "opus", label: "Opus", description: "Most capable" },
26
+ ],
27
+ };
28
+
29
+ let statusResult: AcpSessionState | AcpSessionState[] = RUNNING_STATE;
30
+
31
+ const realAcpModule = await import("../../acp/index.js");
32
+ mock.module("../../acp/index.js", () => ({
33
+ ...realAcpModule,
34
+ getAcpSessionManager: () => ({ getStatus: () => statusResult }),
35
+ }));
36
+
37
+ const { executeAcpStatus } = await import("./status.js");
38
+
39
+ const context = {
40
+ conversationId: "conv-1",
41
+ workingDir: "/tmp",
42
+ } as unknown as ToolContext;
43
+
44
+ describe("executeAcpStatus", () => {
45
+ test("a single session reports its model but not the picker", async () => {
46
+ statusResult = RUNNING_STATE;
47
+
48
+ const result = await executeAcpStatus({ acp_session_id: "acp-1" }, context);
49
+
50
+ expect(result.isError).toBe(false);
51
+ const payload = JSON.parse(result.content);
52
+ expect(payload).toEqual({
53
+ id: "acp-1",
54
+ agentId: "claude",
55
+ acpSessionId: "proto-1",
56
+ parentConversationId: "conv-1",
57
+ status: "running",
58
+ startedAt: 1234,
59
+ task: "refactor the parser",
60
+ model: "claude-opus-4-5",
61
+ });
62
+ expect(result.content).not.toContain("Most capable");
63
+ });
64
+
65
+ test("the listing drops the picker from every entry", async () => {
66
+ statusResult = [
67
+ RUNNING_STATE,
68
+ { ...RUNNING_STATE, id: "acp-2", model: "sonnet" },
69
+ ];
70
+
71
+ const result = await executeAcpStatus({}, context);
72
+
73
+ const payload = JSON.parse(result.content) as Array<
74
+ Record<string, unknown>
75
+ >;
76
+ expect(payload).toHaveLength(2);
77
+ expect(payload.map((entry) => entry.model)).toEqual([
78
+ "claude-opus-4-5",
79
+ "sonnet",
80
+ ]);
81
+ for (const entry of payload) {
82
+ expect(entry).not.toHaveProperty("availableModels");
83
+ }
84
+ });
85
+
86
+ test("an empty listing says so rather than rendering an empty array", async () => {
87
+ statusResult = [];
88
+
89
+ const result = await executeAcpStatus({}, context);
90
+
91
+ expect(result.content).toBe("No ACP sessions found.");
92
+ expect(result.isError).toBe(false);
93
+ });
94
+ });
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { getAcpSessionManager } from "../../acp/index.js";
4
+ import type { AcpSessionState } from "../../acp/types.js";
4
5
  import {
5
6
  invalidToolInputResult,
6
7
  nullAsOmitted,
@@ -16,6 +17,40 @@ export const acpStatusInputSchema = z.looseObject({
16
17
  acp_session_id: nullAsOmitted(z.string()),
17
18
  });
18
19
 
20
+ /**
21
+ * The session fields worth spending LLM context on. An allowlist rather than
22
+ * an omission: `availableModels` is a picker for the human surfaces (HTTP and
23
+ * SSE) that says nothing the agent can act on, and a field added to
24
+ * `AcpSessionState` for those surfaces should not reach this one by default.
25
+ * `model` stays: which model a session is running on is answerable.
26
+ */
27
+ function projectSession(state: AcpSessionState) {
28
+ return {
29
+ id: state.id,
30
+ agentId: state.agentId,
31
+ acpSessionId: state.acpSessionId,
32
+ parentConversationId: state.parentConversationId,
33
+ status: state.status,
34
+ startedAt: state.startedAt,
35
+ completedAt: state.completedAt,
36
+ error: state.error,
37
+ stopReason: state.stopReason,
38
+ task: state.task,
39
+ parentToolUseId: state.parentToolUseId,
40
+ authErrorCode: state.authErrorCode,
41
+ authErrorCredential: state.authErrorCredential,
42
+ latestUsage: state.latestUsage,
43
+ model: state.model,
44
+ };
45
+ }
46
+
47
+ /** Projects either shape `getStatus` answers with. */
48
+ function projectStatus(status: AcpSessionState | AcpSessionState[]): unknown {
49
+ return Array.isArray(status)
50
+ ? status.map(projectSession)
51
+ : projectSession(status);
52
+ }
53
+
19
54
  export async function executeAcpStatus(
20
55
  input: Record<string, unknown>,
21
56
  _context: ToolContext,
@@ -29,9 +64,8 @@ export async function executeAcpStatus(
29
64
 
30
65
  try {
31
66
  if (acpSessionId) {
32
- const state = manager.getStatus(acpSessionId);
33
67
  return {
34
- content: JSON.stringify(state),
68
+ content: JSON.stringify(projectStatus(manager.getStatus(acpSessionId))),
35
69
  isError: false,
36
70
  };
37
71
  }
@@ -42,7 +76,10 @@ export async function executeAcpStatus(
42
76
  return { content: "No ACP sessions found.", isError: false };
43
77
  }
44
78
 
45
- return { content: JSON.stringify(allStates), isError: false };
79
+ return {
80
+ content: JSON.stringify(projectStatus(allStates)),
81
+ isError: false,
82
+ };
46
83
  } catch (err) {
47
84
  const msg = err instanceof Error ? err.message : String(err);
48
85
  return { content: msg, isError: true };
@@ -5,6 +5,7 @@
5
5
  * the proxy resolver, which forwards the call to the connected client.
6
6
  */
7
7
 
8
+ import { formatDesktopAppRequired } from "../capability-offer.js";
8
9
  import { throwIfCancelled } from "../shared/abort.js";
9
10
  import type { ToolContext, ToolExecutionResult } from "../types.js";
10
11
 
@@ -32,7 +33,7 @@ export function forwardAppControlProxyTool(
32
33
  }
33
34
  if (!context.proxyToolResolver) {
34
35
  return Promise.resolve({
35
- content: `Cannot execute ${toolName}: no proxy resolver available. This tool requires a connected client.`,
36
+ content: formatDesktopAppRequired("apps"),
36
37
  isError: true,
37
38
  });
38
39
  }
@@ -262,7 +262,7 @@ export const EXTRACT_LINKS_EXPRESSION = `
262
262
  (() => {
263
263
  const anchors = Array.from(document.querySelectorAll('a[href]'));
264
264
  return anchors.slice(0, 200).map(a => ({
265
- text: (a.textContent || '').trim().slice(0, 80),
265
+ text: Array.from((a.textContent || '').trim()).slice(0, 80).join(''),
266
266
  href: a.href,
267
267
  }));
268
268
  })()
@@ -14,6 +14,8 @@
14
14
  * a later PR is a direct drop-in replacement.
15
15
  */
16
16
 
17
+ import { safeStringSlice } from "../../../util/unicode.js";
18
+
17
19
  // ── Types ─────────────────────────────────────────────────────────────
18
20
 
19
21
  /**
@@ -288,7 +290,11 @@ function extractAttrs(node: RawAxNode): Record<string, string> {
288
290
  if (!PROPERTY_ALLOWLIST.has(name)) {
289
291
  continue;
290
292
  }
291
- attrs[name] = stringifyAxValue(prop.value?.value).slice(0, MAX_VALUE_CHARS);
293
+ attrs[name] = safeStringSlice(
294
+ stringifyAxValue(prop.value?.value),
295
+ 0,
296
+ MAX_VALUE_CHARS,
297
+ );
292
298
  }
293
299
  return attrs;
294
300
  }
@@ -346,12 +352,12 @@ export function transformAxTree(
346
352
  }
347
353
 
348
354
  const rawName = typeof node.name?.value === "string" ? node.name.value : "";
349
- const name = rawName.trim().slice(0, MAX_NAME_CHARS);
355
+ const name = safeStringSlice(rawName.trim(), 0, MAX_NAME_CHARS);
350
356
 
351
357
  const rawValue = node.value?.value;
352
358
  const value =
353
359
  typeof rawValue === "string" && rawValue.length > 0
354
- ? rawValue.slice(0, MAX_VALUE_CHARS)
360
+ ? safeStringSlice(rawValue, 0, MAX_VALUE_CHARS)
355
361
  : undefined;
356
362
 
357
363
  const attrs = extractAttrs(node);
@@ -25,6 +25,7 @@
25
25
  * value.
26
26
  */
27
27
 
28
+ import { safeStringSlice } from "../../../../util/unicode.js";
28
29
  import type { CdpWsTransport } from "./ws-transport.js";
29
30
 
30
31
  /**
@@ -336,7 +337,7 @@ function parseJsonText(text: string, endpoint: string): unknown {
336
337
  } catch (err) {
337
338
  throw new DevToolsDiscoveryError(
338
339
  "invalid_response",
339
- `Expected JSON from ${endpoint} but got: ${text.slice(0, 200)}`,
340
+ `Expected JSON from ${endpoint} but got: ${safeStringSlice(text, 0, 200)}`,
340
341
  err,
341
342
  );
342
343
  }
@@ -1,5 +1,6 @@
1
1
  import type { HostBrowserProxy } from "../../../daemon/host-browser-proxy.js";
2
2
  import { getLogger } from "../../../util/logger.js";
3
+ import { safeStringSlice } from "../../../util/unicode.js";
3
4
  import type { CdpErrorCode } from "./errors.js";
4
5
  import { CdpError } from "./errors.js";
5
6
  import type { CdpClientKind, ScopedCdpClient, TabInfo } from "./types.js";
@@ -121,7 +122,7 @@ export class ExtensionCdpClient implements ScopedCdpClient {
121
122
  // and mask the extension path as the true failing hop.
122
123
  throw new CdpError(
123
124
  "cdp_error",
124
- result.content.slice(0, 200) || `CDP error for ${method}`,
125
+ safeStringSlice(result.content, 0, 200) || `CDP error for ${method}`,
125
126
  {
126
127
  cdpMethod: method,
127
128
  cdpParams: params,
@@ -164,7 +165,7 @@ export class ExtensionCdpClient implements ScopedCdpClient {
164
165
  } catch (err) {
165
166
  throw new CdpError(
166
167
  "transport_error",
167
- `Non-JSON content from host_browser_result: ${result.content.slice(0, 200)}`,
168
+ `Non-JSON content from host_browser_result: ${safeStringSlice(result.content, 0, 200)}`,
168
169
  {
169
170
  cdpMethod: method,
170
171
  cdpParams: params,
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { getLogger } from "../../util/logger.js";
9
9
  import { truncate } from "../../util/truncate.js";
10
+ import { safeStringSlice } from "../../util/unicode.js";
10
11
  import type {
11
12
  ExtractedCredential,
12
13
  NetworkRecordedEntry,
@@ -457,7 +458,7 @@ export class NetworkRecorder {
457
458
 
458
459
  entry.response.body =
459
460
  body.length > MAX_BODY_SIZE
460
- ? body.slice(0, MAX_BODY_SIZE) + "...[truncated]"
461
+ ? safeStringSlice(body, 0, MAX_BODY_SIZE) + "...[truncated]"
461
462
  : body;
462
463
  }
463
464
  } catch {
@@ -4,11 +4,26 @@ import {
4
4
  appendLoggedInBrowserOffer,
5
5
  CHROME_WEB_STORE_INSTALL_URL,
6
6
  DESKTOP_APP_DOWNLOAD_URL,
7
+ formatDesktopAppRequired,
7
8
  formatLoggedInBrowserOffer,
8
9
  isPhoneSurface,
9
10
  shouldOfferLoggedInBrowser,
10
11
  } from "./capability-offer.js";
11
12
 
13
+ describe("formatDesktopAppRequired", () => {
14
+ test("offers a direct desktop-app download and retry step", () => {
15
+ const message = formatDesktopAppRequired("shell");
16
+
17
+ expect(message).toContain(
18
+ "The Vellum desktop app is needed to run commands on your computer",
19
+ );
20
+ expect(message).toContain(DESKTOP_APP_DOWNLOAD_URL);
21
+ expect(message).toContain("Open it, then retry.");
22
+ expect(message).not.toContain("client");
23
+ expect(message).not.toContain("daemon");
24
+ });
25
+ });
26
+
12
27
  describe("isPhoneSurface", () => {
13
28
  test("is true for iOS transport and mobile client OS", () => {
14
29
  expect(isPhoneSurface({ transportInterface: "ios" })).toBe(true);
@@ -49,7 +64,9 @@ describe("shouldOfferLoggedInBrowser", () => {
49
64
  expect(shouldOfferLoggedInBrowser('Invalid browser_mode "bogus"')).toBe(
50
65
  false,
51
66
  );
52
- expect(shouldOfferLoggedInBrowser("url must use http or https")).toBe(false);
67
+ expect(shouldOfferLoggedInBrowser("url must use http or https")).toBe(
68
+ false,
69
+ );
53
70
  });
54
71
  });
55
72
 
@@ -71,6 +88,7 @@ describe("formatLoggedInBrowserOffer", () => {
71
88
  });
72
89
  expect(offer).toContain("no in-app browser");
73
90
  expect(offer).toContain("Do not describe a browser panel");
91
+ expect(offer).toContain("Mac or Windows PC");
74
92
  expect(offer).toContain(DESKTOP_APP_DOWNLOAD_URL);
75
93
  expect(offer).toContain(CHROME_WEB_STORE_INSTALL_URL);
76
94
  });
@@ -78,7 +96,9 @@ describe("formatLoggedInBrowserOffer", () => {
78
96
 
79
97
  describe("appendLoggedInBrowserOffer", () => {
80
98
  test("appends the offer once", () => {
81
- const once = appendLoggedInBrowserOffer("Error: Navigation failed: timeout");
99
+ const once = appendLoggedInBrowserOffer(
100
+ "Error: Navigation failed: timeout",
101
+ );
82
102
  expect(once).toContain("Error: Navigation failed: timeout");
83
103
  expect(once).toContain(DESKTOP_APP_DOWNLOAD_URL);
84
104
  const twice = appendLoggedInBrowserOffer(once);
@@ -7,7 +7,22 @@ export const DESKTOP_APP_DOWNLOAD_URL = "https://www.vellum.ai/downloads";
7
7
  export const CHROME_WEB_STORE_INSTALL_URL =
8
8
  "https://chromewebstore.google.com/detail/vellum-assistant-browser/hphbdmpffeigpcdjkckleobjmhhokpne";
9
9
 
10
- export const DESKTOP_APP_INSTALL_HINT = `Install the Vellum desktop app: ${DESKTOP_APP_DOWNLOAD_URL}`;
10
+ export const DESKTOP_APP_INSTALL_HINT = `Download the Vellum desktop app: ${DESKTOP_APP_DOWNLOAD_URL}. Open it, then retry.`;
11
+
12
+ export type DesktopAppCapability = "files" | "screen" | "shell" | "apps";
13
+
14
+ const DESKTOP_APP_CAPABILITY_ACTION: Record<DesktopAppCapability, string> = {
15
+ files: "access files on your computer",
16
+ screen: "view or control your screen",
17
+ shell: "run commands on your computer",
18
+ apps: "control apps on your computer",
19
+ };
20
+
21
+ export function formatDesktopAppRequired(
22
+ capability: DesktopAppCapability,
23
+ ): string {
24
+ return `Error: The Vellum desktop app is needed to ${DESKTOP_APP_CAPABILITY_ACTION[capability]}. ${DESKTOP_APP_INSTALL_HINT}`;
25
+ }
11
26
 
12
27
  /** User-facing install step for status userActions and command-failure hints. */
13
28
  export const CHROME_EXTENSION_INSTALL_HINT = `Install the Vellum Assistant Chrome extension from the Chrome Web Store: ${CHROME_WEB_STORE_INSTALL_URL}`;
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { RiskLevel } from "../../permissions/types.js";
11
+ import { formatDesktopAppRequired } from "../capability-offer.js";
11
12
  import type {
12
13
  ToolContext,
13
14
  ToolDefinition,
@@ -31,7 +32,7 @@ function proxyExecute(toolName: string) {
31
32
  ): Promise<ToolExecutionResult> => {
32
33
  if (!context.proxyToolResolver) {
33
34
  return {
34
- content: `No proxy resolver configured for proxy tool "${toolName}". This tool requires a connected desktop client.`,
35
+ content: formatDesktopAppRequired("screen"),
35
36
  isError: true,
36
37
  };
37
38
  }
@@ -381,6 +382,112 @@ export const computerUseRunAppleScriptTool = {
381
382
  execute: proxyExecute("computer_use_run_applescript"),
382
383
  } satisfies ToolDefinition;
383
384
 
385
+ // ---------------------------------------------------------------------------
386
+ // sequence
387
+ // ---------------------------------------------------------------------------
388
+
389
+ export const computerUseSequenceTool = {
390
+ name: "computer_use_sequence",
391
+ description:
392
+ "Run several computer-use actions you already know, in order, in one step, for example open an app, press cmd+n, type a URL and press enter. Use it when no action depends on seeing the result of the one before; otherwise act one step at a time. Element IDs refer to the latest observation, so use them only for actions that act before the screen changes. Stops at the first action that is refused or fails and reports which one. Returns one observation after the last action.",
393
+ category: "computer-use",
394
+ defaultRiskLevel: RiskLevel.Low,
395
+ executionTarget: "host",
396
+ supportedClientOs: ["macos"],
397
+
398
+ input_schema: {
399
+ type: "object",
400
+ properties: {
401
+ actions: {
402
+ type: "array",
403
+ minItems: 1,
404
+ maxItems: 10,
405
+ description: "The actions to run, in order",
406
+ items: {
407
+ type: "object",
408
+ properties: {
409
+ action: {
410
+ type: "string",
411
+ enum: [
412
+ "key",
413
+ "type_text",
414
+ "type",
415
+ "click",
416
+ "double_click",
417
+ "right_click",
418
+ "scroll",
419
+ "wait",
420
+ "open_app",
421
+ ],
422
+ description: "The action to run",
423
+ },
424
+ key: {
425
+ type: "string",
426
+ description:
427
+ "key: key or shortcut to press (e.g. enter, tab, cmd+n)",
428
+ },
429
+ text: {
430
+ type: "string",
431
+ description: "type_text (or type): the text to type",
432
+ },
433
+ element_id: {
434
+ type: "integer",
435
+ description:
436
+ "click, double_click, right_click, scroll: the [ID] of the element from the latest accessibility tree (preferred)",
437
+ },
438
+ x: {
439
+ type: "integer",
440
+ description:
441
+ "click, double_click, right_click, scroll: X coordinate on screen (fallback when no element_id)",
442
+ },
443
+ y: {
444
+ type: "integer",
445
+ description:
446
+ "click, double_click, right_click, scroll: Y coordinate on screen (fallback when no element_id)",
447
+ },
448
+ direction: {
449
+ type: "string",
450
+ enum: ["up", "down", "left", "right"],
451
+ description: "scroll: scroll direction",
452
+ },
453
+ amount: {
454
+ type: "integer",
455
+ description: "scroll: scroll amount (1-10)",
456
+ },
457
+ duration_ms: {
458
+ type: "integer",
459
+ description: "wait: milliseconds to wait",
460
+ },
461
+ app_name: {
462
+ type: "string",
463
+ description:
464
+ 'open_app: the name of the application to open (e.g. "Google Chrome")',
465
+ },
466
+ reasoning: {
467
+ type: "string",
468
+ description: "Optional: why this action",
469
+ },
470
+ },
471
+ required: ["action"],
472
+ },
473
+ },
474
+ reasoning: {
475
+ type: "string",
476
+ description:
477
+ "Optional: what these actions do together. Reasoning on each action works too",
478
+ },
479
+ target_client_id: {
480
+ type: "string",
481
+ description:
482
+ "ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
483
+ },
484
+ },
485
+ required: ["actions"],
486
+ },
487
+
488
+ execute: proxyExecute("computer_use_sequence"),
489
+ } satisfies ToolDefinition;
490
+
384
491
  // ---------------------------------------------------------------------------
385
492
  // done
386
493
  // ---------------------------------------------------------------------------
@@ -456,7 +563,18 @@ export const computerUseObserveTool = {
456
563
  type: "integer",
457
564
  minimum: 1,
458
565
  maximum: 4294967295,
459
- description: "macOS only: capture this native CGWindowID instead of the desktop, including only its accessibility tree. Obtain a current native window ID first; do not guess or use a browser tab ID. Applies to this observation only, not subsequent actions. Requires a desktop helper with window-capture support. Screenshot coordinates are window-relative; use accessibility element IDs for later actions, not desktop scaling.",
566
+ description:
567
+ "macOS only: capture this native CGWindowID instead of the desktop, including only its accessibility tree. Obtain a current native window ID first; do not guess or use a browser tab ID. Applies to this observation only, not subsequent actions. Requires a desktop helper with window-capture support. Screenshot coordinates are window-relative; use accessibility element IDs for later actions, not desktop scaling.",
568
+ },
569
+ include_screenshot: {
570
+ type: "boolean",
571
+ description:
572
+ "Force a screenshot with this observation. The accessibility tree is returned every step; ask for pixels whenever the tree is not enough to act on, such as a canvas, a game, a custom-drawn view, a window with few or unlabeled controls, or a question about layout.",
573
+ },
574
+ full_tree: {
575
+ type: "boolean",
576
+ description:
577
+ "Walk the accessibility tree to full depth. Observations list the tree to a limited depth and say when it was cut off; pass this when the element you need is not in the tree.",
460
578
  },
461
579
  target_client_id: {
462
580
  type: "string",
@@ -484,6 +602,7 @@ export const allComputerUseTools: ToolDefinition[] = [
484
602
  computerUseWaitTool,
485
603
  computerUseOpenAppTool,
486
604
  computerUseRunAppleScriptTool,
605
+ computerUseSequenceTool,
487
606
  computerUseDoneTool,
488
607
  computerUseRespondTool,
489
608
  ];
@@ -5,6 +5,7 @@
5
5
  * the proxy resolver, which forwards the call to the connected desktop client.
6
6
  */
7
7
 
8
+ import { formatDesktopAppRequired } from "../capability-offer.js";
8
9
  import { throwIfCancelled } from "../shared/abort.js";
9
10
  import type { ToolContext, ToolExecutionResult } from "../types.js";
10
11
 
@@ -45,7 +46,7 @@ export function forwardComputerUseProxyTool(
45
46
  }
46
47
  if (!context.proxyToolResolver) {
47
48
  return Promise.resolve({
48
- content: `Cannot execute ${toolName}: no proxy resolver available. This tool requires a connected desktop client.`,
49
+ content: formatDesktopAppRequired("screen"),
49
50
  isError: true,
50
51
  });
51
52
  }
@@ -20,6 +20,7 @@ import {
20
20
  } from "@vellumai/credential-storage";
21
21
  import type { CredentialRecord } from "@vellumai/service-contracts/credential-rpc";
22
22
 
23
+ import { createCesHttpRecordBackendIfConfigured } from "../../security/ces-http-record-backend.js";
23
24
  import type { CredentialRecordBackend } from "../../security/ces-rpc-record-backend.js";
24
25
  import { getLogger } from "../../util/logger.js";
25
26
  import type { CredentialInjectionTemplate } from "./policy-types.js";
@@ -55,6 +56,11 @@ const CES_MEMORY_PATH = "ces-in-memory-credential-metadata";
55
56
  let _store: StaticCredentialMetadataStore | undefined;
56
57
  let _overridePath: string | null = null;
57
58
  let _recordBackend: CredentialRecordBackend | undefined;
59
+ /**
60
+ * Test override for the CES HTTP listing fallback. `undefined` uses the
61
+ * production factory. `null` disables HTTP fallback.
62
+ */
63
+ let _httpRecordBackendOverride: CredentialRecordBackend | null | undefined;
58
64
  let _cesRecordsLoaded = false;
59
65
  let _cesLoad: Promise<void> | undefined;
60
66
  let _cesLoadGeneration = 0;
@@ -255,6 +261,93 @@ export function listCredentialMetadata(): CredentialMetadata[] {
255
261
  return getStore().list() as CredentialMetadata[];
256
262
  }
257
263
 
264
+ export type CredentialRecordListResult = {
265
+ records: CredentialMetadata[];
266
+ unreachable: boolean;
267
+ };
268
+
269
+ type ListedCredentialRecords = Array<{
270
+ account: string;
271
+ record: CredentialRecord;
272
+ }>;
273
+
274
+ function backendIsAvailable(backend: CredentialRecordBackend): boolean {
275
+ try {
276
+ return backend.isAvailable();
277
+ } catch {
278
+ return false;
279
+ }
280
+ }
281
+
282
+ async function listFromRecordBackend(
283
+ backend: CredentialRecordBackend,
284
+ ): Promise<ListedCredentialRecords | null> {
285
+ if (!backendIsAvailable(backend)) {
286
+ return null;
287
+ }
288
+ return backend.list();
289
+ }
290
+
291
+ function resolveHttpRecordBackend(): CredentialRecordBackend | null {
292
+ if (_httpRecordBackendOverride !== undefined) {
293
+ return _httpRecordBackendOverride;
294
+ }
295
+ return createCesHttpRecordBackendIfConfigured();
296
+ }
297
+
298
+ async function listFromHttpFallback(): Promise<ListedCredentialRecords | null> {
299
+ const httpBackend = resolveHttpRecordBackend();
300
+ if (!httpBackend) {
301
+ return null;
302
+ }
303
+ return listFromRecordBackend(httpBackend);
304
+ }
305
+
306
+ /**
307
+ * Live catalog from the credential record backend. Does not read the
308
+ * daemon in-process cache, so an outage cannot be mistaken for an empty
309
+ * vault. `unreachable` is true when the backend is missing, down, or
310
+ * the list RPC fails, and CES HTTP failover is also unavailable.
311
+ *
312
+ * When the attached backend is CES RPC (or none is attached), listing
313
+ * fails over to CES HTTP in containerized deployments so a down RPC
314
+ * socket does not hide a healthy vault sidecar. A successful empty
315
+ * catalog (`[]`) does not fall through to HTTP.
316
+ *
317
+ * The file-backed test store is the catalog when a test override path is set.
318
+ */
319
+ export async function listCredentialRecordsLive(): Promise<CredentialRecordListResult> {
320
+ if (_overridePath) {
321
+ return {
322
+ records: getStore().list() as CredentialMetadata[],
323
+ unreachable: false,
324
+ };
325
+ }
326
+ if (_recordBackend) {
327
+ const remote = await listFromRecordBackend(_recordBackend);
328
+ if (remote !== null) {
329
+ return {
330
+ records: remote.map((entry) => entry.record as CredentialMetadata),
331
+ unreachable: false,
332
+ };
333
+ }
334
+ if (_recordBackend.name !== "ces-rpc") {
335
+ return { records: [], unreachable: true };
336
+ }
337
+ }
338
+ const httpRemote = await listFromHttpFallback();
339
+ if (httpRemote !== null) {
340
+ log.warn(
341
+ "CES RPC credential record list unavailable. Failing over to CES HTTP.",
342
+ );
343
+ return {
344
+ records: httpRemote.map((entry) => entry.record as CredentialMetadata),
345
+ unreachable: false,
346
+ };
347
+ }
348
+ return { records: [], unreachable: true };
349
+ }
350
+
258
351
  /**
259
352
  * Delete metadata for a credential.
260
353
  */
@@ -287,3 +380,10 @@ export function _setMetadataPath(path: string | null): void {
287
380
  }
288
381
  }
289
382
  }
383
+
384
+ /** @internal Test-only: override the CES HTTP listing fallback. */
385
+ export function _setHttpRecordBackendForTests(
386
+ backend: CredentialRecordBackend | null | undefined,
387
+ ): void {
388
+ _httpRecordBackendOverride = backend;
389
+ }