@vellumai/assistant 0.12.0 → 0.12.1-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 (406) hide show
  1. package/AGENTS.md +5 -1
  2. package/ARCHITECTURE.md +11 -12
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/openapi.yaml +292 -2
  5. package/package.json +1 -1
  6. package/scripts/smoke-desktop-window-theme.ts +186 -0
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  8. package/src/__tests__/app-compiler.test.ts +6 -0
  9. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  10. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  11. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  12. package/src/__tests__/computer-use-tools.test.ts +17 -5
  13. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  14. package/src/__tests__/config-schema.test.ts +28 -29
  15. package/src/__tests__/config-watcher.test.ts +23 -8
  16. package/src/__tests__/connection-model-compat.test.ts +2 -2
  17. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  18. package/src/__tests__/conversation-pairing.test.ts +26 -0
  19. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  20. package/src/__tests__/conversation-queue.test.ts +48 -207
  21. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  22. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  23. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  24. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  25. package/src/__tests__/conversation-title-service.test.ts +14 -10
  26. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  27. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  28. package/src/__tests__/credential-record-retire.test.ts +171 -1
  29. package/src/__tests__/credential-routes.test.ts +212 -3
  30. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  31. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  32. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  33. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  34. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  35. package/src/__tests__/git-porcelain.test.ts +47 -0
  36. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  37. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  38. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  39. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  40. package/src/__tests__/host-file-proxy.test.ts +11 -23
  41. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  42. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  43. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  44. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  45. package/src/__tests__/mcp-health-check.test.ts +8 -11
  46. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  47. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  48. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  49. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  50. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  51. package/src/__tests__/model-intents.test.ts +3 -0
  52. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  53. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  54. package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
  55. package/src/__tests__/resolve-app.test.ts +135 -0
  56. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  57. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  58. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  59. package/src/__tests__/source-fingerprint.test.ts +184 -0
  60. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  61. package/src/__tests__/subagent-tools.test.ts +3 -2
  62. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  63. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  64. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  65. package/src/__tests__/workspace-git-service.test.ts +26 -0
  66. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  67. package/src/acp/__tests__/agent-process.test.ts +258 -4
  68. package/src/acp/__tests__/auth-required.test.ts +96 -0
  69. package/src/acp/__tests__/client-handler.test.ts +90 -2
  70. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  71. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  72. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  73. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  74. package/src/acp/__tests__/session-manager.test.ts +895 -4
  75. package/src/acp/agent-process.test.ts +4 -1
  76. package/src/acp/agent-process.ts +94 -9
  77. package/src/acp/auth-required.ts +50 -1
  78. package/src/acp/auto-install.test.ts +9 -7
  79. package/src/acp/client-handler.ts +16 -3
  80. package/src/acp/model-config.test.ts +166 -0
  81. package/src/acp/model-config.ts +138 -0
  82. package/src/acp/resolve-agent.test.ts +220 -6
  83. package/src/acp/resolve-agent.ts +88 -23
  84. package/src/acp/session-manager.test.ts +196 -3
  85. package/src/acp/session-manager.ts +441 -18
  86. package/src/acp/types.ts +50 -5
  87. package/src/api/events/acp-session-model-update.test.ts +88 -0
  88. package/src/api/events/acp-session-model-update.ts +46 -0
  89. package/src/api/events/app-files-changed.ts +4 -3
  90. package/src/api/index.ts +6 -0
  91. package/src/apps/app-store.ts +1 -1
  92. package/src/apps/resolve-app.ts +81 -0
  93. package/src/apps/source-fingerprint.ts +266 -0
  94. package/src/bundler/app-compiler.ts +15 -0
  95. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  96. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  97. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  98. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  99. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  100. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  101. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  102. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  103. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  104. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  105. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  106. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  107. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  108. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  109. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  110. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  111. package/src/calls/barge-in-guard.ts +118 -0
  112. package/src/calls/call-constants.ts +24 -4
  113. package/src/calls/call-controller.ts +499 -199
  114. package/src/calls/call-transport.ts +16 -0
  115. package/src/calls/media-stream-output.ts +32 -0
  116. package/src/calls/media-stream-server.ts +82 -30
  117. package/src/calls/media-stream-stt-session.ts +15 -0
  118. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  119. package/src/calls/resolve-call-tts-provider.ts +33 -2
  120. package/src/calls/tts-call-strategy.ts +9 -5
  121. package/src/calls/types.ts +2 -0
  122. package/src/calls/voice-control-protocol.ts +53 -21
  123. package/src/calls/voice-leg-coordinator.ts +189 -0
  124. package/src/calls/voice-progress-cadence.ts +423 -0
  125. package/src/calls/voice-session-bridge.ts +59 -46
  126. package/src/calls/voice-triage-escalate.ts +175 -56
  127. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  128. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  129. package/src/cli/commands/apps.help.ts +67 -13
  130. package/src/cli/commands/apps.ts +183 -0
  131. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  132. package/src/cli/commands/channels/index.help.ts +70 -2
  133. package/src/cli/commands/channels/index.ts +16 -3
  134. package/src/cli/commands/channels/send.ts +128 -0
  135. package/src/cli/commands/credentials.help.ts +6 -2
  136. package/src/cli/commands/credentials.ts +2 -2
  137. package/src/cli/commands/mcp.help.ts +8 -8
  138. package/src/cli/commands/mcp.ts +1 -1
  139. package/src/cli/commands/roadmap.help.ts +5 -1
  140. package/src/cli/commands/roadmap.ts +1 -1
  141. package/src/cli/lib/bundled-marketplace.json +103 -90
  142. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  143. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  144. package/src/config/__tests__/platform-identity.test.ts +117 -0
  145. package/src/config/__tests__/platform-rehydration.test.ts +93 -0
  146. package/src/config/acp-agent-write.ts +43 -0
  147. package/src/config/acp-defaults.test.ts +13 -3
  148. package/src/config/acp-defaults.ts +23 -6
  149. package/src/config/acp-schema.test.ts +81 -0
  150. package/src/config/acp-schema.ts +30 -2
  151. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  152. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  153. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  154. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  155. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  156. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  157. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  158. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  159. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  160. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  161. package/src/config/default-profile-catalog.ts +1 -1
  162. package/src/config/env.ts +2 -2
  163. package/src/config/feature-flag-registry.json +8 -12
  164. package/src/config/memory-v3-gate.test.ts +31 -0
  165. package/src/config/memory-v3-gate.ts +20 -16
  166. package/src/config/platform-identity.ts +144 -0
  167. package/src/config/platform-rehydration.ts +75 -21
  168. package/src/config/schema.ts +2 -2
  169. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  170. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  171. package/src/config/schemas/calls.ts +1 -28
  172. package/src/config/schemas/live-voice.ts +1 -173
  173. package/src/config/schemas/mcp.ts +1 -1
  174. package/src/config/schemas/memory-retrospective.ts +10 -0
  175. package/src/config/schemas/skills.ts +0 -14
  176. package/src/config/schemas/voice.ts +150 -0
  177. package/src/credential-execution/process-manager.test.ts +18 -19
  178. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  179. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  180. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  181. package/src/daemon/app-change-notify.ts +45 -0
  182. package/src/daemon/config-watcher.ts +13 -194
  183. package/src/daemon/conversation-process.ts +35 -64
  184. package/src/daemon/conversation-skill-tools.ts +13 -31
  185. package/src/daemon/conversation-store.ts +1 -1
  186. package/src/daemon/conversation-surfaces.ts +10 -8
  187. package/src/daemon/conversation-tool-setup.ts +37 -43
  188. package/src/daemon/conversation.ts +0 -3
  189. package/src/daemon/host-bash-proxy.ts +8 -12
  190. package/src/daemon/host-browser-proxy.ts +15 -1
  191. package/src/daemon/host-file-proxy.ts +8 -10
  192. package/src/daemon/host-transfer-proxy.ts +14 -18
  193. package/src/daemon/lifecycle.ts +0 -5
  194. package/src/daemon/mcp-reload-service.ts +5 -5
  195. package/src/daemon/message-types/sync.ts +1 -0
  196. package/src/daemon/providers-setup.ts +1 -1
  197. package/src/daemon/shutdown-handlers.ts +1 -9
  198. package/src/daemon/skill-memory-refresh.ts +64 -0
  199. package/src/daemon/tool-side-effects.ts +7 -18
  200. package/src/desktop/__tests__/fake-desktop.ts +119 -0
  201. package/src/desktop/desktop-chrome-policy.test.ts +71 -0
  202. package/src/desktop/desktop-chrome-policy.ts +42 -0
  203. package/src/desktop/desktop-dependencies.test.ts +102 -0
  204. package/src/desktop/desktop-dependencies.ts +306 -0
  205. package/src/desktop/desktop-feature.test.ts +18 -0
  206. package/src/desktop/desktop-feature.ts +19 -0
  207. package/src/desktop/desktop-panel-config.test.ts +135 -0
  208. package/src/desktop/desktop-panel-config.ts +149 -0
  209. package/src/desktop/desktop-session-manager.test.ts +627 -0
  210. package/src/desktop/desktop-session-manager.ts +814 -0
  211. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  212. package/src/desktop/desktop-stream-bridge.ts +214 -0
  213. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  214. package/src/desktop/desktop-wallpaper.ts +113 -0
  215. package/src/desktop/desktop-window-theme.ts +170 -0
  216. package/src/hooks/types.ts +33 -0
  217. package/src/i18n/AGENTS.md +22 -0
  218. package/src/i18n/__tests__/i18n.test.ts +108 -0
  219. package/src/i18n/index.ts +16 -0
  220. package/src/i18n/locales.ts +93 -0
  221. package/src/i18n/messages.ts +61 -0
  222. package/src/i18n/title.ts +25 -0
  223. package/src/inbound/platform-callback-registration.ts +4 -5
  224. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  225. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  226. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  227. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  228. package/src/live-voice/live-voice-session.ts +213 -693
  229. package/src/live-voice/protocol.ts +0 -2
  230. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  231. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  232. package/src/mcp/effective-config.ts +3 -3
  233. package/src/mcp/mcp-header-store.ts +29 -45
  234. package/src/mcp/spec-schema.ts +111 -0
  235. package/src/mcp/startup.ts +8 -4
  236. package/src/mcp/workspace-mcp-config.ts +188 -0
  237. package/src/messaging/provider.ts +10 -3
  238. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  239. package/src/messaging/providers/callback-routing.ts +18 -0
  240. package/src/messaging/providers/channel-transport.ts +56 -0
  241. package/src/messaging/providers/discord/transport.ts +17 -0
  242. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  243. package/src/messaging/providers/slack/adapter.ts +7 -44
  244. package/src/messaging/providers/slack/transport.ts +16 -0
  245. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  246. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  247. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  248. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  249. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  250. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  251. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  252. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  253. package/src/notifications/assistant-reply-producer.ts +8 -3
  254. package/src/notifications/conversation-pairing.ts +36 -16
  255. package/src/notifications/delivered-post-record.ts +11 -1
  256. package/src/notifications/home-feed-side-effect.ts +55 -7
  257. package/src/outbound-proxy/certs.ts +3 -5
  258. package/src/permissions/checker.test.ts +11 -6
  259. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  260. package/src/persistence/conversation-crud.ts +71 -63
  261. package/src/persistence/conversation-lineage.ts +12 -4
  262. package/src/persistence/conversation-title-placeholders.ts +23 -0
  263. package/src/persistence/conversation-title-service.ts +9 -26
  264. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  265. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  266. package/src/persistence/message-cursor.ts +78 -0
  267. package/src/persistence/message-reads.ts +30 -0
  268. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  269. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  270. package/src/persistence/schema/memory-core.ts +5 -0
  271. package/src/platform/client.test.ts +2 -0
  272. package/src/platform/client.ts +2 -10
  273. package/src/plugin-api/constants.ts +2 -0
  274. package/src/plugin-api/index.ts +1 -0
  275. package/src/plugin-api/types.ts +2 -0
  276. package/src/plugins/defaults/index.ts +2 -0
  277. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  278. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  279. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  280. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  281. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  282. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  285. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  286. package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
  287. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  288. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  289. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  290. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  291. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  292. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  293. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  294. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  295. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  296. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  297. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  298. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  299. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  300. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  301. package/src/plugins/defaults/memory/startup.ts +6 -1
  302. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  303. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  304. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
  305. package/src/plugins/defaults/memory/worker.ts +6 -0
  306. package/src/plugins/mcp-servers.ts +15 -75
  307. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  308. package/src/providers/gemini/client.ts +9 -23
  309. package/src/providers/gemini-thought-signature.test.ts +63 -0
  310. package/src/providers/gemini-thought-signature.ts +44 -0
  311. package/src/providers/model-catalog.ts +34 -0
  312. package/src/providers/model-intents.ts +10 -5
  313. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  314. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  315. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  316. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  317. package/src/providers/openai/chat-completions-provider.ts +192 -78
  318. package/src/providers/openai/codex-models.ts +0 -4
  319. package/src/providers/openai/google-thought-signature.ts +320 -0
  320. package/src/runtime/AGENTS.md +2 -0
  321. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  322. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  323. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  324. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  325. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  326. package/src/runtime/auth/same-actor.ts +38 -3
  327. package/src/runtime/channel-send.ts +316 -0
  328. package/src/runtime/http-server.ts +160 -151
  329. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  330. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  331. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  332. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  333. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  334. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  335. package/src/runtime/routes/acp-routes.test.ts +15 -0
  336. package/src/runtime/routes/acp-routes.ts +72 -9
  337. package/src/runtime/routes/app-management-routes.ts +73 -0
  338. package/src/runtime/routes/app-routes.ts +1 -1
  339. package/src/runtime/routes/channel-send-routes.ts +85 -0
  340. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  341. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  342. package/src/runtime/routes/credential-routes.ts +19 -10
  343. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  344. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  345. package/src/runtime/routes/diagnostics-routes.ts +55 -3
  346. package/src/runtime/routes/index.ts +4 -0
  347. package/src/runtime/routes/log-export-routes.ts +2 -0
  348. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  349. package/src/runtime/routes/migration-routes.ts +8 -21
  350. package/src/runtime/routes/platform-routes.ts +18 -15
  351. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  352. package/src/runtime/services/conversation-serializer.ts +16 -3
  353. package/src/runtime/sync/resource-sync-events.ts +2 -1
  354. package/src/schedule/worker.ts +1 -1
  355. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  356. package/src/security/ces-http-record-backend.ts +120 -0
  357. package/src/security/ces-rpc-record-backend.ts +7 -0
  358. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  359. package/src/skills/workspace-skill-md-signature.ts +50 -0
  360. package/src/subagent/consult-context.ts +12 -25
  361. package/src/tools/acp/list-agents.test.ts +3 -1
  362. package/src/tools/acp/spawn.test.ts +146 -11
  363. package/src/tools/acp/spawn.ts +21 -3
  364. package/src/tools/acp/status.test.ts +94 -0
  365. package/src/tools/acp/status.ts +40 -3
  366. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  367. package/src/tools/capability-offer.test.ts +22 -2
  368. package/src/tools/capability-offer.ts +16 -1
  369. package/src/tools/computer-use/definitions.ts +4 -2
  370. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  371. package/src/tools/credentials/metadata-store.ts +100 -0
  372. package/src/tools/host-filesystem/edit.test.ts +2 -1
  373. package/src/tools/host-filesystem/edit.ts +2 -2
  374. package/src/tools/host-filesystem/read.test.ts +2 -1
  375. package/src/tools/host-filesystem/read.ts +2 -2
  376. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  377. package/src/tools/host-filesystem/transfer.ts +2 -2
  378. package/src/tools/host-filesystem/write.test.ts +2 -1
  379. package/src/tools/host-filesystem/write.ts +2 -2
  380. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  381. package/src/tools/host-terminal/host-shell.ts +4 -20
  382. package/src/tools/policy-context.ts +6 -7
  383. package/src/tools/subagent/spawn.ts +5 -8
  384. package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
  385. package/src/tools/terminal/safe-env.ts +23 -3
  386. package/src/tools/watch/watch-retro-report.ts +0 -8
  387. package/src/util/__tests__/language-subtag.test.ts +23 -1
  388. package/src/util/ca-bundle.ts +13 -0
  389. package/src/util/language-subtag.ts +16 -0
  390. package/src/util/platform.ts +8 -0
  391. package/src/workspace/git-porcelain.ts +66 -0
  392. package/src/workspace/git-service.ts +5 -54
  393. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  394. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  395. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  396. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  397. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  398. package/src/workspace/migrations/registry.ts +6 -0
  399. package/src/__tests__/app-source-watcher.test.ts +0 -218
  400. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  401. package/src/__tests__/speaker-identification.test.ts +0 -56
  402. package/src/calls/speaker-identification.ts +0 -233
  403. package/src/daemon/app-source-watcher.ts +0 -218
  404. package/src/messaging/providers/whatsapp/client.ts +0 -29
  405. package/src/util/fs-watcher-error.ts +0 -36
  406. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -22,6 +22,7 @@ import {
22
22
  getConfiguredProvider,
23
23
  userMessage,
24
24
  } from "../../providers/provider-send-message.js";
