@vellumai/assistant 0.12.0 → 0.12.1-dev.202609142113.d1590bc

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 (541) hide show
  1. package/AGENTS.md +5 -1
  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 +2 -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 +119 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +71 -0
  254. package/src/desktop/desktop-chrome-policy.ts +42 -0
  255. package/src/desktop/desktop-dependencies.test.ts +102 -0
  256. package/src/desktop/desktop-dependencies.ts +306 -0
  257. package/src/desktop/desktop-feature.test.ts +18 -0
  258. package/src/desktop/desktop-feature.ts +19 -0
  259. package/src/desktop/desktop-panel-config.test.ts +135 -0
  260. package/src/desktop/desktop-panel-config.ts +149 -0
  261. package/src/desktop/desktop-session-manager.test.ts +627 -0
  262. package/src/desktop/desktop-session-manager.ts +814 -0
  263. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  264. package/src/desktop/desktop-stream-bridge.ts +214 -0
  265. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  266. package/src/desktop/desktop-wallpaper.ts +113 -0
  267. package/src/desktop/desktop-window-theme.ts +170 -0
  268. package/src/heartbeat/heartbeat-service.ts +21 -3
  269. package/src/home/__tests__/feed-writer.test.ts +22 -0
  270. package/src/home/feed-writer.ts +11 -2
  271. package/src/hooks/types.ts +33 -0
  272. package/src/i18n/AGENTS.md +22 -0
  273. package/src/i18n/__tests__/i18n.test.ts +108 -0
  274. package/src/i18n/index.ts +16 -0
  275. package/src/i18n/locales.ts +93 -0
  276. package/src/i18n/messages.ts +61 -0
  277. package/src/i18n/title.ts +25 -0
  278. package/src/inbound/platform-callback-registration.ts +4 -5
  279. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  280. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  281. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  282. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  283. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  284. package/src/live-voice/live-voice-photo.ts +25 -6
  285. package/src/live-voice/live-voice-session.ts +213 -693
  286. package/src/live-voice/protocol.ts +0 -2
  287. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  288. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  289. package/src/mcp/effective-config.ts +3 -3
  290. package/src/mcp/mcp-header-store.ts +29 -45
  291. package/src/mcp/spec-schema.ts +111 -0
  292. package/src/mcp/startup.ts +8 -4
  293. package/src/mcp/workspace-mcp-config.ts +188 -0
  294. package/src/messaging/provider.ts +10 -3
  295. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  296. package/src/messaging/providers/callback-routing.ts +18 -0
  297. package/src/messaging/providers/channel-transport.ts +56 -0
  298. package/src/messaging/providers/discord/transport.ts +17 -0
  299. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  300. package/src/messaging/providers/slack/adapter.ts +7 -44
  301. package/src/messaging/providers/slack/transport.ts +16 -0
  302. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  303. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  304. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  305. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  306. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  307. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  308. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  309. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  310. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  311. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  312. package/src/notifications/adapters/macos.ts +3 -0
  313. package/src/notifications/assistant-reply-producer.ts +8 -3
  314. package/src/notifications/conversation-pairing.ts +36 -16
  315. package/src/notifications/delivered-post-record.ts +11 -1
  316. package/src/notifications/guardian-feed-projection.ts +125 -5
  317. package/src/notifications/home-feed-side-effect.ts +55 -7
  318. package/src/notifications/schedule-result-producer.ts +66 -22
  319. package/src/oauth/AGENTS.md +2 -0
  320. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  321. package/src/oauth/byo-connection.test.ts +105 -0
  322. package/src/oauth/byo-connection.ts +36 -6
  323. package/src/oauth/connection-resolver.test.ts +73 -0
  324. package/src/oauth/connection-resolver.ts +95 -2
  325. package/src/oauth/scope-utils.ts +24 -0
  326. package/src/oauth/seed-providers.ts +204 -25
  327. package/src/outbound-proxy/certs.ts +3 -5
  328. package/src/permissions/checker.test.ts +11 -6
  329. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  330. package/src/persistence/conversation-crud.ts +71 -63
  331. package/src/persistence/conversation-lineage.ts +12 -4
  332. package/src/persistence/conversation-queries.ts +2 -2
  333. package/src/persistence/conversation-title-placeholders.ts +23 -0
  334. package/src/persistence/conversation-title-service.ts +9 -26
  335. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  336. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  337. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  338. package/src/persistence/message-cursor.ts +78 -0
  339. package/src/persistence/message-reads.ts +30 -0
  340. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  341. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  342. package/src/persistence/schema/memory-core.ts +5 -0
  343. package/src/platform/client.test.ts +4 -2
  344. package/src/platform/client.ts +2 -10
  345. package/src/plugin-api/constants.ts +2 -0
  346. package/src/plugin-api/index.ts +1 -0
  347. package/src/plugin-api/types.ts +2 -0
  348. package/src/plugins/defaults/index.ts +2 -0
  349. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  350. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  351. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  352. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  358. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  359. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  360. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  361. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  362. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  363. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  364. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  365. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  366. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  367. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  368. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  369. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  370. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  371. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  372. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  374. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  375. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  376. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  377. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  378. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  379. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  380. package/src/plugins/defaults/memory/startup.ts +28 -2
  381. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  382. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  383. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  384. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  385. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  386. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  387. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  388. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  389. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  390. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  391. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  392. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  393. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  394. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  395. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  396. package/src/plugins/defaults/memory/worker.ts +6 -0
  397. package/src/plugins/mcp-servers.ts +15 -75
  398. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  399. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  400. package/src/providers/anthropic/client.ts +0 -47
  401. package/src/providers/gemini/client.ts +9 -23
  402. package/src/providers/gemini-thought-signature.test.ts +63 -0
  403. package/src/providers/gemini-thought-signature.ts +44 -0
  404. package/src/providers/inference/auth.ts +2 -3
  405. package/src/providers/inference/connection-availability.ts +27 -8
  406. package/src/providers/inference/resolve-auth.ts +22 -4
  407. package/src/providers/inference/service-account-token.ts +248 -0
  408. package/src/providers/model-catalog.ts +34 -0
  409. package/src/providers/model-intents.ts +10 -5
  410. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  411. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  412. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  413. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  414. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  415. package/src/providers/openai/chat-completions-provider.ts +212 -79
  416. package/src/providers/openai/codex-models.ts +0 -4
  417. package/src/providers/openai/google-thought-signature.ts +320 -0
  418. package/src/providers/openrouter/client.ts +20 -0
  419. package/src/providers/outbound-request-sanitize.ts +96 -0
  420. package/src/providers/retry.ts +24 -3
  421. package/src/providers/unparseable-tool-args.ts +5 -1
  422. package/src/runtime/AGENTS.md +3 -1
  423. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  424. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  425. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  426. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  427. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  428. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  429. package/src/runtime/agent-wake.ts +2 -1
  430. package/src/runtime/assistant-event-hub.ts +21 -3
  431. package/src/runtime/auth/same-actor.ts +38 -3
  432. package/src/runtime/channel-send.ts +316 -0
  433. package/src/runtime/host-observe.ts +11 -1
  434. package/src/runtime/http-server.ts +160 -151
  435. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  436. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  437. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  438. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  439. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  440. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  441. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  442. package/src/runtime/routes/acp-routes.test.ts +15 -0
  443. package/src/runtime/routes/acp-routes.ts +72 -9
  444. package/src/runtime/routes/app-management-routes.ts +73 -0
  445. package/src/runtime/routes/app-routes.ts +1 -1
  446. package/src/runtime/routes/channel-send-routes.ts +85 -0
  447. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  448. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  449. package/src/runtime/routes/conversation-routes.ts +16 -3
  450. package/src/runtime/routes/credential-routes.ts +19 -10
  451. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  452. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  453. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  454. package/src/runtime/routes/events-routes.ts +4 -0
  455. package/src/runtime/routes/host-cu-routes.ts +6 -0
  456. package/src/runtime/routes/index.ts +4 -2
  457. package/src/runtime/routes/log-export-routes.ts +2 -0
  458. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  459. package/src/runtime/routes/migration-routes.ts +6 -20
  460. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  461. package/src/runtime/routes/platform-routes.ts +18 -15
  462. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  463. package/src/runtime/routes/workspace-routes.ts +46 -63
  464. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  465. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  466. package/src/runtime/routes/workspace-utils.ts +17 -0
  467. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  468. package/src/runtime/services/conversation-serializer.ts +16 -3
  469. package/src/runtime/sync/resource-sync-events.ts +2 -1
  470. package/src/schedule/run-script.ts +2 -1
  471. package/src/schedule/schedule-store.ts +9 -2
  472. package/src/schedule/worker.ts +1 -1
  473. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  474. package/src/security/ces-http-record-backend.ts +120 -0
  475. package/src/security/ces-rpc-record-backend.ts +7 -0
  476. package/src/security/untrusted-content.ts +3 -1
  477. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  478. package/src/skills/workspace-skill-md-signature.ts +50 -0
  479. package/src/subagent/consult-context.ts +12 -25
  480. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  481. package/src/tools/acp/list-agents.test.ts +3 -1
  482. package/src/tools/acp/spawn.test.ts +146 -11
  483. package/src/tools/acp/spawn.ts +21 -3
  484. package/src/tools/acp/status.test.ts +94 -0
  485. package/src/tools/acp/status.ts +40 -3
  486. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  487. package/src/tools/browser/browser-execution.ts +1 -1
  488. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  489. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  490. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  491. package/src/tools/browser/network-recorder.ts +2 -1
  492. package/src/tools/capability-offer.test.ts +22 -2
  493. package/src/tools/capability-offer.ts +16 -1
  494. package/src/tools/computer-use/definitions.ts +121 -2
  495. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/credentials/metadata-store.ts +100 -0
  497. package/src/tools/filesystem/search.ts +2 -1
  498. package/src/tools/host-filesystem/edit.test.ts +2 -1
  499. package/src/tools/host-filesystem/edit.ts +2 -2
  500. package/src/tools/host-filesystem/read.test.ts +2 -1
  501. package/src/tools/host-filesystem/read.ts +2 -2
  502. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  503. package/src/tools/host-filesystem/transfer.ts +2 -2
  504. package/src/tools/host-filesystem/write.test.ts +2 -1
  505. package/src/tools/host-filesystem/write.ts +2 -2
  506. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  507. package/src/tools/host-terminal/host-shell.ts +4 -20
  508. package/src/tools/network/web-search.ts +6 -1
  509. package/src/tools/policy-context.ts +6 -7
  510. package/src/tools/skills/scaffold-managed.ts +61 -3
  511. package/src/tools/subagent/spawn.ts +5 -8
  512. package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
  513. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  514. package/src/tools/terminal/safe-env.ts +23 -3
  515. package/src/tools/terminal/sanitized-bash.ts +105 -0
  516. package/src/tools/watch/watch-retro-report.ts +0 -8
  517. package/src/types/host-capabilities.ts +5 -3
  518. package/src/util/__tests__/language-subtag.test.ts +23 -1
  519. package/src/util/ca-bundle.ts +13 -0
  520. package/src/util/language-subtag.ts +16 -0
  521. package/src/util/platform.ts +8 -0
  522. package/src/util/unicode.ts +15 -1
  523. package/src/watch/watch-timeline.ts +2 -2
  524. package/src/workflows/run-manager.ts +2 -1
  525. package/src/workspace/git-porcelain.ts +66 -0
  526. package/src/workspace/git-service.ts +6 -55
  527. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  528. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  529. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  530. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  531. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  532. package/src/workspace/migrations/registry.ts +6 -0
  533. package/src/__tests__/app-source-watcher.test.ts +0 -218
  534. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  535. package/src/__tests__/speaker-identification.test.ts +0 -56
  536. package/src/calls/speaker-identification.ts +0 -233
  537. package/src/daemon/app-source-watcher.ts +0 -218
  538. package/src/messaging/providers/whatsapp/client.ts +0 -29
  539. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  540. package/src/util/fs-watcher-error.ts +0 -36
  541. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -36,10 +36,36 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
