@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
@@ -0,0 +1,108 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ DEFAULT_LOCALE,
5
+ isMessageKey,
6
+ localeFromAcceptLanguage,
7
+ MESSAGE_CATALOGS,
8
+ MESSAGE_KEYS,
9
+ negotiateLocale,
10
+ resolveConversationTitle,
11
+ SUPPORTED_LOCALES,
12
+ t,
13
+ } from "../index.js";
14
+
15
+ describe("negotiateLocale", () => {
16
+ test("matches a full shipped tag", () => {
17
+ expect(negotiateLocale(["es"])).toBe("es");
18
+ expect(negotiateLocale(["zh-TW"])).toBe("zh-TW");
19
+ });
20
+
21
+ test("falls back from a regional tag to the primary language", () => {
22
+ expect(negotiateLocale(["es-MX", "en"])).toBe("es");
23
+ });
24
+
25
+ test("maps Traditional Chinese tags to zh-TW", () => {
26
+ expect(negotiateLocale(["zh-Hant"])).toBe("zh-TW");
27
+ expect(negotiateLocale(["zh-HK"])).toBe("zh-TW");
28
+ });
29
+
30
+ test("defaults when nothing matches", () => {
31
+ expect(negotiateLocale([])).toBe(DEFAULT_LOCALE);
32
+ expect(negotiateLocale(["xx"])).toBe(DEFAULT_LOCALE);
33
+ });
34
+ });
35
+
36
+ describe("localeFromAcceptLanguage", () => {
37
+ test("honors quality values", () => {
38
+ expect(localeFromAcceptLanguage("fr;q=0.8, es;q=0.9, en;q=0.5")).toBe(
39
+ "es",
40
+ );
41
+ });
42
+
43
+ test("defaults on missing or empty", () => {
44
+ expect(localeFromAcceptLanguage(null)).toBe(DEFAULT_LOCALE);
45
+ expect(localeFromAcceptLanguage("")).toBe(DEFAULT_LOCALE);
46
+ });
47
+ });
48
+
49
+ describe("catalog completeness", () => {
50
+ test("every locale has every key", () => {
51
+ const keys = Object.values(MESSAGE_KEYS);
52
+ for (const locale of SUPPORTED_LOCALES) {
53
+ for (const key of keys) {
54
+ expect(MESSAGE_CATALOGS[locale][key].length).toBeGreaterThan(0);
55
+ }
56
+ }
57
+ });
58
+ });
59
+
60
+ describe("t", () => {
61
+ test("resolves generating and untitled per locale", () => {
62
+ expect(t(MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING, "en")).toBe(
63
+ "Generating title...",
64
+ );
65
+ expect(t(MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED, "en")).toBe(
66
+ "Untitled Conversation",
67
+ );
68
+ expect(t(MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED, "es")).toBe(
69
+ "Sin título",
70
+ );
71
+ });
72
+ });
73
+
74
+ describe("resolveConversationTitle", () => {
75
+ test("resolves a stored key and an empty title", () => {
76
+ expect(
77
+ resolveConversationTitle(MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING, "zh"),
78
+ ).toBe("标题生成中...");
79
+ expect(resolveConversationTitle(null, "es")).toBe("Sin título");
80
+ expect(resolveConversationTitle("")).toBe("Untitled Conversation");
81
+ });
82
+
83
+ test("passes stored display strings through, including English placeholders", () => {
84
+ expect(resolveConversationTitle("Untitled Conversation", "es")).toBe(
85
+ "Untitled Conversation",
86
+ );
87
+ expect(resolveConversationTitle("Generating title...", "zh")).toBe(
88
+ "Generating title...",
89
+ );
90
+ expect(resolveConversationTitle("Auth Middleware Rewrite", "es")).toBe(
91
+ "Auth Middleware Rewrite",
92
+ );
93
+ });
94
+
95
+ test("defaults locale inside the helper", () => {
96
+ expect(
97
+ resolveConversationTitle(MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING),
98
+ ).toBe("Generating title...");
99
+ });
100
+ });
101
+
102
+ describe("isMessageKey", () => {
103
+ test("recognizes catalog keys only", () => {
104
+ expect(isMessageKey(MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED)).toBe(true);
105
+ expect(isMessageKey("Untitled Conversation")).toBe(false);
106
+ expect(isMessageKey("Untitled")).toBe(false);
107
+ });
108
+ });
@@ -0,0 +1,16 @@
1
+ export {
2
+ DEFAULT_LOCALE,
3
+ isSupportedLocale,
4
+ localeFromAcceptLanguage,
5
+ negotiateLocale,
6
+ SUPPORTED_LOCALES,
7
+ type SupportedLocale,
8
+ } from "./locales.js";
9
+ export {
10
+ isMessageKey,
11
+ MESSAGE_CATALOGS,
12
+ MESSAGE_KEYS,
13
+ type MessageKey,
14
+ t,
15
+ } from "./messages.js";
16
+ export { resolveConversationTitle } from "./title.js";
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Locales the daemon can resolve user-facing copy into.
3
+ *
4
+ * Keep this set aligned with `clients/web/src/i18n/supported-locales.ts`.
5
+ * A locale that exists only here cannot be requested by the web app; a
6
+ * locale that exists only there cannot be resolved for CLI or HTTP
7
+ * consumers that go through this module.
8
+ */
9
+
10
+ export const DEFAULT_LOCALE = "en";
11
+
12
+ export const SUPPORTED_LOCALES = ["en", "es", "ru", "zh", "zh-TW"] as const;
13
+
14
+ export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
15
+
16
+ export function isSupportedLocale(value: unknown): value is SupportedLocale {
17
+ return (
18
+ typeof value === "string" &&
19
+ (SUPPORTED_LOCALES as readonly string[]).includes(value)
20
+ );
21
+ }
22
+
23
+ /**
24
+ * Pick the best shipped locale for an ordered list of preferred BCP 47 tags.
25
+ *
26
+ * Full tag first, then the primary language subtag, so `es-MX` resolves to
27
+ * `es`. `zh-Hant` / `zh-HK` / `zh-MO` map to `zh-TW`.
28
+ */
29
+ export function negotiateLocale(preferred: readonly string[]): SupportedLocale {
30
+ for (const tag of preferred) {
31
+ const normalized = tag.trim();
32
+ if (normalized === "") {
33
+ continue;
34
+ }
35
+ const exact = SUPPORTED_LOCALES.find(
36
+ (locale) => locale.toLowerCase() === normalized.toLowerCase(),
37
+ );
38
+ if (exact) {
39
+ return exact;
40
+ }
41
+ const lower = normalized.toLowerCase();
42
+ if (
43
+ lower === "zh-hant" ||
44
+ lower.startsWith("zh-hant-") ||
45
+ lower === "zh-hk" ||
46
+ lower === "zh-mo"
47
+ ) {
48
+ return "zh-TW";
49
+ }
50
+ const base = normalized.split("-")[0]?.toLowerCase();
51
+ const baseMatched = SUPPORTED_LOCALES.find(
52
+ (locale) => locale.toLowerCase() === base,
53
+ );
54
+ if (baseMatched) {
55
+ return baseMatched;
56
+ }
57
+ }
58
+ return DEFAULT_LOCALE;
59
+ }
60
+
61
+ /**
62
+ * Parse an `Accept-Language` header into preference order and negotiate.
63
+ *
64
+ * Quality values are honored; the header is a hint, not a required input.
65
+ * Missing or empty headers resolve to {@link DEFAULT_LOCALE}.
66
+ */
67
+ export function localeFromAcceptLanguage(
68
+ header: string | null | undefined,
69
+ ): SupportedLocale {
70
+ if (header == null || header.trim() === "") {
71
+ return DEFAULT_LOCALE;
72
+ }
73
+ const tags = header
74
+ .split(",")
75
+ .map((part) => {
76
+ const [tag, ...params] = part.trim().split(";");
77
+ let quality = 1;
78
+ for (const param of params) {
79
+ const [name, value] = param.trim().split("=");
80
+ if (name === "q" && value !== undefined) {
81
+ const parsed = Number.parseFloat(value);
82
+ if (Number.isFinite(parsed)) {
83
+ quality = parsed;
84
+ }
85
+ }
86
+ }
87
+ return { tag: tag?.trim() ?? "", quality };
88
+ })
89
+ .filter((entry) => entry.tag.length > 0)
90
+ .sort((a, b) => b.quality - a.quality)
91
+ .map((entry) => entry.tag);
92
+ return negotiateLocale(tags);
93
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Daemon message catalog.
3
+ *
4
+ * Keys are the stable identity of a hardcoded constant. English is the
5
+ * source language. Add a key here, then add the same key to every locale
6
+ * catalog. A key present in a translated catalog but missing from English
7
+ * is a test failure.
8
+ *
9
+ * Persist the key when a column holds a system constant. Never persist a
10
+ * translated string, and never treat a stored display string as a key:
11
+ * a user title may be in any language.
12
+ */
13
+
14
+ import {
15
+ DEFAULT_LOCALE,
16
+ type SupportedLocale,
17
+ } from "./locales.js";
18
+
19
+ export const MESSAGE_KEYS = {
20
+ CONVERSATION_TITLE_GENERATING: "conversation.title.generating",
21
+ CONVERSATION_TITLE_UNTITLED: "conversation.title.untitled",
22
+ } as const;
23
+
24
+ export type MessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];
25
+
26
+ export const MESSAGE_CATALOGS: Record<
27
+ SupportedLocale,
28
+ Record<MessageKey, string>
29
+ > = {
30
+ en: {
31
+ "conversation.title.generating": "Generating title...",
32
+ "conversation.title.untitled": "Untitled Conversation",
33
+ },
34
+ es: {
35
+ "conversation.title.generating": "Generando título...",
36
+ "conversation.title.untitled": "Sin título",
37
+ },
38
+ ru: {
39
+ "conversation.title.generating": "Создание названия...",
40
+ "conversation.title.untitled": "Без названия",
41
+ },
42
+ zh: {
43
+ "conversation.title.generating": "标题生成中...",
44
+ "conversation.title.untitled": "无标题",
45
+ },
46
+ "zh-TW": {
47
+ "conversation.title.generating": "標題產生中...",
48
+ "conversation.title.untitled": "未命名",
49
+ },
50
+ };
51
+
52
+ export function t(
53
+ key: MessageKey,
54
+ locale: SupportedLocale = DEFAULT_LOCALE,
55
+ ): string {
56
+ return MESSAGE_CATALOGS[locale][key] ?? MESSAGE_CATALOGS[DEFAULT_LOCALE][key];
57
+ }
58
+
59
+ export function isMessageKey(value: string): value is MessageKey {
60
+ return (Object.values(MESSAGE_KEYS) as string[]).includes(value);
61
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Resolve a conversation title for a locale.
3
+ *
4
+ * A stored message key (or an empty title) is a hardcoded constant and
5
+ * resolves through the catalog. Any other stored string is user or model
6
+ * copy and passes through, including English placeholders written before
7
+ * keys were persisted.
8
+ */
9
+
10
+ import { DEFAULT_LOCALE, type SupportedLocale } from "./locales.js";
11
+ import { isMessageKey, MESSAGE_KEYS, t } from "./messages.js";
12
+
13
+ export function resolveConversationTitle(
14
+ title: string | null | undefined,
15
+ locale: SupportedLocale = DEFAULT_LOCALE,
16
+ ): string {
17
+ const trimmed = title?.trim() ?? "";
18
+ if (trimmed === "") {
19
+ return t(MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED, locale);
20
+ }
21
+ if (isMessageKey(trimmed)) {
22
+ return t(trimmed, locale);
23
+ }
24
+ return trimmed;
25
+ }
@@ -18,9 +18,10 @@
18
18
  * the platform can register a route that points at this instance.
19
19
  */
20
20
 
21
- import { getPlatformAssistantId, getPlatformBaseUrl } from "../config/env.js";
21
+ import { getPlatformBaseUrl } from "../config/env.js";
22
22
  import { getIsPlatform } from "../config/env-registry.js";
23
23
  import { getConfig } from "../config/loader.js";
24
+ import { resolvePlatformAssistantId } from "../config/platform-identity.js";
24
25
  import { ipcRegisterWebhookRoute } from "../ipc/gateway-client.js";
25
26
  import { credentialKey } from "../security/credential-key.js";
26
27
  import { getSecureKeyAsync } from "../security/secure-keys.js";
@@ -45,11 +46,11 @@ export interface PlatformCallbackRegistrationContext {
45
46
 
46
47
  export async function resolvePlatformCallbackRegistrationContext(): Promise<PlatformCallbackRegistrationContext> {
47
48
  const platform = getIsPlatform();
48
- const [storedBaseUrlRaw, storedAssistantIdRaw, storedAssistantApiKeyRaw] =
49
+ const [storedBaseUrlRaw, storedAssistantApiKeyRaw, assistantId] =
49
50
  await Promise.all([
50
51
  getSecureKeyAsync(credentialKey("vellum", "platform_base_url")),
51
- getSecureKeyAsync(credentialKey("vellum", "platform_assistant_id")),
52
52
  getSecureKeyAsync(credentialKey("vellum", "assistant_api_key")),
53
+ resolvePlatformAssistantId(),
53
54
  ]);
54
55
 
55
56
  const storedBaseUrl = storedBaseUrlRaw?.trim();
@@ -57,8 +58,6 @@ export async function resolvePlatformCallbackRegistrationContext(): Promise<Plat
57
58
  /\/+$/,
58
59
  "",
59
60
  );
60
- const assistantId =
61
- getPlatformAssistantId().trim() || storedAssistantIdRaw?.trim() || "";
62
61
  const envAssistantCredential = process.env.ASSISTANT_API_KEY?.trim();
63
62
  const assistantCredential =
64
63
  storedAssistantApiKeyRaw?.trim() || envAssistantCredential || undefined;
@@ -329,7 +329,6 @@ describe("LiveVoiceMetricsCollector", () => {
329
329
  endpointHoldCount: 0,
330
330
  endpointDecisionMaxLatencyMs: 95,
331
331
  });
332
- expect(completed).not.toHaveProperty("ackSpoken");
333
332
  });