25
+ import { isMaxTokensStopReason } from "../../providers/stop-reasons.js";
25
26
  import { getLogger } from "../../util/logger.js";
26
27
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
27
28
  import { BadRequestError } from "./errors.js";
@@ -176,9 +177,44 @@ function buildCommandPrompt(body: DictationBody, stylePrompt?: string): string {
176
177
  return sections.join("\n");
177
178
  }
178
179
 
179
- function computeMaxTokens(inputLength: number): number {
180
+ export function computeMaxTokens(inputLength: number): number {
180
181
  const estimatedInputTokens = Math.ceil(inputLength / 3);
181
- return Math.max(256, estimatedInputTokens + 128);
182
+ // The cleanup tool call carries the whole transcript back in its `text`
183
+ // argument, plus a `reasoning` string and the JSON scaffolding around both,
184
+ // so the budget has to cover well more than the input alone. `max_tokens` is
185
+ // a ceiling rather than a target: a call that stops short of it costs no
186
+ // extra latency.
187
+ return Math.max(512, estimatedInputTokens * 2 + 256);
188
+ }
189
+
190
+ export type CleanupRejection = "truncated";
191
+
192
+ /**
193
+ * Decide whether the cleanup model's rewrite is safe to use as the payload.
194
+ *
195
+ * The rewrite replaces what the user actually said. A token-cap stop means the
196
+ * tool JSON was cut mid-argument, so however complete the `text` argument
197
+ * happens to look, it is a fragment; the raw transcript is the safer payload
198
+ * then. Unpolished beats wrong.
199
+ *
200
+ * Whether a shorter rewrite still preserved the meaning is the model's call
201
+ * (the prompt asks it to drop fillers and tighten phrasing), so there is no
202
+ * length check here: a deterministic ratio cannot tell a concise cleanup from
203
+ * a summary.
204
+ */
205
+ export function resolveCleanedDictation(
206
+ raw: string,
207
+ cleaned: string,
208
+ stopReason: string | null | undefined,
209
+ ): { text: string; rejected: CleanupRejection | null } {
210
+ const trimmed = cleaned.trim();
211
+ if (!trimmed) {
212
+ return { text: raw, rejected: null };
213
+ }
214
+ if (isMaxTokensStopReason(stopReason)) {
215
+ return { text: raw, rejected: "truncated" };
216
+ }
217
+ return { text: trimmed, rejected: null };
182
218
  }
183
219
 
184
220
  interface DictationResult {
@@ -329,7 +365,23 @@ async function handleDictation(body: DictationBody): Promise<DictationResult> {
329
365
  ...profileMeta,
330
366
  };
331
367
  }
332
- const cleanedText = input.text?.trim() || transcription;
368
+ const { text: cleanedText, rejected } = resolveCleanedDictation(
369
+ transcription,
370
+ input.text ?? "",
371
+ response.stopReason,
372
+ );
373
+ if (rejected) {
374
+ // Lengths only -- transcript content must never be logged.
375
+ log.warn(
376
+ {
377
+ rejected,
378
+ stopReason: response.stopReason,
379
+ rawChars: transcription.length,
380
+ cleanedChars: input.text?.trim().length ?? 0,
381
+ },
382
+ "Dictation cleanup rejected, using raw transcription",
383
+ );
384
+ }
333
385
  const normalizedText = applyDictionary(cleanedText, profile.dictionary);
334
386
  return {
335
387
  text: normalizedText,
@@ -39,6 +39,7 @@ import { ROUTES as CALL_ROUTES } from "./call-routes.js";
39
39
  import { ROUTES as CHANNEL_AVAILABILITY_ROUTES } from "./channel-availability-routes.js";
40
40
  import { ROUTES as CHANNEL_READINESS_ROUTES } from "./channel-readiness-routes.js";
41
41
  import { CHANNEL_ROUTES } from "./channel-route-definitions.js";
42
+ import { ROUTES as CHANNEL_SEND_ROUTES } from "./channel-send-routes.js";
42
43
  import { ROUTES as CHANNEL_VERIFICATION_ROUTES } from "./channel-verification-routes.js";
43
44
  import { ROUTES as CHATGPT_SUBSCRIPTION_AUTH_ROUTES } from "./chatgpt-subscription-auth-routes.js";
44
45
  import { ROUTES as CLIENT_ROUTES } from "./client-routes.js";
@@ -62,6 +63,7 @@ import { ROUTES as DEBUG_BASH_ROUTES } from "./debug-bash-routes.js";
62
63
  import { ROUTES as DEBUG_ROUTES } from "./debug-routes.js";
63
64
  import { ROUTES as DEFAULT_PROVIDER_ROUTES } from "./default-provider-routes.js";
64
65
  import { ROUTES as DEFER_ROUTES } from "./defer-routes.js";
66
+ import { ROUTES as DESKTOP_SETUP_ROUTES } from "./desktop-setup-routes.js";
65
67
  import { ROUTES as DIAGNOSTICS_ROUTES } from "./diagnostics-routes.js";
66
68
  import { ROUTES as DISK_PRESSURE_ROUTES } from "./disk-pressure-routes.js";
67
69
  import { ROUTES as DOCUMENT_COMMENT_ROUTES } from "./document-comments-routes.js";
@@ -191,6 +193,7 @@ export const ROUTES: RouteDefinition[] = [
191
193
  ...CHANNEL_VERIFICATION_ROUTES,
192
194
  ...CHANNEL_AVAILABILITY_ROUTES,
193
195
  ...CHANNEL_READINESS_ROUTES,
196
+ ...CHANNEL_SEND_ROUTES,
194
197
  ...BROWSER_ROUTES,
195
198
  ...BROWSER_TABS_ROUTES,
196
199
  ...BTW_ROUTES,
@@ -218,6 +221,7 @@ export const ROUTES: RouteDefinition[] = [
218
221
  ...DEBUG_BASH_ROUTES,
219
222
  ...DEBUG_ROUTES,
220
223
  ...DEFAULT_PROVIDER_ROUTES,
224
+ ...DESKTOP_SETUP_ROUTES,
221
225
  ...DIAGNOSTICS_ROUTES,
222
226
  ...DISK_PRESSURE_ROUTES,
223
227
  ...RESOURCE_PRESSURE_ROUTES,
@@ -22,6 +22,7 @@ import { join } from "node:path";
22
22
  import { and, desc, eq, gte, lte } from "drizzle-orm";
23
23
  import { z } from "zod";
24
24
 
25
+ import { overlayWorkspaceMcpForConfigRead } from "../../mcp/workspace-mcp-config.js";
25
26
  import { getDb, getLogsDb } from "../../persistence/db-connection.js";
26
27
  import {
27
28
  llmRequestLogs,
@@ -568,6 +569,7 @@ function readSanitizedConfig(): Record<string, unknown> | undefined {
568
569
  }
569
570
  }
570
571
 
572
+ overlayWorkspaceMcpForConfigRead(config);
571
573
  const mcpServers = asRecord(asRecord(config.mcp)?.servers);
572
574
  for (const server of Object.values(mcpServers ?? {})) {
573
575
  const transport = asRecord(asRecord(server)?.transport);
@@ -14,8 +14,7 @@
14
14
 
15
15
  import { z } from "zod";
16
16
 
17
- import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
18
- import type { McpConfig, McpServerConfig } from "../../config/schemas/mcp.js";
17
+ import type { McpServerConfig } from "../../config/schemas/mcp.js";
19
18
  import { estimateToolDefinitionTokens } from "../../context/token-estimator.js";
20
19
  import { reloadMcpServers } from "../../daemon/mcp-reload-service.js";
21
20
  import { McpClient } from "../../mcp/client.js";
@@ -31,6 +30,10 @@ import {
31
30
  deleteMcpOAuthCredentials,
32
31
  hasMcpOAuthTokens,
33
32
  } from "../../mcp/mcp-oauth-provider.js";
33
+ import {
34
+ loadWorkspaceMcpConfig,
35
+ saveWorkspaceMcpConfig,
36
+ } from "../../mcp/workspace-mcp-config.js";
34
37
  import { readPluginMcpServers } from "../../plugins/mcp-servers.js";
35
38
  import { getMcpToolsByServer } from "../../tools/registry.js";
36
39
  import { getLogger } from "../../util/logger.js";
@@ -65,6 +68,18 @@ const McpAddParams = z.object({
65
68
 
66
69
  const McpRemoveParams = z.object({ name: z.string() });
67
70
 
71
+ function persistWorkspaceMcpConfig(config: {
72
+ servers: Record<string, McpServerConfig>;
73
+ }): void {
74
+ try {
75
+ saveWorkspaceMcpConfig(config);
76
+ } catch (err) {
77
+ throw new InternalError(
78
+ err instanceof Error ? err.message : String(err),
79
+ );
80
+ }
81
+ }
82
+
68
83
  async function handleMcpAuthStart({
69
84
  body,
70
85
  }: {
@@ -76,8 +91,7 @@ async function handleMcpAuthStart({
76
91
  }> {
77
92
  const { serverId } = parseBody(McpServerIdParams, body);
78
93
 
79
- const raw = loadRawConfig();
80
- const servers = (raw.mcp as Partial<McpConfig> | undefined)?.servers ?? {};
94
+ const servers = loadWorkspaceMcpConfig().servers;
81
95
  const serverConfig = servers[serverId];
82
96
 
83
97
  if (!serverConfig) {
@@ -253,9 +267,7 @@ function detectAuthType(headers: Record<string, string>): "bearer" | "api-key" {
253
267
  async function handleMcpList(_args: {
254
268
  body?: Record<string, unknown>;
255
269
  }): Promise<{ servers: McpServerEntry[] }> {
256
- const raw = loadRawConfig();
257
- const mcpConfig = raw.mcp as Partial<McpConfig> | undefined;
258
- const servers = mcpConfig?.servers ?? {};
270
+ const servers = loadWorkspaceMcpConfig().servers;
259
271
  const configEntries = (
260
272
  Object.entries(servers) as [string, McpServerConfig][]
261
273
  ).filter(([, config]) => config && typeof config === "object");
@@ -434,18 +446,13 @@ async function handleMcpUpdate({
434
446
  }): Promise<{ updated: true }> {
435
447
  const { name, headers } = parseBody(McpUpdateParams, body);
436
448
 
437
- const raw = loadRawConfig();
438
- const mcpConfig = raw.mcp as Record<string, unknown> | undefined;
439
- const serverMap = mcpConfig?.servers as
440
- | Record<string, Record<string, unknown>>
441
- | undefined;
449
+ const servers = loadWorkspaceMcpConfig().servers;
450
+ const server = servers[name];
442
451
 
443
- if (!serverMap || !serverMap[name]) {
452
+ if (!server) {
444
453
  throw new NotFoundError(`MCP server "${name}" not found.`);
445
454
  }
446
455
 
447
- const server = serverMap[name];
448
-
449
456
  if (headers !== undefined) {
450
457
  const transport = server.transport as Record<string, unknown> | undefined;
451
458
  if (
@@ -474,7 +481,7 @@ async function handleMcpUpdate({
474
481
  }
475
482
  }
476
483
 
477
- saveRawConfig(raw);
484
+ persistWorkspaceMcpConfig({ servers });
478
485
  triggerReload("internal_mcp_update");
479
486
 
480
487
  return { updated: true };
@@ -494,7 +501,7 @@ async function handleMcpAdd({
494
501
  body,
495
502
  );
496
503
 
497
- let transport: Record<string, unknown>;
504
+ let transport: McpServerConfig["transport"];
498
505
  switch (transportType) {
499
506
  case "stdio":
500
507
  if (!command) {
@@ -517,27 +524,16 @@ async function handleMcpAdd({
517
524
  );
518
525
  }
519
526
 
520
- const raw = loadRawConfig();
521
- if (!raw.mcp) {
522
- raw.mcp = { servers: {} };
523
- }
524
- const mcpConfig = raw.mcp as Record<string, unknown>;
525
- if (!mcpConfig.servers) {
526
- mcpConfig.servers = {};
527
- }
528
- const serverMap = mcpConfig.servers as Record<string, unknown>;
527
+ const servers = { ...loadWorkspaceMcpConfig().servers };
529
528
 
530
- if (serverMap[name]) {
529
+ if (servers[name]) {
531
530
  throw new BadRequestError(
532
531
  `MCP server "${name}" already exists. Remove it first with: assistant mcp remove ${name}`,
533
532
  );
534
533
  }
535
534
 
536
- serverMap[name] = {
537
- transport,
538
- };
535
+ servers[name] = { transport };
539
536
 
540
- // Store auth headers in credential store, not config
541
537
  if (headers && Object.keys(headers).length > 0) {
542
538
  const ok = await setMcpHeaders(name, headers);
543
539
  if (!ok) {
@@ -547,7 +543,7 @@ async function handleMcpAdd({
547
543
  }
548
544
  }
549
545
 
550
- saveRawConfig(raw);
546
+ persistWorkspaceMcpConfig({ servers });
551
547
  triggerReload("internal_mcp_add");
552
548
 
553
549
  return { added: true };
@@ -564,8 +560,7 @@ async function handleMcpAuthRevoke({
564
560
  }): Promise<{ revoked: true }> {
565
561
  const { serverId } = parseBody(McpServerIdParams, body);
566
562
 
567
- const raw = loadRawConfig();
568
- const servers = (raw.mcp as Partial<McpConfig> | undefined)?.servers ?? {};
563
+ const servers = loadWorkspaceMcpConfig().servers;
569
564
  const serverConfig = servers[serverId];
570
565
 
571
566
  if (!serverConfig) {
@@ -602,32 +597,29 @@ async function handleMcpRemove({
602
597
  }): Promise<{ removed: true }> {
603
598
  const { name } = parseBody(McpRemoveParams, body);
604
599
 
605
- const raw = loadRawConfig();
606
- const mcpConfig = raw.mcp as Record<string, unknown> | undefined;
607
- const serverMap = mcpConfig?.servers as Record<string, unknown> | undefined;
600
+ const servers = { ...loadWorkspaceMcpConfig().servers };
601
+ const serverConfig = servers[name];
608
602
 
609
- if (!serverMap || !serverMap[name]) {
603
+ if (!serverConfig) {
610
604
  throw new NotFoundError(`MCP server "${name}" not found.`);
611
605
  }
612
606
 
613
- // Best-effort cleanup of credentials stored for this server
614
- const serverConfig = serverMap[name] as Record<string, unknown>;
615
- const transport = serverConfig?.transport as
616
- | Record<string, unknown>
617
- | undefined;
618
- if (transport?.type === "sse" || transport?.type === "streamable-http") {
607
+ if (
608
+ serverConfig.transport.type === "sse" ||
609
+ serverConfig.transport.type === "streamable-http"
610
+ ) {
619
611
  try {
620
612
  await Promise.all([
621
613
  deleteMcpOAuthCredentials(name),
622
614
  deleteMcpHeaders(name),
623
615
  ]);
624
616
  } catch {
625
- // Ignore — credentials may not exist
617
+ // Credentials may not exist.
626
618
  }
627
619
  }
628
620
 
629
- delete serverMap[name];
630
- saveRawConfig(raw);
621
+ delete servers[name];
622
+ persistWorkspaceMcpConfig({ servers });
631
623
  triggerReload("internal_mcp_remove");
632
624
 
633
625
  return { removed: true };
@@ -22,8 +22,8 @@ import { PassThrough, Readable } from "node:stream";
22
22
 
23
23
  import { z } from "zod";
24
24
 
25
- import { getPlatformAssistantId } from "../../config/env.js";
26
25
  import { invalidateConfigCache } from "../../config/loader.js";
26
+ import { resolvePlatformAssistantId } from "../../config/platform-identity.js";
27
27
  import { getAssistantName } from "../../daemon/identity-helpers.js";
28
28
  import { runAsyncSqlite } from "../../persistence/db-async-query.js";
29
29
  import {
@@ -236,28 +236,15 @@ interface ExportManifestInputs {
236
236
  /**
237
237
  * Resolve the `assistant.id` for an export.
238
238
  *
239
- * Mirrors `platform/client.ts`'s precedence: in-memory override (set at
240
- * daemon startup or by secret-routes) → credential store → daemon-internal
241
- * fallback. The schema requires `id` to be non-empty, so we fall back to
242
- * `DAEMON_INTERNAL_ASSISTANT_ID` rather than the empty string.
239
+ * In-memory identity (validate rehydration or secret-routes) first, then
240
+ * the vault copy of `platform_assistant_id`. The schema requires `id` to be
241
+ * non-empty, so we fall back to `DAEMON_INTERNAL_ASSISTANT_ID` rather than
242
+ * the empty string.
243
243
  */
244
244
  async function resolveAssistantId(): Promise<string> {
245
- const inMemory = getPlatformAssistantId();
246
- if (inMemory) {
247
- return inMemory;
248
- }
249
- try {
250
- const stored = await getSecureKeyAsync(
251
- credentialKey("vellum", "platform_assistant_id"),
252
- );
253
- if (stored) {
254
- return stored;
255
- }
256
- } catch (err) {
257
- log.warn(
258
- { err },
259
- "Failed to read platform_assistant_id from credential store; falling back to daemon-internal id",
260
- );
245
+ const resolved = await resolvePlatformAssistantId();
246
+ if (resolved) {
247
+ return resolved;
261
248
  }
262
249
  return DAEMON_INTERNAL_ASSISTANT_ID;
263
250
  }
@@ -39,7 +39,17 @@ import {
39
39
  } from "@vellumai/service-contracts/platform-credential";
40
40
  import { z } from "zod";
41
41
 
42
+ import {
43
+ setPlatformAssistantId,
44
+ setPlatformBaseUrl,
45
+ setPlatformOrganizationId,
46
+ setPlatformUserId,
47
+ } from "../../config/env.js";
42
48
  import { isPlatformRemote } from "../../config/env-registry.js";
49
+ import {
50
+ resolvePlatformOrganizationId,
51
+ resolvePlatformUserId,
52
+ } from "../../config/platform-identity.js";
43
53
  import {
44
54
  registerCallbackRoute,
45
55
  resolvePlatformCallbackRegistrationContext,
@@ -289,24 +299,12 @@ async function handlePlatformStatus(
289
299
  ): Promise<PlatformStatusResponse> {
290
300
  const context = await resolvePlatformCallbackRegistrationContext();
291
301
 
292
- const [orgIdRaw, userIdRaw, webhookSecretRaw] = await Promise.all([
293
- getSecureKeyAsync(
294
- credentialKey(
295
- CREDENTIAL_KEYS.organizationId.service,
296
- CREDENTIAL_KEYS.organizationId.field,
297
- ),
298
- ),
299
- getSecureKeyAsync(
300
- credentialKey(
301
- CREDENTIAL_KEYS.userId.service,
302
- CREDENTIAL_KEYS.userId.field,
303
- ),
304
- ),
302
+ const [organizationId, userId, webhookSecretRaw] = await Promise.all([
303
+ resolvePlatformOrganizationId(),
304
+ resolvePlatformUserId(),
305
305
  getSecureKeyAsync(credentialKey("vellum", "webhook_secret")),
306
306
  ]);
307
307
 
308
- const organizationId = orgIdRaw?.trim() ?? "";
309
- const userId = userIdRaw?.trim() ?? "";
310
308
  const hasWebhookSecret = !!webhookSecretRaw;
311
309
 
312
310
  return {
@@ -467,6 +465,11 @@ async function handlePlatformDisconnect(
467
465
  );
468
466
  }
469
467
 
468
+ setPlatformBaseUrl(undefined);
469
+ setPlatformAssistantId(undefined);
470
+ setPlatformOrganizationId(undefined);
471
+ setPlatformUserId(undefined);
472
+
470
473
  // Notify connected clients
471
474
  broadcastMessage({ type: "platform_disconnected" });
472
475
 
@@ -159,3 +159,31 @@ describe("serializeConversationSummary · enabledPlugins", () => {
159
159
  expect("enabledPlugins" in summary).toBe(false);
160
160
  });
161
161
  });
162
+
163
+ describe("serializeConversationSummary · title keys", () => {
164
+ test("resolves a stored title key for the locale", () => {
165
+ const summary = serializeConversationSummary({
166
+ conversation: makeConversationRow({
167
+ title: "conversation.title.generating",
168
+ }),
169
+ displayMeta: { displayOrder: null, isPinned: false, groupId: null },
170
+ parentCache: new Map(),
171
+ isProcessing: false,
172
+ locale: "es",
173
+ });
174
+ expect(summary.title).toBe("Generando título...");
175
+ expect("titleState" in summary).toBe(false);
176
+ });
177
+
178
+ test("passes a stored display string through", () => {
179
+ const summary = serialize(
180
+ makeConversationRow({ title: "Untitled Conversation" }),
181
+ );
182
+ expect(summary.title).toBe("Untitled Conversation");
183
+ });
184
+
185
+ test("passes a custom title through", () => {
186
+ const summary = serialize(makeConversationRow({ title: "Weekly planning" }));
187
+ expect(summary.title).toBe("Weekly planning");
188
+ });
189
+ });
@@ -8,6 +8,10 @@
8
8
 
9
9
  import { parseChannelId } from "../../channels/types.js";
10
10
  import { normalizeConversationType } from "../../daemon/message-types/shared.js";
11
+ import {
12
+ resolveConversationTitle,
13
+ type SupportedLocale,
14
+ } from "../../i18n/index.js";
11
15
  import { buildChannelBindingMetadata } from "../../messaging/channel-binding-metadata.js";
12
16
  import {
13
17
  type AttentionState,
@@ -78,6 +82,7 @@ interface ForkLineage {
78
82
  function buildForkLineage(
79
83
  conversation: ConversationRow,
80
84
  parentCache: Map<string, ConversationRow | null>,
85
+ locale?: SupportedLocale,
81
86
  ): ForkLineage {
82
87
  const parentConversationId = conversation.forkParentConversationId;
83
88
  const parentMessageId = conversation.forkParentMessageId;
@@ -101,7 +106,7 @@ function buildForkLineage(
101
106
  forkParent: {
102
107
  conversationId: parentConversationId,
103
108
  messageId: parentMessageId,
104
- title: parentConversation.title ?? "Untitled",
109
+ title: resolveConversationTitle(parentConversation.title, locale),
105
110
  },
106
111
  };
107
112
  }
@@ -178,6 +183,11 @@ export function serializeConversationSummary(params: {
178
183
  * with no daemon-store coupling.
179
184
  */
180
185
  isProcessing: boolean;
186
+ /**
187
+ * Locale used to resolve stored title keys. Omitted locale is English
188
+ * inside {@link resolveConversationTitle}.
189
+ */
190
+ locale?: SupportedLocale;
181
191
  }) {
182
192
  const {
183
193
  conversation,
@@ -186,14 +196,15 @@ export function serializeConversationSummary(params: {
186
196
  displayMeta,
187
197
  parentCache,
188
198
  isProcessing,
199
+ locale,
189
200
  } = params;
190
201
  const originChannel = parseChannelId(conversation.originChannel);
191
202
  const assistantAttention = buildAssistantAttention(attentionState);
192
- const forkLineage = buildForkLineage(conversation, parentCache);
203
+ const forkLineage = buildForkLineage(conversation, parentCache, locale);
193
204
 
194
205
  return {
195
206
  id: conversation.id,
196
- title: conversation.title ?? "Untitled",
207
+ title: resolveConversationTitle(conversation.title, locale),
197
208
  createdAt: conversation.createdAt,
198
209
  updatedAt: conversation.updatedAt,
199
210
  lastMessageAt: conversation.lastMessageAt,
@@ -253,6 +264,7 @@ export function serializeConversationSummary(params: {
253
264
  */
254
265
  export function buildConversationDetailResponse(
255
266
  conversationId: string,
267
+ locale?: SupportedLocale,
256
268
  ): { conversation: ReturnType<typeof serializeConversationSummary> } | null {
257
269
  const conversation = getConversation(conversationId);
258
270
  if (!conversation || conversation.conversationType === "private") {
@@ -274,6 +286,7 @@ export function buildConversationDetailResponse(
274
286
  // Checks in-memory flag first (hot path), falls back to the
275
287
  // persisted `processing_started_at` column for cold conversations.
276
288
  isProcessing: isConversationProcessing(conversation.id),
289
+ locale,
277
290
  }),
278
291
  };
279
292
  }
@@ -4,6 +4,7 @@ import {
4
4
  conversationMetadataSyncTag,
5
5
  SYNC_TAGS,
6
6
  } from "../../daemon/message-types/sync.js";
7
+ import { resolveConversationTitle } from "../../i18n/index.js";
7
8
  import { syncAvatarToPlatform } from "../../platform/sync-avatar.js";
8
9
  import { getAvatarImagePath } from "../../util/platform.js";
9
10
  import { broadcastMessage } from "../assistant-event-hub.js";
@@ -257,7 +258,7 @@ export function publishConversationTitleChanged(
257
258
  {
258
259
  type: "conversation_title_updated",
259
260
  conversationId,
260
- title,
261
+ title: resolveConversationTitle(title),
261
262
  },
262
263
  conversationId,
263
264
  );
@@ -195,7 +195,7 @@ async function main(): Promise<void> {
195
195
  //
196
196
  // Bounded by MCP_STARTUP_GRACE_MS: unreachable servers must not hold the
197
197
  // schedules that do not use them. The connect runs on past the deadline.
198
- const mcpStartup = startConfiguredMcpServers(getConfig().mcp);
198
+ const mcpStartup = startConfiguredMcpServers();
199
199
  await Promise.race([
200
200
  mcpStartup,
201
201
  // Unref'd so a connect that beats the deadline leaves nothing pending.