36
36
  },
37
37
  }));
38
38
 
39
+ // Captured `log.info` payloads, so the step-timings line can be asserted on.
40
+ const infoLogs: { payload: Record<string, unknown>; message: string }[] = [];
41
+
42
+ const realLogger = await import("../util/logger.js");
43
+ mock.module("../util/logger.js", () => ({
44
+ ...realLogger,
45
+ getLogger: () => ({
46
+ info: (payload: unknown, message: unknown) => {
47
+ infoLogs.push({
48
+ payload: (payload ?? {}) as Record<string, unknown>,
49
+ message: String(message),
50
+ });
51
+ },
52
+ warn: () => {},
53
+ error: () => {},
54
+ debug: () => {},
55
+ trace: () => {},
56
+ }),
57
+ }));
58
+
39
59
  // Use the REAL pending-interactions module — the proxy self-registers here.
40
60
  const pendingInteractions = await import("../runtime/pending-interactions.js");
41
61
  const { HostCuProxy } = await import("../daemon/host-cu-proxy.js");
42
62
 
63
+ function stepTimingsLogs(): Record<string, unknown>[] {
64
+ return infoLogs
65
+ .filter((entry) => entry.message === "Host CU step timings")
66
+ .map((entry) => entry.payload);
67
+ }
68
+
43
69
  describe("HostCuProxy", () => {
44
70
  let proxy: InstanceType<typeof HostCuProxy>;
45
71
 
@@ -48,6 +74,7 @@ describe("HostCuProxy", () => {
48
74
  sentOptions.length = 0;
49
75
  mockHasClient = false;
50
76
  mockClients = [];
77
+ infoLogs.length = 0;
51
78
  pendingInteractions.clear();
52
79
  proxy = new HostCuProxy(maxSteps);
53
80
  }
@@ -164,6 +191,124 @@ describe("HostCuProxy", () => {
164
191
  });
165
192
  });
166
193
 
194
+ // -------------------------------------------------------------------------
195
+ // Step timings
196
+ // -------------------------------------------------------------------------
197
+
198
+ describe("step timings", () => {
199
+ test("logs helper timings alongside the proxy round trip", async () => {
200
+ setup();
201
+ proxy.recordAction("computer_use_click", { element_id: 42 });
202
+
203
+ const resultPromise = proxy.request(
204
+ "computer_use_click",
205
+ { element_id: 42 },
206
+ "session-1",
207
+ 1,
208
+ );
209
+ const sent = sentMessages[0] as Record<string, unknown>;
210
+
211
+ proxy.processObservation(sent.requestId as string, {
212
+ axTree: "Button [1]",
213
+ executionResult: "Clicked element 42",
214
+ timings: { total: 420, axWalk: 120, capture: 240 },
215
+ });
216
+
217
+ const result = await resultPromise;
218
+ const logs = stepTimingsLogs();
219
+ expect(logs).toHaveLength(1);
220
+ expect(logs[0]).toMatchObject({
221
+ requestId: sent.requestId,
222
+ toolName: "computer_use_click",
223
+ step: 1,
224
+ helper: { total: 420, axWalk: 120, capture: 240 },
225
+ });
226
+ expect(typeof logs[0].roundTripMs).toBe("number");
227
+
228
+ // The timings are observability only: nothing reaches the model.
229
+ expect(result.content).not.toContain("420");
230
+ expect(result.content).not.toContain("roundTripMs");
231
+ });
232
+
233
+ test("labels each line with its own request, not the latest dispatch", async () => {
234
+ setup();
235
+
236
+ // One model response can dispatch several CU tools, and the agent loop
237
+ // runs them concurrently. Dispatch two, then answer the first one last.
238
+ proxy.recordAction("computer_use_click", { element_id: 42 });
239
+ const firstPromise = proxy.request(
240
+ "computer_use_click",
241
+ { element_id: 42 },
242
+ "session-1",
243
+ proxy.stepCount,
244
+ );
245
+ proxy.recordAction("computer_use_type_text", { text: "hello" });
246
+ const secondPromise = proxy.request(
247
+ "computer_use_type_text",
248
+ { text: "hello" },
249
+ "session-1",
250
+ proxy.stepCount,
251
+ );
252
+
253
+ const first = sentMessages[0] as Record<string, unknown>;
254
+ const second = sentMessages[1] as Record<string, unknown>;
255
+
256
+ proxy.processObservation(second.requestId as string, {
257
+ axTree: "Field [2]",
258
+ timings: { total: 200 },
259
+ });
260
+ proxy.processObservation(first.requestId as string, {
261
+ axTree: "Button [1]",
262
+ timings: { total: 400 },
263
+ });
264
+ await Promise.all([firstPromise, secondPromise]);
265
+
266
+ const logs = stepTimingsLogs();
267
+ const firstLog = logs.find((l) => l.requestId === first.requestId);
268
+ const secondLog = logs.find((l) => l.requestId === second.requestId);
269
+
270
+ // The click landed after the type_text, so reading current proxy state
271
+ // would have filed it under computer_use_type_text at step 2.
272
+ expect(firstLog).toMatchObject({
273
+ toolName: "computer_use_click",
274
+ step: 1,
275
+ helper: { total: 400 },
276
+ });
277
+ expect(secondLog).toMatchObject({
278
+ toolName: "computer_use_type_text",
279
+ step: 2,
280
+ helper: { total: 200 },
281
+ });
282
+ });
283
+
284
+ test("an observation without timings resolves normally", async () => {
285
+ setup();
286
+
287
+ const resultPromise = proxy.request(
288
+ "computer_use_click",
289
+ { element_id: 7 },
290
+ "session-1",
291
+ 1,
292
+ );
293
+ const sent = sentMessages[0] as Record<string, unknown>;
294
+
295
+ proxy.processObservation(sent.requestId as string, {
296
+ axTree: "Button [1]",
297
+ executionResult: "Clicked element 7",
298
+ });
299
+
300
+ const result = await resultPromise;
301
+ expect(result.isError).toBe(false);
302
+ expect(result.content).toContain("Clicked element 7");
303
+ expect(result.content).toContain("<ax-tree>");
304
+
305
+ const logs = stepTimingsLogs();
306
+ expect(logs).toHaveLength(1);
307
+ expect(logs[0]).not.toHaveProperty("helper");
308
+ expect(typeof logs[0].roundTripMs).toBe("number");
309
+ });
310
+ });
311
+
167
312
  // -------------------------------------------------------------------------
168
313
  // Timeout
169
314
  // -------------------------------------------------------------------------
@@ -1652,4 +1797,398 @@ describe("HostCuProxy", () => {
1652
1797
  expect(proxy.previousAXTree).toBeUndefined();
1653
1798
  });
1654
1799
  });
