@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
@@ -60,7 +60,7 @@ Apps live under `/workspace/data/apps/`:
60
60
  <slug>.json # App metadata
61
61
  <slug>/
62
62
  src/ # Source files (TSX) — what you write
63
- dist/ # Compiled output — auto-generated by app_refresh
63
+ dist/ # Compiled output, generated by app_refresh or assistant apps refresh
64
64
  records/ # Data records (one JSON file per record)
65
65
  <slug>.preview # Preview image (auto-generated)
66
66
  ```
@@ -156,9 +156,9 @@ Calculators, single-page tools, landing pages, and slide decks skip this — pas
156
156
 
157
157
  ### 3 — Create the app (scaffold, then expand)
158
158
 
159
- ⚠️ **`app_create` is ONE-SHOT per build.** Call it exactly once. After it returns an `app_id`, all further changes go through `file_write` / `file_edit` + `app_refresh`. To start over: `app_delete(app_id)` first, then a fresh `app_create`.
159
+ ⚠️ **`app_create` is ONE-SHOT per build.** Call it exactly once. After it returns an `app_id`, all further changes go through `file_write` / `file_edit` and a single compile (`assistant apps refresh` or `app_refresh`). To start over: `app_delete(app_id)` first, then a fresh `app_create`.
160
160
 
161
- Apps are multi-file Preact + TSX projects; esbuild bundles automatically. Structure:
161
+ Apps are multi-file Preact + TSX projects; esbuild bundles when you compile. Structure:
162
162
 
163
163
  ```
164
164
  src/
@@ -179,9 +179,9 @@ render(<App />, document.getElementById("app")!);
179
179
 
180
180
  1. **`app_create`** with a **4-file scaffold**: `src/index.html`, `src/main.tsx`, a **placeholder** `src/components/App.tsx` (`<div>Loading...</div>`), and an **empty** `src/styles.css`. The placeholders make the first compile clean — a 2-file scaffold leaves broken imports.
181
181
  2. **`file_write`** each real file, one per tool call, overwriting the placeholders and adding components.
182
- 3. **`app_refresh`** ONCE at the end to compile.
182
+ 3. Compile ONCE at the end. File edits do not compile on their own. Use `assistant apps inspect <name>` to see whether source drifted, then `assistant apps refresh <name>` (or `app_refresh`).
183
183
 
184
- **Actually write the files.** Source code in reasoning/thinking is invisible to the app sandbox and is not a file write. Put every real file body in an `app_create.source_files`, `file_write`, `file_edit`, or `app_update.source_files` tool call, then verify the files exist before `app_refresh` when there is any doubt.
184
+ **Actually write the files.** Source code in reasoning/thinking is invisible to the app sandbox and is not a file write. Put every real file body in an `app_create.source_files`, `file_write`, `file_edit`, or `app_update.source_files` tool call, then verify the files exist before compiling when there is any doubt.
185
185
 
186
186
  **Allowed packages** (esbuild-resolved, no CDN): `date-fns`, `chart.js`, `lodash-es`, `zod`, `clsx`. For icons, write inline `<svg>` markup directly — no icon package is bundled.
187
187
 
