@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,238 @@
1
+ import { describe, expect, mock, spyOn, test } from "bun:test";
2
+
3
+ import {
4
+ createMockProvider,
5
+ textResponse,
6
+ } from "../../__tests__/helpers/mock-provider.js";
7
+ import { setConfig } from "../../__tests__/helpers/set-config.js";
8
+ import { waitFor } from "../../__tests__/helpers/wait-for.js";
9
+
10
+ setConfig("memory", { enabled: false });
11
+
12
+ import { UserMessageEchoEventSchema } from "../../api/events/user-message-echo.js";
13
+ import type { AssistantEventEnvelope } from "../../api/index.js";
14
+ import { Conversation } from "../../daemon/conversation.js";
15
+ import * as conversationMessaging from "../../daemon/conversation-messaging.js";
16
+ import {
17
+ deleteConversation,
18
+ setConversation,
19
+ } from "../../daemon/conversation-registry.js";
20
+ import { conversationMessagesSyncTag } from "../../daemon/message-types/sync.js";
21
+ import { uploadAttachment } from "../../persistence/attachments-store.js";
22
+ import * as conversationCrud from "../../persistence/conversation-crud.js";
23
+ import { initializeDb } from "../../persistence/db-init.js";
24
+ import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
25
+ import {
26
+ _drainFrameReclaimRechecksForTests,
27
+ _pendingFrameReclaimCountForTests,
28
+ persistAmbientSightFrame,
29
+ persistLiveVoicePhoto,
30
+ } from "../live-voice-photo.js";
31
+ import { LiveVoiceSession } from "../live-voice-session.js";
32
+ import { createLiveVoiceServerFrameSequencer } from "../protocol.js";
33
+
34
+ await initializeDb();
35
+
36
+ function createHarness() {
37
+ const row = conversationCrud.createConversation("Camera frame echo");
38
+ const { provider } = createMockProvider([textResponse("")]);
39
+ const conversation = new Conversation(
40
+ row.id,
41
+ provider,
42
+ "system prompt",
43
+ () => {},
44
+ "/tmp",
45
+ { maxTokens: 4096 },
46
+ );
47
+ conversation.setTrustContext({
48
+ trustClass: "guardian",
49
+ sourceChannel: "vellum",
50
+ });
51
+ setConversation(row.id, conversation);
52
+
53
+ const sequencer = createLiveVoiceServerFrameSequencer();
54
+ const startVoiceTurn = mock(async () => ({
55
+ turnId: "turn-123",
56
+ abort: mock(),
57
+ }));
58
+ const session = new LiveVoiceSession(
59
+ {
60
+ sessionId: "session-123",
61
+ startFrame: {
62
+ type: "start",
63
+ conversationId: row.id,
64
+ audio: { mimeType: "audio/pcm", sampleRate: 24_000, channels: 1 },
65
+ },
66
+ sendFrame: async (payload) => sequencer.next(payload),
67
+ },
68
+ {
69
+ resolveTranscriber: async () => ({
70
+ providerId: "deepgram",
71
+ boundaryId: "daemon-streaming",
72
+ async start() {},
73
+ sendAudio() {},
74
+ stop() {},
75
+ }),
76
+ startVoiceTurn,
77
+ emitMetrics: false,
78
+ },
79
+ );
80
+ const published: {
81
+ event: AssistantEventEnvelope;
82
+ persistedIds: string[];
83
+ persistedSeq: number | null;
84
+ }[] = [];
85
+ const subscription = assistantEventHub.subscribe({
86
+ type: "process",
87
+ filter: { conversationId: row.id },
88
+ callback: (event) => {
89
+ if (
90
+ (event.message.type === "user_message_echo" &&
91
+ event.message.conversationId === row.id) ||
92
+ (event.message.type === "sync_changed" &&
93
+ event.message.tags.includes(conversationMessagesSyncTag(row.id)))
94
+ ) {
95
+ published.push({
96
+ event,
97
+ persistedIds: conversationCrud.getMessages(row.id).map((m) => m.id),
98
+ persistedSeq: conversationCrud.getConversationPersistedSeq(row.id),
99
+ });
100
+ }
101
+ },
102
+ });
103
+
104
+ return {
105
+ conversationId: row.id,
106
+ session,
107
+ startVoiceTurn,
108
+ published,
109
+ async dispose() {
110
+ subscription.dispose();
111
+ await session.close("client_end");
112
+ deleteConversation(row.id);
113
+ conversation.dispose();
114
+ },
115
+ };
116
+ }
117
+
118
+ async function uploadFrame(): Promise<string> {
119
+ const attachment = await uploadAttachment(
120
+ "frame.png",
121
+ "image/png",
122
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk",
123
+ );
124
+ return attachment.id;
125
+ }
126
+
127
+ async function expectAnnouncement(
128
+ harness: ReturnType<typeof createHarness>,
129
+ cameraFrame: boolean,
130
+ ): Promise<void> {
131
+ await waitFor(() => harness.published.length >= 2);
132
+ expect(harness.published.map(({ event }) => event.message.type)).toEqual([
133
+ "user_message_echo",
134
+ "sync_changed",
135
+ ]);
136
+ const [echo, sync] = harness.published;
137
+ const message = UserMessageEchoEventSchema.parse(echo.event.message);
138
+ const rows = conversationCrud.getMessages(harness.conversationId);
139
+ expect(rows).toHaveLength(1);
140
+ expect(message.messageId).toBe(rows[0].id);
141
+ expect(echo.persistedIds).toEqual([rows[0].id]);
142
+ expect(echo.event.seq).toBeNumber();
143
+ expect(sync.persistedSeq).toBe(echo.event.seq!);
144
+ expect(message.text).toBe(cameraFrame ? "(camera frame)" : "here's a photo:");
145
+ if (cameraFrame) {
146
+ expect(message.cameraFrame).toBe(true);
147
+ } else {
148
+ expect(echo.event.message).not.toHaveProperty("cameraFrame");
149
+ }
150
+ expect(harness.startVoiceTurn).not.toHaveBeenCalled();
151
+ }
152
+
153
+ describe("standalone image echoes", () => {
154
+ test.each(["sight_frame", "attach_image"] as const)(
155
+ "%s emits one committed row before the history invalidation",
156
+ async (type) => {
157
+ const harness = createHarness();
158
+ try {
159
+ await harness.session.start();
160
+ const attachmentId = await uploadFrame();
161
+ await harness.session.handleClientFrame({ type, attachmentId });
162
+ await expectAnnouncement(harness, type === "sight_frame");
163
+ } finally {
164
+ await harness.dispose();
165
+ }
166
+ },
167
+ );
168
+
169
+ test.each([
170
+ { kind: "sight_frame", deferred: false },
171
+ { kind: "photo", deferred: false },
172
+ { kind: "sight_frame", deferred: true },
173
+ ] as const)(
174
+ "$kind preserves its echo marker when a committed write throws (deferred: $deferred)",
175
+ async ({ kind, deferred }) => {
176
+ const harness = createHarness();
177
+ const realPersist = conversationMessaging.persistQueuedMessageBody;
178
+ const realGetMessage = conversationCrud.getMessageById;
179
+ let committed = false;
180
+ const persist = spyOn(
181
+ conversationMessaging,
182
+ "persistQueuedMessageBody",
183
+ ).mockImplementation(async (...args) => {
184
+ await realPersist(...args);
185
+ committed = true;
186
+ throw new Error("Post-insert write failed");
187
+ });
188
+ const read = spyOn(conversationCrud, "getMessageById").mockImplementation(
189
+ (...args) => {
190
+ if (deferred && committed) {
191
+ throw new Error("Store temporarily unreadable");
192
+ }
193
+ return realGetMessage(...args);
194
+ },
195
+ );
196
+ try {
197
+ const attachmentId = await uploadFrame();
198
+ const result =
199
+ kind === "sight_frame"
200
+ ? await persistAmbientSightFrame(
201
+ harness.conversationId,
202
+ attachmentId,
203
+ "voice",
204
+ )
205
+ : await persistLiveVoicePhoto(harness.conversationId, attachmentId);
206
+ expect(result.ok).toBe(!deferred);
207
+ if (deferred) {
208
+ expect(harness.published).toHaveLength(0);
209
+ expect(_pendingFrameReclaimCountForTests()).toBe(1);
210
+ _drainFrameReclaimRechecksForTests();
211
+ expect(_pendingFrameReclaimCountForTests()).toBe(1);
212
+ read.mockRestore();
213
+ _drainFrameReclaimRechecksForTests();
214
+ _drainFrameReclaimRechecksForTests();
215
+ expect(_pendingFrameReclaimCountForTests()).toBe(0);
216
+ }
217
+ await expectAnnouncement(harness, kind === "sight_frame");
218
+ } finally {
219
+ persist.mockRestore();
220
+ read.mockRestore();
221
+ _drainFrameReclaimRechecksForTests();
222
+ await harness.dispose();
223
+ }
224
+ },
225
+ );
226
+
227
+ test("the strict echo schema accepts only a true camera marker", () => {
228
+ const echo = { type: "user_message_echo", text: "(camera frame)" };
229
+ expect(
230
+ UserMessageEchoEventSchema.safeParse({ ...echo, cameraFrame: false })
231
+ .success,
232
+ ).toBe(false);
233
+ expect(
234
+ UserMessageEchoEventSchema.safeParse({ ...echo, unexpected: true })
235
+ .success,
236
+ ).toBe(false);
237
+ });
238
+ });
@@ -12,7 +12,7 @@ import {
12
12
  loadRawConfig,
13
13
  saveRawConfig,
14
14
  } from "../../config/loader.js";