1800
+
1801
+ describe("batched actions", () => {
1802
+ const ACTIONS = [
1803
+ { action: "open_app", app_name: "Google Chrome" },
1804
+ { action: "key", key: "cmd+n" },
1805
+ { action: "type_text", text: "example.com" },
1806
+ { action: "key", key: "enter" },
1807
+ ];
1808
+
1809
+ function connect(capabilities: string[]) {
1810
+ mockClients = [
1811
+ { clientId: "mac-1", actorPrincipalId: "user-1", capabilities },
1812
+ ];
1813
+ }
1814
+ function sequence(targetClientId?: string) {
1815
+ return proxy.request(
1816
+ "computer_use_sequence",
1817
+ { actions: ACTIONS, reasoning: "open a new window on a known URL" },
1818
+ "session-1",
1819
+ 1,
1820
+ "open a new window on a known URL",
1821
+ undefined,
1822
+ targetClientId,
1823
+ "user-1",
1824
+ );
1825
+ }
1826
+
1827
+ test("a client without host_cu_sequence is refused and nothing is sent", async () => {
1828
+ setup();
1829
+ connect(["host_cu", "host_cu_window_capture"]);
1830
+ for (const target of [undefined, "mac-1"]) {
1831
+ const result = await sequence(target);
1832
+ expect(result.isError).toBe(true);
1833
+ expect(result.content).toContain(
1834
+ "Batched actions require a desktop app that supports computer_use_sequence",
1835
+ );
1836
+ expect(result.content).toContain("Nothing was run.");
1837
+ }
1838
+ expect(sentMessages).toHaveLength(0);
1839
+ });
1840
+
1841
+ test("no connected client never falls back to an untargeted broadcast", async () => {
1842
+ setup();
1843
+ expect((await sequence()).isError).toBe(true);
1844
+ expect(sentMessages).toHaveLength(0);
1845
+ });
1846
+
1847
+ test("a client with host_cu_sequence gets one request carrying the actions", async () => {
1848
+ setup();
1849
+ connect(["host_cu", "host_cu_sequence"]);
1850
+ const pending = sequence();
1851
+ expect(sentMessages).toHaveLength(1);
1852
+ const sent = sentMessages[0] as {
1853
+ requestId: string;
1854
+ toolName: string;
1855
+ targetClientId: string;
1856
+ input: Record<string, unknown>;
1857
+ };
1858
+ expect(sent.toolName).toBe("computer_use_sequence");
1859
+ expect(sent.targetClientId).toBe("mac-1");
1860
+ expect(sent.input.actions).toEqual(ACTIONS);
1861
+ proxy.processObservation(sent.requestId, {
1862
+ axTree: "Window: New Tab",
1863
+ executionResult: "Ran 4 actions",
1864
+ });
1865
+ const result = await pending;
1866
+ expect(result.isError).toBe(false);
1867
+ expect(result.content).toContain("Ran 4 actions");
1868
+ });
1869
+ });
1870
+
1871
+ describe("screenshot on request", () => {
1872
+ const TREE = 'Window: "Inbox" (Mail)\n [1] button "Reply" at (10, 10)';
1873
+ const OMITTED = "Screenshot omitted";
1874
+
1875
+ function connect() {
1876
+ mockClients = [
1877
+ {
1878
+ clientId: "mac-1",
1879
+ actorPrincipalId: "user-1",
1880
+ capabilities: [
1881
+ "host_cu",
1882
+ "host_cu_window_capture",
1883
+ "host_cu_annotate",
1884
+ ],
1885
+ },
1886
+ ];
1887
+ }
1888
+
1889
+ /** Dispatch one request and return what was sent, without answering it. */
1890
+ function dispatch(
1891
+ input: Record<string, unknown> = { element_id: 1 },
1892
+ toolName = "computer_use_click",
1893
+ ) {
1894
+ const pending = proxy.request(
1895
+ toolName,
1896
+ input,
1897
+ "session-1",
1898
+ 1,
1899
+ undefined,
1900
+ undefined,
1901
+ "mac-1",
1902
+ "user-1",
1903
+ );
1904
+ const sent = sentMessages.at(-1) as {
1905
+ requestId: string;
1906
+ input: Record<string, unknown>;
1907
+ };
1908
+ return { pending, sent };
1909
+ }
1910
+
1911
+ /** Dispatch one request, answer it with `observation`, and return both ends. */
1912
+ async function step(
1913
+ observation: Record<string, unknown>,
1914
+ input?: Record<string, unknown>,
1915
+ toolName?: string,
1916
+ ) {
1917
+ const { pending, sent } = dispatch(input, toolName);
1918
+ proxy.processObservation(sent.requestId, observation);
1919
+ return { sent, result: await pending };
1920
+ }
1921
+
1922
+ function asksForScreenshot(sent: { input: Record<string, unknown> }) {
1923
+ return !Object.hasOwn(sent.input, "includeScreenshot");
1924
+ }
1925
+
1926
+ test("the first observed step attaches and the next unscoped step does not", async () => {
1927
+ setup();
1928
+ connect();
1929
+ const first = await step({ axTree: TREE, screenshot: "img" });
1930
+ expect(first.sent.input).toEqual({ element_id: 1 });
1931
+ expect(first.result.content).not.toContain(OMITTED);
1932
+
1933
+ const second = await step({ axTree: TREE });
1934
+ expect(second.sent.input).toEqual({
1935
+ element_id: 1,
1936
+ includeScreenshot: false,
1937
+ });
1938
+ expect(second.result.content).toContain(OMITTED);
1939
+ expect(second.result.isError).toBe(false);
1940
+ });
1941
+
1942
+ test("the first look is per desktop, so a newly targeted one attaches", async () => {
1943
+ setup();
1944
+ mockClients = [
1945
+ {
1946
+ clientId: "mac-1",
1947
+ actorPrincipalId: "user-1",
1948
+ capabilities: ["host_cu"],
1949
+ },
1950
+ {
1951
+ clientId: "mac-2",
1952
+ actorPrincipalId: "user-1",
1953
+ capabilities: ["host_cu"],
1954
+ },
1955
+ ];
1956
+ const on = (clientId: string) => {
1957
+ const pending = proxy.request(
1958
+ "computer_use_click",
1959
+ { element_id: 1 },
1960
+ "session-1",
1961
+ 1,
1962
+ undefined,
1963
+ undefined,
1964
+ clientId,
1965
+ "user-1",
1966
+ );
1967
+ const sent = sentMessages.at(-1) as {
1968
+ requestId: string;
1969
+ input: Record<string, unknown>;
1970
+ };
1971
+ return { pending, sent };
1972
+ };
1973
+
1974
+ const firstA = on("mac-1");
1975
+ proxy.processObservation(firstA.sent.requestId, {
1976
+ axTree: TREE,
1977
+ screenshot: "img",
1978
+ });
1979
+ await firstA.pending;
1980
+ const secondA = on("mac-1");
1981
+ expect(asksForScreenshot(secondA.sent)).toBe(false);
1982
+ proxy.processObservation(secondA.sent.requestId, { axTree: TREE });
1983
+ await secondA.pending;
1984
+
1985
+ // A different machine the assistant has never seen gets its own look.
1986
+ const firstB = on("mac-2");
1987
+ expect(asksForScreenshot(firstB.sent)).toBe(true);
1988
+ proxy.processObservation(firstB.sent.requestId, {
1989
+ axTree: TREE,
1990
+ screenshot: "img",
1991
+ });
1992
+ await firstB.pending;
1993
+ const secondB = on("mac-2");
1994
+ expect(asksForScreenshot(secondB.sent)).toBe(false);
1995
+ proxy.processObservation(secondB.sent.requestId, { axTree: TREE });
1996
+ await secondB.pending;
1997
+ });
1998
+
1999
+ test("include_screenshot: true attaches and is not forwarded", async () => {
2000
+ setup();
2001
+ connect();
2002
+ await step({ axTree: TREE, screenshot: "img" });
2003
+ const input = { include_screenshot: true };
2004
+ const { sent, result } = await step(
2005
+ { axTree: TREE, screenshot: "img" },
2006
+ input,
2007
+ "computer_use_observe",
2008
+ );
2009
+ expect(sent.input).toEqual({});
2010
+ expect(input).toEqual({ include_screenshot: true });
2011
+ expect(result.content).not.toContain(OMITTED);
2012
+ });
2013
+
2014
+ test("include_screenshot: false is stripped and the step still skips", async () => {
2015
+ setup();
2016
+ connect();
2017
+ await step({ axTree: TREE, screenshot: "img" });
2018
+ const { sent } = await step(
2019
+ { axTree: TREE },
2020
+ { include_screenshot: false },
2021
+ "computer_use_observe",
2022
+ );
2023
+ expect(sent.input).toEqual({ includeScreenshot: false });
2024
+ });
2025
+
2026
+ test("a scoped observe always attaches", async () => {
2027
+ setup();
2028
+ connect();
2029
+ await step({ axTree: TREE, screenshot: "img" });
2030
+ for (const input of [
2031
+ { capture_window_id: 12 },
2032
+ { captureWindowId: 12 },
2033
+ { captureDisplayId: 1 },
2034
+ ]) {
2035
+ const scoped = await step(
2036
+ { axTree: TREE, screenshot: "img" },
2037
+ input,
2038
+ "computer_use_observe",
2039
+ );
2040
+ expect(scoped.sent.input).toEqual(input);
2041
+ }
2042
+ });
2043
+
2044
+ test("a scoped observation does not count as the first look", async () => {
2045
+ setup();
2046
+ connect();
2047
+ await step(
2048
+ { axTree: TREE, screenshot: "img" },
2049
+ { capture_window_id: 12 },
2050
+ "computer_use_observe",
2051
+ );
2052
+ expect(asksForScreenshot((await step({ axTree: TREE })).sent)).toBe(true);
2053
+ });
2054
+
2055
+ test("reset() makes the next step attach again", async () => {
2056
+ setup();
2057
+ connect();
2058
+ await step({ axTree: TREE, screenshot: "img" });
2059
+ expect(asksForScreenshot((await step({ axTree: TREE })).sent)).toBe(
2060
+ false,
2061
+ );
2062
+ proxy.reset();
2063
+ expect(asksForScreenshot((await step({ axTree: TREE })).sent)).toBe(true);
2064
+ });
2065
+
2066
+ test("the omitted line is absent when a requested capture failed", async () => {
2067
+ setup();
2068
+ connect();
2069
+ const failed = await step({ axTree: TREE });
2070
+ expect(asksForScreenshot(failed.sent)).toBe(true);
2071
+ expect(failed.result.content).not.toContain(OMITTED);
2072
+ });
2073
+
2074
+ test("a first look whose capture failed is asked for again", async () => {
2075
+ setup();
2076
+ connect();
2077
+ const failed = await step({ axTree: TREE });
2078
+ expect(asksForScreenshot(failed.sent)).toBe(true);
2079
+ const retry = await step({ axTree: TREE, screenshot: "img" });
2080
+ expect(asksForScreenshot(retry.sent)).toBe(true);
2081
+ expect(asksForScreenshot((await step({ axTree: TREE })).sent)).toBe(
2082
+ false,
2083
+ );
2084
+ });
2085
+
2086
+ test("an observation that lands after a reset leaves the new run's state alone", async () => {
2087
+ setup();
2088
+ connect();
2089
+ const stale = dispatch();
2090
+ proxy.reset();
2091
+ proxy.processObservation(stale.sent.requestId, {
2092
+ axTree: "stale tree from the finished run",
2093
+ screenshot: "img",
2094
+ });
2095
+ await stale.pending;
2096
+ expect(proxy.previousAXTree).toBeUndefined();
2097
+ expect(proxy.consecutiveUnchangedSteps).toBe(0);
2098
+ });
2099
+
2100
+ test("an observation that lands after a reset does not restore the first look", async () => {
2101
+ setup();
2102
+ connect();
2103
+ const stale = dispatch();
2104
+ proxy.reset();
2105
+ proxy.processObservation(stale.sent.requestId, {
2106
+ axTree: TREE,
2107
+ screenshot: "img",
2108
+ });
2109
+ await stale.pending;
2110
+ expect(asksForScreenshot((await step({ axTree: TREE })).sent)).toBe(true);
2111
+ });
2112
+
2113
+ test("a skipped step that fails still carries the omitted line after the failure", async () => {
2114
+ setup();
2115
+ connect();
2116
+ await step({ axTree: TREE, screenshot: "img" });
2117
+ const failed = await step({
2118
+ axTree: TREE,
2119
+ executionError: "Element not found",
2120
+ });
2121
+ expect(asksForScreenshot(failed.sent)).toBe(false);
2122
+ expect(failed.result.isError).toBe(true);
2123
+ expect(failed.result.content).toStartWith(
2124
+ "Action failed: Element not found",
2125
+ );
2126
+ expect(failed.result.content).toContain(OMITTED);
2127
+ });
2128
+
2129
+ test("a refused skipped step with a tree carries the omitted line", async () => {
2130
+ setup();
2131
+ connect();
2132
+ await step({ axTree: TREE, screenshot: "img" });
2133
+ const refused = await step({
2134
+ axTree: TREE,
2135
+ executionError: "BLOCKED: the user is active",
2136
+ });
2137
+ expect(asksForScreenshot(refused.sent)).toBe(false);
2138
+ expect(refused.result.isError).toBe(true);
2139
+ expect(refused.result.content).toContain(OMITTED);
2140
+ });
2141
+
2142
+ test("a skipped step with no tree gets no omitted line", async () => {
2143
+ setup();
2144
+ connect();
2145
+ await step({ axTree: TREE, screenshot: "img" });
2146
+ const empty = await step({ executionResult: "Clicked" });
2147
+ expect(asksForScreenshot(empty.sent)).toBe(false);
2148
+ expect(empty.result.content).not.toContain(OMITTED);
2149
+ });
2150
+
2151
+ test("a helper that ignores the flag keeps its screenshot and gets no omitted line", async () => {
2152
+ setup();
2153
+ connect();
2154
+ await step({ axTree: TREE, screenshot: "img" });
2155
+ const legacy = await step({ axTree: TREE, screenshot: "img" });
2156
+ expect(asksForScreenshot(legacy.sent)).toBe(false);
2157
+ expect(legacy.result.content).not.toContain(OMITTED);
2158
+ expect(legacy.result.contentBlocks).toHaveLength(1);
2159
+ });
2160
+
2161
+ test("a point_at response leaves the first-look state untouched", async () => {
2162
+ setup();
2163
+ connect();
2164
+ const input = { marks: [], include_screenshot: true };
2165
+ const { pending, sent } = dispatch(input, "computer_use_point_at");
2166
+ expect(sent.input).toBe(input);
2167
+ proxy.processObservation(sent.requestId, { axTree: TREE });
2168
+ expect((await pending).content).not.toContain(OMITTED);
2169
+ expect(asksForScreenshot((await step({ axTree: TREE })).sent)).toBe(true);
2170
+ });
2171
+
2172
+ test("concurrent requests each keep their own dispatch decision", async () => {
2173
+ setup();
2174
+ connect();
2175
+ // Both are dispatched before any observation, so both ask for pixels,
2176
+ // even though the first one's observation lands before the second's.
2177
+ const first = dispatch();
2178
+ const second = dispatch();
2179
+ expect(asksForScreenshot(first.sent)).toBe(true);
2180
+ expect(asksForScreenshot(second.sent)).toBe(true);
2181
+ proxy.processObservation(first.sent.requestId, {
2182
+ axTree: TREE,
2183
+ screenshot: "img",
2184
+ });
2185
+ const third = dispatch();
2186
+ expect(asksForScreenshot(third.sent)).toBe(false);
2187
+ proxy.processObservation(third.sent.requestId, { axTree: TREE });
2188
+ proxy.processObservation(second.sent.requestId, { axTree: TREE });
2189
+ await first.pending;
2190
+ expect((await second.pending).content).not.toContain(OMITTED);
2191
+ expect((await third.pending).content).toContain(OMITTED);
2192
+ });
2193
+ });
1655
2194
  });