334
333
 
335
334
  test("decisions without a source are attributed to the front door", () => {
@@ -435,7 +434,6 @@ describe("LiveVoiceMetricsCollector", () => {
435
434
  expect(completed).not.toHaveProperty("endpointHoldCount");
436
435
  expect(completed).not.toHaveProperty("endpointDecisionMaxLatencyMs");
437
436
  expect(completed).not.toHaveProperty("endpointDecisionSource");
438
- expect(completed).not.toHaveProperty("ackSpoken");
439
437
  expect(completed).not.toHaveProperty("progressUpdatesSpoken");
440
438
 
441
439
  const aggregateFields = getLiveVoiceMetricsAggregateFields(
@@ -446,7 +444,6 @@ describe("LiveVoiceMetricsCollector", () => {
446
444
  expect(aggregateFields).not.toHaveProperty("endpointHoldCount");
447
445
  expect(aggregateFields).not.toHaveProperty("endpointDecisionMaxLatencyMs");
448
446
  expect(aggregateFields).not.toHaveProperty("endpointDecisionSource");
449
- expect(aggregateFields).not.toHaveProperty("ackSpoken");
450
447
  expect(aggregateFields).not.toHaveProperty("progressUpdatesSpoken");
451
448
  });
452
449
 
@@ -1,5 +1,14 @@
1
1
  import { describe, expect, mock, test } from "bun:test";
2
2
 
3
+ import type {
4
+ VoiceProgressNarrator,
5
+ VoiceProgressTextInput,
6
+ } from "../../calls/progress-narration.js";
7
+ import {
8
+ pickProgressPhrase,
9
+ PROGRESS_FALLBACK_PHRASES,
10
+ PROGRESS_FALLBACK_PHRASES_BY_LANGUAGE,
11
+ } from "../../calls/progress-phrases.js";
3
12
  import { sanitizeForTts } from "../../calls/tts-text-sanitizer.js";
4
13
  import type {
5
14
  VoiceTurnCallbacks,
@@ -7,9 +16,9 @@ import type {
7
16
  } from "../../calls/voice-session-bridge.js";
8
17
  import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
9
18
  import type {
10
- LiveVoiceFrontModelConfig,
11
- LiveVoiceProgressConfig,
12
- } from "../../config/schemas/live-voice.js";
19
+ VoiceFrontModelConfig,
20
+ VoiceProgressConfig,
21
+ } from "../../config/schemas/voice.js";
13
22
  import type {
14
23
  StreamingTranscriber,
15
24
  SttStreamServerEvent,
@@ -21,15 +30,6 @@ import {
21
30
  } from "../live-voice-session.js";
22
31
  import type { LiveVoiceSessionFactoryContext } from "../live-voice-session-manager.js";
23
32
  import type { LiveVoiceTtsOptions } from "../live-voice-tts.js";
24
- import type {
25
- VoiceProgressNarrator,
26
- VoiceProgressTextInput,
27
- } from "../progress-narration.js";
28
- import {
29
- pickProgressPhrase,
30
- PROGRESS_FALLBACK_PHRASES,
31
- PROGRESS_FALLBACK_PHRASES_BY_LANGUAGE,
32
- } from "../progress-phrases.js";
33
33
  import {
34
34
  createLiveVoiceServerFrameSequencer,
35
35
  type LiveVoiceClientStartFrame,
@@ -142,8 +142,8 @@ function makeProgressNarrator(
142
142
  }
143
143
 
144
144
  function progressConfig(
145
- overrides: Partial<LiveVoiceProgressConfig> = {},
146
- ): Partial<LiveVoiceFrontModelConfig> {
145
+ overrides: Partial<VoiceProgressConfig> = {},
146
+ ): Partial<VoiceFrontModelConfig> {
147
147
  const idleIntervalMs = overrides.idleIntervalMs ?? 60_000;
148
148
  return {
149
149
  progress: {
@@ -163,7 +163,7 @@ function progressConfig(
163
163
  }
164
164
 
165
165
  function createProgressHarness(options: {
166
- frontModelConfig: Partial<LiveVoiceFrontModelConfig>;
166
+ frontModelConfig: Partial<VoiceFrontModelConfig>;
167
167
  progressNarrator: VoiceProgressNarrator;
168
168
  emitMetrics?: boolean;
169
169
  gateTtsText?: (text: string) => Promise<void> | null;
@@ -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