@@ -221,11 +221,14 @@ app_create({ app_create({
221
221
 
222
222
  ### 4 — Compile
223
223
 
224
+ File edits do not compile on their own. After ALL file writes, compile once:
225
+
224
226
  ```
225
- app_refresh(app_id)
227
+ assistant apps inspect <name>
228
+ assistant apps refresh <name>
226
229
  ```
227
230
 
228
- Call it ONCE, after ALL file writes — batching is required. If it fails, the response has error details; fix with `file_edit`, then `app_refresh` again.
231
+ `assistant apps inspect` reports whether source changed since the last compile. `assistant apps refresh` compiles and refreshes open surfaces. `app_refresh(app_id)` also compiles. If compile fails, the response has error details; fix with `file_edit`, then compile again.
229
232
 
230
233
  ### 5 — Show the preview card
231
234
 
@@ -244,7 +247,7 @@ Editing an existing app means reusing its `app_id` — never `app_create`. Resol
244
247
  - **`app_update`** — rename or change description/schema, and/or rewrite files via `source_files`, in one call. It recompiles for you, so no separate `app_refresh` is needed. Use this instead of editing `<slug>.json` by hand.
245
248
  - **Full rebrand** — still iteration, edit the existing files.
246
249
 
247
- Then `app_refresh(app_id)` ONCE (unless you used `app_update`, which already recompiled). If the change is substantial, `app_open(app_id, open_mode: "preview")` for a fresh card; for small tweaks the existing card stays valid.
250
+ Then compile ONCE (`assistant apps refresh <name>` or `app_refresh(app_id)`) unless you used `app_update`, which already recompiled. If the change is substantial, `app_open(app_id, open_mode: "preview")` for a fresh card; for small tweaks the existing card stays valid.
248
251
 
249
252
  > ⚠️ **`skill_load("app-builder")` is required before every `app_*` call** (including the first `app_create`). The skill can auto-unload between turns; without the reload, `app_refresh` / `app_open` error with "not currently active." It's idempotent — call it every time.
250
253
 
@@ -350,7 +353,7 @@ Slide decks are a different domain — skip app patterns (contextual headers, se
350
353
  ## SKILL COMPLETE WHEN
351
354
 
352
355
  - [ ] Request was scoped: personal build (sandbox) or complex/shippable (handed off to a project folder + coding agent)
353
- - [ ] **Sandbox path:** `app_create` returned an `app_id`; all files written via `file_write`; `app_refresh` ran ONCE clean; `app_open(open_mode: "preview")` rendered the card; user told what was built (3-6 bullets); iterations reflected live
356
+ - [ ] **Sandbox path:** `app_create` returned an `app_id`; all files written via `file_write`; compile ran ONCE clean (`assistant apps refresh` or `app_refresh`); `app_open(open_mode: "preview")` rendered the card; user told what was built (3-6 bullets); iterations reflected live
354
357
  - [ ] **Handoff path:** project folder established; coding agent spawned via `acp_spawn({ task, cwd })`; user told work continues in the folder
355
358
 
356
359
  ---
@@ -143,7 +143,7 @@ Older routes were written with a second `context` argument (`context.assistantEv
143
143
 
144
144
  ## Key rules
145
145
 
146
- - Always create the route handler files via `file_write` before calling `app_refresh`
146
+ - Always create the route handler files via `file_write` before compiling (`app_refresh` or `assistant apps refresh`)
147
147
  - Export an optional `description` string for CLI discoverability (`assistant routes list`)
148
148
  - Handlers have full Node.js API access — `fs`, `path`, `crypto`, etc.
149
149
  - Handlers get a 30-second timeout per request
@@ -125,7 +125,7 @@
125
125
  },
126
126
  {
127
127
  "name": "messaging_send",
128
- "description": "Send a message, reply to a thread, or create a draft. On Gmail and Outlook, always creates a real mailbox draft for review (recipients optional on Outlook). Supports replies (via thread_id), attachments (via attachment_paths, Gmail and Outlook), and all messaging platforms.",
128
+ "description": "Send a message, reply to a thread, or create a draft. On Gmail and Outlook, always creates a real mailbox draft for review (recipients optional on Outlook). On a channel (Slack, Telegram, Discord, WhatsApp) the message goes out as the channel's own bot, threaded when thread_id names a thread. Supports replies (via thread_id) and attachments (via attachment_paths, Gmail and Outlook).",
129
129
  "category": "messaging",
130
130
  "risk": "high",
131
131
  "input_schema": {
@@ -133,11 +133,11 @@
133
133
  "properties": {
134
134
  "platform": {
135
135
  "type": "string",
136
- "description": "Platform (e.g. \"gmail\"). Auto-detected if only one is connected. Slack is not supported — use the Slack Web API directly."
136
+ "description": "Platform (e.g. \"gmail\", \"slack\", \"telegram\", \"discord\"). Auto-detected if only one is connected."
137
137
  },
138
138
  "account": {
139
139
  "type": "string",
140
- "description": "Email address of the account to use. Required when multiple accounts are connected for the same platform. If omitted, uses the most recently connected account."
140
+ "description": "Email address of the account to use. Required when multiple accounts are connected for the same email platform. If omitted, uses the most recently connected account. Does not apply to a channel: a channel send goes out as the channel's own bot."
141
141
  },
142
142
  "conversation_id": {
143
143
  "type": "string",
@@ -1,7 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { basename } from "node:path";
3
3
 
4
- import { isChannelId } from "../../../../channels/types.js";
5
4
  import type { OutboundAttachment } from "../../../../messaging/provider-types.js";
6
5
  import {
7
6
  createDraft,
@@ -16,11 +15,10 @@ import {
16
15
  toOutlookFileAttachments,
17
16
  } from "../../../../messaging/providers/outlook/client.js";
18
17
  import type { OutlookDraftMessage } from "../../../../messaging/providers/outlook/types.js";
19
- import { resolveProactiveHomeConversation } from "../../../../notifications/conversation-pairing.js";
20
- import { recordDeliveredChannelPost } from "../../../../notifications/delivered-post-record.js";
21
- import { getConversation } from "../../../../persistence/conversation-crud.js";
22
- import { syncMessageToDisk } from "../../../../persistence/conversation-disk-view.js";
23
- import { normalizeExternalThreadId } from "../../../../persistence/external-conversation-store.js";
18
+ import {
19
+ isProactivelyAddressable,
20
+ sendChannelText,
21
+ } from "../../../../runtime/channel-send.js";
24
22
  import {
25
23
  isAbortLikeError,
26
24
  throwIfCancelled,
@@ -29,7 +27,6 @@ import type {
29
27
  ToolContext,
30
28
  ToolExecutionResult,
31
29
  } from "../../../../tools/types.js";
32
- import { getLogger } from "../../../../util/logger.js";
33
30
  import { guessMimeType } from "../../../../util/mime-type.js";
34
31
  import {
35
32
  err,
@@ -42,8 +39,6 @@ import {
42
39
  resolveProvider,
43
40
  } from "./shared.js";
44
41
 
45
- const log = getLogger("messaging-send");
46
-
47
42
  /** Read attachment files from disk into in-memory parts for outbound sending. */
48
43
  async function readAttachments(paths: string[]): Promise<OutboundAttachment[]> {
49
44
  return Promise.all(
@@ -58,123 +53,6 @@ async function readAttachments(paths: string[]): Promise<OutboundAttachment[]> {
58
53
  /** Email providers that accept file attachments on outbound sends. */
59
54
  const ATTACHMENT_CAPABLE_PLATFORMS = new Set(["gmail", "outlook"]);
60
55
 
61
- /**
62
- * Record a message the provider just accepted where the chat's proactive
63
- * posts live, so the chat's own conversation and `recall` can see what was
64
- * sent from elsewhere (a scheduled run, another conversation).
65
- *
66
- * Only channel providers have such a home; an email send is tool-mediated
67
- * and has no chat conversation. The row is written after the provider
68
- * returned the message id, never before, and carries that id on its
69
- * envelope and in `channel_outbound_posts` like every other post the daemon
70
- * makes.
71
- *
72
- * A send into the sending turn's own chat writes no row: its tool call and
73
- * result already sit in that conversation's history, and a second assistant
74
- * row beside the tool pair would break history repair. Whether the send is
75
- * "its own" is decided by the delivered target against the turn, not by the
76
- * home: the turn arrived on a channel, in a chat, in a thread (the turn-local
77
- * snapshot on the tool context), and a post the provider delivered to exactly
78
- * that place is a same-conversation send even when the chat's home resolves
79
- * elsewhere. The delivered thread is the one the provider reports, not the
80
- * one requested: a provider that ignores the request lands the post in the
81
- * thread-less chat, and the record must say so; a post into a thread is
82
- * recorded in the thread's own conversation, where its replies arrive. The home
83
- * comparison stays as the second test, for a sender that arrived through no
84
- * channel but is the home. The post is then in the outbound index only
85
- * through no path, which is the same class as a raw API send and is deferred
86
- * with it.
87
- *
88
- * Failures here never fail the send: the message is already out.
89
- */
90
- async function recordSentChannelPost(params: {
91
- providerId: string;
92
- externalChatId: string;
93
- /** The thread the provider delivered into, as it reported it. */
94
- deliveredThreadId: string | undefined;
95
- text: string;
96
- providerMessageId: string;
97
- sender: ToolContext;
98
- }): Promise<void> {
99
- const { providerId, externalChatId, sender } = params;
100
- if (!isChannelId(providerId) || !params.providerMessageId) {
101
- return;
102
- }
103
- try {
104
- if (
105
- isSendIntoOwnChat(
106
- sender,
107
- providerId,
108
- externalChatId,
109
- params.deliveredThreadId,
110
- )
111
- ) {
112
- return;
113
- }
114
- const home = await resolveProactiveHomeConversation({
115
- sourceChannel: providerId,
116
- externalChatId,
117
- threadId: params.deliveredThreadId,
118
- source: "notification",
119
- conversationType: "background",
120
- title: `Messages to ${externalChatId}`,
121
- });
122
- if (home.conversationId === sender.conversationId) {
123
- return;
124
- }
125
- const recorded = await recordDeliveredChannelPost({
126
- conversationId: home.conversationId,
127
- channel: providerId,
128
- externalChatId,
129
- ...(params.deliveredThreadId
130
- ? { threadId: params.deliveredThreadId }
131
- : {}),
132
- text: params.text,
133
- providerMessageId: params.providerMessageId,
134
- crossPostedFrom: sender.conversationId,
135
- });
136
- const homeConversation = getConversation(home.conversationId);
137
- if (homeConversation) {
138
- syncMessageToDisk(
139
- home.conversationId,
140
- recorded.messageId,
141
- homeConversation.createdAt,
142
- );
143
- }
144
- } catch (e) {
145
- log.warn(
146
- { err: e, provider: providerId, externalChatId },
147
- "Failed to record the sent message in the chat's conversation",
148
- );
149
- }
150
- }
151
-
152
- /**
153
- * True when the delivered chat and thread are the ones the sending turn
154
- * arrived in. Reads the turn-local snapshot the tool context carries
155
- * (`executionChannel`, `requesterChatId`, `sourceThreadId`), never the live
156
- * binding, which a concurrent inbound can rewrite while this call runs.
157
- * Thread ids compare in the binding store's normalized form, so an absent
158
- * thread on both sides matches and a thread-less delivery never matches a
159
- * turn that arrived in a thread.
160
- */
161
- function isSendIntoOwnChat(
162
- sender: ToolContext,
163
- channel: string,
164
- externalChatId: string,
165
- deliveredThreadId: string | undefined,
166
- ): boolean {
167
- if (!sender.executionChannel || !sender.requesterChatId) {
168
- return false;
169
- }
170
- return (
171
- sender.executionChannel === channel &&
172
- sender.requesterChatId === externalChatId &&
173
- normalizeExternalThreadId(sender.sourceThreadId) ===
174
- normalizeExternalThreadId(deliveredThreadId)
175
- );
176
- }
177
-
178
56
  export async function run(
179
57
  input: Record<string, unknown>,
180
58
  context: ToolContext,
@@ -197,9 +75,62 @@ export async function run(
197
75
  throwIfCancelled(context);
198
76
 
199
77
  try {
200
- const provider = await resolveProvider(platform);
78
+ // A channel whose transport can be addressed from a named chat is sent
79
+ // through the transport, which is the one send implementation for that
80
+ // channel, records what it sent, and reaches channels with no messaging
81
+ // provider at all. A platform named outright is checked first; one
82
+ // auto-detected from the connected providers is checked the same way.
83
+ const namedChannel =
84
+ platform && isProactivelyAddressable(platform) ? platform : undefined;
85
+ const provider = namedChannel ? undefined : await resolveProvider(platform);
86
+ const channel =
87
+ namedChannel ??
88
+ (provider && isProactivelyAddressable(provider.id)
89
+ ? provider.id
90
+ : undefined);
91
+
92
+ if (channel) {
93
+ if (attachmentPaths?.length) {
94
+ return err("Attachments are only supported on Gmail and Outlook.");
95
+ }
96
+ // A channel's transport sends as the channel's one bot identity, so an
97
+ // account cannot select where the send goes out from. Refusing is
98
+ // safer than silently sending from the default account.
99
+ if (input.account) {
100
+ return err(
101
+ `account does not apply to ${channel}: a channel send goes out as the channel's own bot.`,
102
+ );
103
+ }
104
+ // Recheck: provider resolution above is an await, and this posts to
105
+ // the channel.
106
+ throwIfCancelled(context);
107
+ const sent = await sendChannelText({
108
+ channel,
109
+ target: {
110
+ kind: "chat",
111
+ chatId: conversationId,
112
+ ...(threadId ? { threadId } : {}),
113
+ },
114
+ text,
115
+ renderRichly: true,
116
+ assistantId: context.assistantId,
117
+ sender: {
118
+ conversationId: context.conversationId,
119
+ executionChannel: context.executionChannel,
120
+ requesterChatId: context.requesterChatId,
121
+ sourceThreadId: context.sourceThreadId,
122
+ },
123
+ });
124
+ const threadSuffix = sent.threadId
125
+ ? `, "thread_id": "${sent.threadId}"`
126
+ : "";
127
+ return ok(`Message sent (ID: ${sent.lastMessageId}${threadSuffix}).`);
128
+ }
129
+ if (!provider) {
130
+ throw new Error(`Messaging provider "${platform}" not found.`);
131
+ }
201
132
 
202
- // Reject attachments on platforms that can't carry them (e.g. Telegram, WhatsApp).
133
+ // Reject attachments on platforms that can't carry them.
203
134
  if (
204
135
  attachmentPaths?.length &&
205
136
  !ATTACHMENT_CAPABLE_PLATFORMS.has(provider.id)
@@ -426,7 +357,14 @@ export async function run(
426
357
  );
427
358
  }
428
359
 
429
- // Non-email platforms
360
+ // A provider with a send of its own and no direct transport (a plugin's,
361
+ // for instance). Its send is tool-mediated and has no chat conversation
362
+ // to record in.
363
+ if (!provider.sendMessage) {
364
+ return err(
365
+ `${provider.displayName} cannot send from here: it has no send of its own and no channel transport to address.`,
366
+ );
367
+ }
430
368
  const attachments = attachmentPaths?.length
431
369
  ? await readAttachments(attachmentPaths)
432
370
  : undefined;
@@ -442,16 +380,6 @@ export async function run(
442
380
  const threadSuffix = result.threadId
443
381
  ? `, "thread_id": "${result.threadId}"`
444
382
  : "";
445
-
446
- await recordSentChannelPost({
447
- providerId: provider.id,
448
- externalChatId: conversationId,
449
- deliveredThreadId: result.threadId,
450
- text,
451
- providerMessageId: result.id,
452
- sender: context,
453
- });
454
-
455
383
  return ok(`Message sent (ID: ${result.id}${threadSuffix}).`);
456
384
  } catch (e) {
457
385
  // A cancelled turn is not a send failure: let it reach the executor's
@@ -23,7 +23,7 @@ sqlite3 "$VELLUM_WORKSPACE_DIR/data/db/assistant.db" \
23
23
 
24
24
  This returns all messages in chronological order with:
25
25
 
26
- - `role: "user"` - caller speech (prefixed with `[SPEAKER]` tags) and system events
26
+ - `role: "user"` - caller speech and system events
27
27
  - `role: "assistant"` - assistant responses, including `text` content and any `tool_use`/`tool_result` blocks
28
28
 
29
29
  ## Quick one-liner for the most recent call
@@ -124,7 +124,14 @@ their ear. Silence is the cue that it is their turn.
124
124
 
125
125
  **Advance on evidence, not on time.** Move to the next step when a fresh
126
126
  picture shows this one done, or when they tell you it is. Do not move on
127
- because a plausible amount of time has passed. If the next picture shows the
127
+ because a plausible amount of time has passed.
128
+
129
+ The most direct telling is automatic. When the user clicks the control an
130
+ arrow is pointing at, a message arrives as their turn saying they clicked it,
131
+ by name, and that mark comes down on its own. Treat it as the step done: say
132
+ what comes next and point at it. Nothing arrives for a click anywhere else,
133
+ and nothing arrives for a ring, so for those the picture and their words are
134
+ still the evidence. If the next picture shows the
128
135
  step not done, or done to the wrong thing, point at the same place again with
129
136
  a shorter caption and say what you saw. Pointing at the next step while the
130
137
  previous one is still open is how someone ends up two steps behind a mark.
@@ -53,7 +53,10 @@ describe("screen_point_at", () => {
53
53
  );
54
54
 
55
55
  expect(result.isError).toBe(true);
56
- expect(result.content).toContain("connected desktop client");
56
+ expect(result.content).toContain(
57
+ "The Vellum desktop app is needed to view or control your screen",
58
+ );
59
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
57
60
  });
58
61
 
59
62
  /** Drawing on the user's screen is a side effect a stopped turn must not have. */
@@ -3,7 +3,7 @@
3
3
  "tools": [
4
4
  {
5
5
  "name": "scaffold_managed_skill",
6
- "description": "Create or update a managed skill in {workspaceDir}/skills. The skill becomes available for skill_load when a valid top-level SKILL.md is written under the skill directory. Never persist a skill without explicit user consent. Before persisting, test the snippet: write to a temp file with bash and run with `bun run /tmp/vellum-eval/snippet.ts`. Iterate up to 3 attempts, then ask the user. Clean up temp files after. Do not use file_write for temp files outside the working directory. After a skill is written, the next turn may run in a recreated conversation due to file-watcher eviction - continue normally.",
6
+ "description": "Create or update a managed skill in {workspaceDir}/skills. The skill becomes available for skill_load when a valid top-level SKILL.md is written under the skill directory. Never persist a skill without explicit user consent. Before persisting, test the snippet: write to a temp file with bash and run with `bun run /tmp/vellum-eval/snippet.ts`. Iterate up to 3 attempts, then ask the user. Clean up temp files after. Do not use file_write for temp files outside the working directory.",
7
7
  "category": "skills",
8
8
  "risk": "high",
9
9
  "input_schema": {
@@ -92,7 +92,7 @@
92
92
  },
93
93
  {
94
94
  "name": "delete_managed_skill",
95
- "description": "Delete a managed skill directory from {workspaceDir}/skills. Never delete a skill without explicit user confirmation. After deletion, the next turn may run in a recreated conversation due to file-watcher eviction - continue normally.",
95
+ "description": "Delete a managed skill directory from {workspaceDir}/skills. Never delete a skill without explicit user confirmation.",
96
96
  "category": "skills",
97
97
  "risk": "high",
98
98
  "input_schema": {
@@ -137,7 +137,7 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
137
137
  // The managed latency class, also what the live-voice front model runs on.
138
138
  // Its leading tokens are the turn-taking verdict, so what this profile
139
139
  // optimizes is the tail of time-to-first-token rather than the median: a
140
- // verdict slower than `liveVoice.frontModel.endpointDecisionTimeoutMs`
140
+ // verdict slower than `voice.frontModel.endpointDecisionTimeoutMs`
141
141
  // trips the speculative fail-open commit in live-voice-session.ts, which
142
142
  // is audible dead air.
143
143
  //
package/src/config/env.ts CHANGED
@@ -229,8 +229,8 @@ export function setPlatformAssistantId(value: string | undefined): void {
229
229
  * Platform assistant ID — UUID of this assistant on the platform.
230
230
  *
231
231
  * Resolved from the in-memory override (populated by providers-setup
232
- * rehydration from the credential store at daemon startup, or by
233
- * secret-routes when the platform pushes the value).
232
+ * rehydration via platform validate, or by secret-routes when the
233
+ * platform pushes the value).
234
234
  */
235
235
  export function getPlatformAssistantId(): string {
236
236
  return _platformAssistantIdOverride ?? "";
@@ -29,18 +29,6 @@
29
29
  "description": "Enable user-hosted onboarding flow",
30
30
  "defaultEnabled": false
31
31
  },
32
- {
33
- "id": "proactive-tips",
34
- "scope": "client",
35
- "key": "proactive-tips",
36
- "label": "Proactive Tips",
37
- "description": "Gates the dismissible proactive tip card in the web sidebar. String-valued so future A/B arms can be added as new values.",
38
- "defaultEnabled": "off",
39
- "values": [
40
- "off",
41
- "on"
42
- ]
43
- },
44
32
  {
45
33
  "id": "vision-mode",
46
34
  "scope": "client",
@@ -523,6 +511,14 @@
523
511
  "label": "Send User Message Tool",
524
512
  "description": "Route all user-facing assistant text through the send_user_message tool instead of streamed assistant text",
525
513
  "defaultEnabled": false
514
+ },
515
+ {
516
+ "id": "assistant-desktop",
517
+ "scope": "assistant",
518
+ "key": "assistant-desktop",
519
+ "label": "Assistant Desktop",
520
+ "description": "Streams an interactive desktop from a containerized assistant pod to the web client over /v1/desktop/stream. Off: no desktop affordance is rendered. On: the chat header offers a desktop panel that views and controls the pod's display.",
521
+ "defaultEnabled": false
526
522
  }
527
523
  ]
528
524
  }
@@ -6,6 +6,7 @@ import {
6
6
  isMemoryV1Active,
7
7
  isMemoryV2ExplicitlyDisabled,
8
8
  isMemoryV3Live,
9
+ isSkillImprovementActive,
9
10
  isV2InjectionEngineActive,
10
11
  isV3TierActive,
11
12
  usesConceptPageMemory,
@@ -55,12 +56,16 @@ function makeConfig(
55
56
  enabled: TriState,
56
57
  v2Enabled: TriState,
57
58
  v3Live: TriState,
59
+ skillImprovement?: boolean,
58
60
  ): AssistantConfig {
59
61
  return {
60
62
  memory: {
61
63
  ...(enabled === undefined ? {} : { enabled }),
62
64
  ...(v2Enabled === undefined ? {} : { v2: { enabled: v2Enabled } }),
63
65
  ...(v3Live === undefined ? {} : { v3: { live: v3Live } }),
66
+ ...(skillImprovement === undefined
67
+ ? {}
68
+ : { retrospective: { skillImprovement } }),
64
69
  },
65
70
  } as AssistantConfig;
66
71
  }
@@ -296,6 +301,32 @@ describe("v1-staleness guard is one condition", () => {
296
301
  * proc-to-skills alias of `isMemoryV3Live` (which did not). One predicate now
297
302
  * answers for both features.
298
303
  */
304
+ describe("skill improvement gate", () => {
305
+ test("defaults on for a live v3 tier", () => {
306
+ expect(isSkillImprovementActive(makeConfig(true, undefined, true))).toBe(
307
+ true,
308
+ );
309
+ expect(isSkillImprovementActive(makeConfig(undefined, undefined, true))).toBe(
310
+ true,
311
+ );
312
+ });
313
+
314
+ test("explicit opt-out disables skill improvement without disabling v3", () => {
315
+ const config = makeConfig(true, undefined, true, false);
316
+
317
+ expect(isV3TierActive(config)).toBe(true);
318
+ expect(isSkillImprovementActive(config)).toBe(false);
319
+ });
320
+
321
+ test("requires the live v3 tier even when explicitly enabled", () => {
322
+ const v2Config = makeConfig(true, true, false, true);
323
+ const memoryOffConfig = makeConfig(false, undefined, true, true);
324
+
325
+ expect(isSkillImprovementActive(v2Config)).toBe(false);
326
+ expect(isSkillImprovementActive(memoryOffConfig)).toBe(false);
327
+ });
328
+ });
329
+
299
330
  describe("v3 tier is one condition", () => {
300
331
  test("memory off suppresses the v3 tier even with memory.v3.live set", () => {
301
332
  const config = makeConfig(false, undefined, true);
@@ -81,28 +81,32 @@ export function isMemoryV3Live(config: AssistantConfig): boolean {
81
81
  * FEATURE asks this predicate; only the injection-suppression checks that must
82
82
  * mirror the raw `memory.v3.live` key ask {@link isMemoryV3Live} directly.
83
83
  *
84
- * The two features gated on it, and why they must agree:
84
+ * The memory concept graph is gated on it: both `GET /memory-graph`
85
+ * (`supported`) and the cheap `graph_supported` bit on `GET /memory/stats`
86
+ * must agree with whether the graph can build from the v3 concept-page
87
+ * substrate.
85
88
  *
86
- * - The memory concept graph — the single source of truth for both
87
- * `GET /memory-graph` (`supported`) and the cheap `graph_supported` bit on
88
- * `GET /memory/stats`, so the advertised capability and the actual build can
89
- * never drift. The graph builds off the v3 concept-page substrate.
90
- * - Procedural-memory-as-skills — the retrospective's skill-authoring step and
91
- * its permission grant. Scoped to the v3 tier because skill retrieval rides
92
- * the v3 lanes and the usage-prune stage lives in the v3 maintain job. That
93
- * prune ships observe-first: with `memory.maintenance.skillPruneDays` at its
94
- * default (`null`) it reports stale assistant-authored skills
95
- * (`prunableSkills`) but deletes none — so a v3-tier assistant authors skills
96
- * without an automatic retirement bound until a positive `skillPruneDays` is
97
- * configured.
98
- *
99
- * Both write into memory on the user's behalf, so both honor the Memory
100
- * opt-out identically: one predicate, one answer.
89
+ * Retrospective skill improvement is a separate optional v3-tier feature.
90
+ * Ask {@link isSkillImprovementActive} for it so an operator can stop managed
91
+ * skill authoring without turning off ordinary retrospective memory capture.
101
92
  */
102
93
  export function isV3TierActive(config: AssistantConfig): boolean {
103
94
  return isMemoryEnabled(config) && isMemoryV3Live(config);
104
95
  }
105
96
 
97
+ /**
98
+ * Whether a retrospective may turn observed procedures into managed skills.
99
+ * This requires the v3 tier because skills are retrieved through its memory
100
+ * substrate, and it honors the retrospective-specific opt-out without
101
+ * disabling ordinary retrospective fact retention.
102
+ */
103
+ export function isSkillImprovementActive(config: AssistantConfig): boolean {
104
+ return (
105
+ isV3TierActive(config) &&
106
+ config.memory?.retrospective?.skillImprovement !== false
107
+ );
108
+ }
109
+
106
110
  /**
107
111
  * Minimal structural view of the memory config the concept-page gate reads.
108
112
  * Accepting the shape (rather than the full `MemoryConfig`) lets call sites