@@ -274,6 +274,29 @@ describe("HostFileProxy — targetClientId (Phase 2)", () => {
274
274
  });
275
275
  });
276
276
 
277
+ test("selects the most recently active same-user client when several are connected", async () => {
278
+ setup();
279
+ setupMultipleClients(["client-1", "client-2"]);
280
+
281
+ const resultPromise = proxy.request(
282
+ { operation: "read", path: "/tmp/file.txt" },
283
+ "session-1",
284
+ undefined,
285
+ undefined,
286
+ TEST_PRINCIPAL,
287
+ );
288
+
289
+ const sent = sentMessages[0] as Record<string, unknown>;
290
+ expect(sent.targetClientId).toBe("client-1");
291
+ const options = sentMessageOptions[0] as
292
+ | Record<string, unknown>
293
+ | undefined;
294
+ expect(options?.targetClientId).toBe("client-1");
295
+
296
+ proxy.resolve(sent.requestId as string, { content: "ok", isError: false });
297
+ expect((await resultPromise).isError).toBe(false);
298
+ });
299
+
277
300
  // ── targetClientId in cancel (abort signal) ──────────────────────────
278
301
 
279
302
  describe("targetClientId in cancel — abort signal", () => {
@@ -746,9 +746,8 @@ describe("HostFileProxy", () => {
746
746
  expect(sentMessages).toHaveLength(0);
747
747
  });
748
748
 
749
- test("untargeted request with no auto-resolve match still broadcasts (legacy path unchanged)", async () => {
749
+ test("rejects when the actor has no capable client", async () => {
750
750
  setup();
751
- // No matching same-user clients available.
752
751
  mockClients = [
753
752
  {
754
753
  clientId: "client-A",
@@ -757,23 +756,18 @@ describe("HostFileProxy", () => {
757
756
  },
758
757
  ];
759
758
 
760
- const resultPromise = proxy.request(
759
+ const result = await proxy.request(
761
760
  { operation: "read", path: "/tmp/test.txt" },
762
761
  "session-1",
763
762
  undefined,
764
763
  undefined,
765
- "user-B", // No same-user match → no auto-resolve, broadcast untargeted.
764
+ "user-B",
766
765
  );
767
766
 
768
- expect(sentMessages).toHaveLength(1);
769
- const sent = sentMessages[0] as Record<string, unknown>;
770
- expect(sent.targetClientId).toBeUndefined();
771
- const requestId = sent.requestId as string;
772
-
773
- proxy.resolve(requestId, { content: "ok", isError: false });
774
- await resultPromise;
767
+ expect(result).toMatchObject({ isError: true });
768
+ expect(result.content).toContain("belongs to this user");
769
+ expect(sentMessages).toHaveLength(0);
775
770
  });
776
-
777
771
  test("auto-resolve picks the same-user client when there's exactly one", async () => {
778
772
  setup();
779
773
  mockClients = [
@@ -807,7 +801,7 @@ describe("HostFileProxy", () => {
807
801
  await resultPromise;
808
802
  });
809
803
 
810
- test("auto-resolve falls through when no client matches the source user", async () => {
804
+ test("rejects when only a different user has a capable client", async () => {
811
805
  setup();
812
806
  mockClients = [
813
807
  {
@@ -817,7 +811,7 @@ describe("HostFileProxy", () => {
817
811
  },
818
812
  ];
819
813
 
820
- const resultPromise = proxy.request(
814
+ const result = await proxy.request(
821
815
  { operation: "read", path: "/tmp/test.txt" },
822
816
  "session-1",
823
817
  undefined,
@@ -825,16 +819,10 @@ describe("HostFileProxy", () => {
825
819
  "user-C",
826
820
  );
827
821
 
828
- // No same-user client → no auto-resolve, broadcast untargeted.
829
- expect(sentMessages).toHaveLength(1);
830
- const sent = sentMessages[0] as Record<string, unknown>;
831
- expect(sent.targetClientId).toBeUndefined();
832
- const requestId = sent.requestId as string;
833
-
834
- proxy.resolve(requestId, { content: "ok", isError: false });
835
- await resultPromise;
822
+ expect(result).toMatchObject({ isError: true });
823
+ expect(result.content).toContain("belongs to this user");
824
+ expect(sentMessages).toHaveLength(0);
836
825
  });
837
-
838
826
  test("legacy embedded targetClientId in input still goes through the same-user gate", async () => {
839
827
  setup();
840
828
  mockClients = [