15
- import type { LiveVoiceFrontModelConfig } from "../../config/schemas/live-voice.js";
15
+ import type { VoiceFrontModelConfig } from "../../config/schemas/voice.js";
16
16
  import type {
17
17
  StreamingTranscriber,
18
18
  SttStreamServerEvent,
@@ -183,7 +183,7 @@ function createHarness(options: {
183
183
  echoBargeInMargin?: number;
184
184
  echoEmaHalfLifeMs?: number;
185
185
  echoDrainSlackMs?: number;
186
- frontModelConfig?: Partial<LiveVoiceFrontModelConfig>;
186
+ frontModelConfig?: Partial<VoiceFrontModelConfig>;
187
187
  emitMetrics?: boolean;
188
188
  metricsClock?: () => number;
189
189
  // Return a promise to hold a frame's transport write open (a backed-up
@@ -92,36 +92,17 @@ mock.module("../live-voice-credential-preflight.js", () => ({
92
92
  : realPreflightModule.resolveLiveVoiceCredentialReadiness(),
93
93
  }));
94
94
 
95
- import { CURRENT_POLICY_EPOCH } from "../../runtime/auth/policy.js";
96
- import { mintToken } from "../../runtime/auth/token-service.js";
95
+ import {
96
+ mintActorToken,
97
+ mintGatewayToken,
98
+ requireHttpAuth,
99
+ upgradeHeaders,
100
+ waitForClose,
101
+ } from "../../runtime/__tests__/runtime-ws-test-utils.js";
97
102
  import { RuntimeHttpServer } from "../../runtime/http-server.js";
98
103
 
99
104
  type JsonFrame = Record<string, unknown>;
100
105
 
101
- const savedAuthEnv = {
102
- DISABLE_HTTP_AUTH: process.env.DISABLE_HTTP_AUTH,
103
- };
104
-
105
- function mintGatewayToken(): string {
106
- return mintToken({
107
- aud: "vellum-daemon",
108
- sub: "svc:gateway:self",
109
- scope_profile: "gateway_ingress_v1",
110
- policy_epoch: CURRENT_POLICY_EPOCH,
111
- ttlSeconds: 3600,
112
- });
113
- }
114
-
115
- function mintActorToken(): string {
116
- return mintToken({
117
- aud: "vellum-daemon",
118
- sub: "actor:self:user-123",
119
- scope_profile: "actor_client_v1",
120
- policy_epoch: CURRENT_POLICY_EPOCH,
121
- ttlSeconds: 3600,
122
- });
123
- }
124
-
125
106
  function startFrame(conversationId = "conversation-123"): string {
126
107
  return JSON.stringify({
127
108
  type: "start",
@@ -161,33 +142,6 @@ async function waitForOpen(ws: WebSocket, timeoutMs = 2000): Promise<void> {
161
142
  });
162
143
  }
163
144
 
164
- async function waitForClose(ws: WebSocket, timeoutMs = 2000): Promise<void> {
165
- if (ws.readyState === WebSocket.CLOSED) {
166
- return;
167
- }
168
- await new Promise<void>((resolve, reject) => {
169
- const timer = setTimeout(() => {
170
- cleanup();
171
- reject(new Error("Timed out waiting for WebSocket close"));
172
- }, timeoutMs);
173
- const cleanup = () => {
174
- clearTimeout(timer);
175
- ws.removeEventListener("close", onClose);
176
- ws.removeEventListener("error", onError);
177
- };
178
- const onClose = () => {
179
- cleanup();
180
- resolve();
181
- };
182
- const onError = () => {
183
- cleanup();
184
- reject(new Error("WebSocket close failed"));
185
- };
186
- ws.addEventListener("close", onClose);
187
- ws.addEventListener("error", onError);
188
- });
189
- }
190
-
191
145
  async function waitForJsonFrame(
192
146
  ws: WebSocket,
193
147
  timeoutMs = 2000,
@@ -241,6 +195,7 @@ describe("RuntimeHttpServer live voice WebSocket shell", () => {
241
195
  let baseUrl: string;
242
196
  let wsBaseUrl: string;
243
197
  let clients: WebSocket[];
198
+ let restoreAuthEnv: () => void;
244
199
 
245
200
  beforeAll(() => {
246
201
  shellMocksActive = true;
@@ -251,7 +206,7 @@ describe("RuntimeHttpServer live voice WebSocket shell", () => {
251
206
  });
252
207
 
253
208
  beforeEach(async () => {
254
- delete process.env.DISABLE_HTTP_AUTH;
209
+ restoreAuthEnv = requireHttpAuth();
255
210
  resolveStreamingTranscriberImpl = async () => createResolvedTranscriber();
256
211
  resolveStreamingTranscriberMock.mockClear();
257
212
  resolvedTranscribers.length = 0;
@@ -268,11 +223,7 @@ describe("RuntimeHttpServer live voice WebSocket shell", () => {
268
223
  closeClient(client);
269
224
  }
270
225
  await server.stop();
271
- if (savedAuthEnv.DISABLE_HTTP_AUTH === undefined) {
272
- delete process.env.DISABLE_HTTP_AUTH;
273
- } else {
274
- process.env.DISABLE_HTTP_AUTH = savedAuthEnv.DISABLE_HTTP_AUTH;
275
- }
226
+ restoreAuthEnv();
276
227
  });
277
228
 
278
229
  function openLiveVoiceClient(token = mintGatewayToken()): WebSocket {
@@ -284,21 +235,14 @@ describe("RuntimeHttpServer live voice WebSocket shell", () => {
284
235
  }
285
236
 
286
237
  test("rejects unauthorized upgrades before creating a WebSocket", async () => {
287
- const baseHeaders = {
288
- Upgrade: "websocket",
289
- Connection: "Upgrade",
290
- "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
291
- "Sec-WebSocket-Version": "13",
292
- };
293
-
294
238
  const missingToken = await fetch(`${baseUrl}/v1/live-voice`, {
295
- headers: baseHeaders,
239
+ headers: upgradeHeaders,
296
240
  });
297
241
  expect(missingToken.status).toBe(401);
298
242
 
299
243
  const actorToken = await fetch(
300
244
  `${baseUrl}/v1/live-voice?token=${mintActorToken()}`,
301
- { headers: baseHeaders },
245
+ { headers: upgradeHeaders },
302
246
  );
303
247
  expect(actorToken.status).toBe(401);
304
248
 
@@ -306,7 +250,7 @@ describe("RuntimeHttpServer live voice WebSocket shell", () => {
306
250
  `${baseUrl}/v1/live-voice?token=${mintGatewayToken()}`,
307
251
  {
308
252
  headers: {
309
- ...baseHeaders,
253
+ ...upgradeHeaders,
310
254
  Origin: "https://external.example.com",
311
255
  },
312
256
  },
@@ -237,6 +237,7 @@ async function enqueueStandaloneImagePersist(
237
237
  [ticket.attachmentId],
238
238
  ticket.content,
239
239
  uuidv7(),
240
+ kind,
240
241
  );
241
242
  return { ok: false };
242
243
  }
@@ -389,11 +390,18 @@ function reclaimOrDefer(
389
390
  attachmentIds: readonly string[],
390
391
  content: string,
391
392
  messageId: string,
393
+ kind: "photo" | "sight_frame",
392
394
  ): void {
393
395
  if (reclaimDroppedFrame(attachmentIds)) {
394
396
  return;
395
397
  }
396
- deferFrameReclaimDecision(conversationId, messageId, attachmentIds, content);
398
+ deferFrameReclaimDecision(
399
+ conversationId,
400
+ messageId,
401
+ attachmentIds,
402
+ content,
403
+ kind,
404
+ );
397
405
  }
398
406
 
399
407
  /** Conversations with a standalone-image persist still in flight. */
@@ -429,6 +437,8 @@ interface PendingFrameReclaim {
429
437
  attachmentIds: readonly string[];
430
438
  /** Row text, so a frame found to have landed can still be announced. */
431
439
  content: string;
440
+ /** The echo shape used if a later recheck finds the row. */
441
+ kind: "photo" | "sight_frame";
432
442
  attempts: number;
433
443
  }
434
444
 
@@ -465,12 +475,14 @@ function deferFrameReclaimDecision(
465
475
  messageId: string,
466
476
  attachmentIds: readonly string[],
467
477
  content: string,
478
+ kind: "photo" | "sight_frame",
468
479
  ): void {
469
480
  pendingFrameReclaims.push({
470
481
  conversationId,
471
482
  messageId,
472
483
  attachmentIds,
473
484
  content,
485
+ kind,
474
486
  attempts: 0,
475
487
  });
476
488
  scheduleFrameReclaimRecheck();
@@ -521,6 +533,7 @@ function announceDeferredImage(pending: PendingFrameReclaim): void {
521
533
  pending.conversationId,
522
534
  pending.content,
523
535
  pending.messageId,
536
+ pending.kind,
524
537
  );
525
538
  } catch (err) {
526
539
  log.warn(
@@ -700,6 +713,7 @@ function persistStandaloneImage(
700
713
  uuidv7(),
701
714
  [attachmentId],
702
715
  persistOptions.content,
716
+ kind,
703
717
  );
704
718
  }
705
719
  return Promise.resolve({ ok: false });
@@ -715,6 +729,7 @@ function persistStandaloneImage(
715
729
  [attachmentId],
716
730
  persistOptions.content,
717
731
  uuidv7(),
732
+ kind,
718
733
  );
719
734
  }
720
735
  return Promise.resolve({ ok: false });
@@ -754,7 +769,7 @@ function dropReplacedImage(
754
769
  "Standalone image dropped: its conversation was replaced before the write",
755
770
  );
756
771
  if (kind === "sight_frame") {
757
- reclaimOrDefer(conversationId, [attachmentId], content, uuidv7());
772
+ reclaimOrDefer(conversationId, [attachmentId], content, uuidv7(), kind);
758
773
  }
759
774
  return { ok: false };
760
775
  }
@@ -806,7 +821,7 @@ async function writeStandaloneImage(
806
821
  "Standalone image dropped: its conversation was deleted while it waited",
807
822
  );
808
823
  if (kind === "sight_frame") {
809
- reclaimOrDefer(conversationId, [attachmentId], content, uuidv7());
824
+ reclaimOrDefer(conversationId, [attachmentId], content, uuidv7(), kind);
810
825
  }
811
826
  return { ok: false };
812
827
  }
@@ -831,7 +846,7 @@ async function writeStandaloneImage(
831
846
  "Standalone image timed out waiting for the conversation to go idle",
832
847
  );
833
848
  if (kind === "sight_frame") {
834
- reclaimOrDefer(conversationId, [attachmentId], content, uuidv7());
849
+ reclaimOrDefer(conversationId, [attachmentId], content, uuidv7(), kind);
835
850
  }
836
851
  return { ok: false };
837
852
  }
@@ -882,7 +897,7 @@ async function writeStandaloneImage(
882
897
  // of.
883
898
  conversation.markHistoryStaleForForeignScope(persistOptions.trustContext);
884
899
 
885
- announcePersistedImage(conversationId, content, persisted.id);
900
+ announcePersistedImage(conversationId, content, persisted.id, kind);
886
901
 
887
902
  return {
888
903
  ok: true,
@@ -930,7 +945,7 @@ async function writeStandaloneImage(
930
945
  // `ensureActorScopedHistory` reloads instead of reusing what it holds.
931
946
  findConversation(conversationId)?.markHistoryStale();
932
947
  try {
933
- announcePersistedImage(conversationId, content, requestId);
948
+ announcePersistedImage(conversationId, content, requestId, kind);
934
949
  } catch (announceErr) {
935
950
  log.warn(
936
951
  { err: announceErr, conversationId, messageId: requestId },
@@ -946,6 +961,7 @@ async function writeStandaloneImage(
946
961
  [attachmentId, ...strandedClones],
947
962
  content,
948
963
  requestId,
964
+ kind,
949
965
  );
950
966
  } else {
951
967
  // The store would not say whether the row landed. The refusal below
@@ -957,6 +973,7 @@ async function writeStandaloneImage(
957
973
  requestId,
958
974
  [attachmentId, ...strandedClones],
959
975
  content,
976
+ kind,
960
977
  );
961
978
  }
962
979
  }
@@ -974,12 +991,14 @@ function announcePersistedImage(
974
991
  conversationId: string,
975
992
  text: string,
976
993
  messageId: string,
994
+ kind: "photo" | "sight_frame",
977
995
  ): void {
978
996
  broadcastMessage({
979
997
  type: "user_message_echo",
980
998
  text,
981
999
  conversationId,
982
1000
  messageId,
1001
+ ...(kind === "sight_frame" ? { cameraFrame: true as const } : {}),
983
1002
  });
984
1003
  recordConversationPersistedSeq(conversationId, getCurrentSeq());
985
1004
  publishConversationMessagesChanged(conversationId);