@vellumai/assistant 0.12.1-staging.1 → 0.12.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 (404) hide show
  1. package/AGENTS.md +0 -4
  2. package/ARCHITECTURE.md +12 -11
  3. package/docs/architecture/turn-actor.md +8 -15
  4. package/openapi.yaml +1 -291
  5. package/package.json +1 -1
  6. package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
  7. package/src/__tests__/app-compiler.test.ts +0 -6
  8. package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
  9. package/src/__tests__/app-dir-path-guard.test.ts +3 -1
  10. package/src/__tests__/app-source-watcher.test.ts +218 -0
  11. package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
  12. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
  13. package/src/__tests__/computer-use-tools.test.ts +5 -17
  14. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  15. package/src/__tests__/config-schema.test.ts +29 -28
  16. package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
  17. package/src/__tests__/config-watcher.test.ts +8 -23
  18. package/src/__tests__/connection-model-compat.test.ts +2 -2
  19. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  20. package/src/__tests__/conversation-pairing.test.ts +0 -26
  21. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
  22. package/src/__tests__/conversation-queue.test.ts +207 -48
  23. package/src/__tests__/conversation-retry-discard.test.ts +1 -54
  24. package/src/__tests__/conversation-skill-tools.test.ts +0 -27
  25. package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
  26. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
  27. package/src/__tests__/conversation-title-service.test.ts +10 -14
  28. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
  29. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
  30. package/src/__tests__/credential-record-retire.test.ts +1 -171
  31. package/src/__tests__/credential-routes.test.ts +3 -212
  32. package/src/__tests__/credential-security-invariants.test.ts +1 -2
  33. package/src/__tests__/cu-unified-flow.test.ts +2 -8
  34. package/src/__tests__/drain-kick-guard.test.ts +2 -0
  35. package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
  36. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  37. package/src/__tests__/host-bash-proxy.test.ts +45 -26
  38. package/src/__tests__/host-browser-proxy.test.ts +17 -6
  39. package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
  40. package/src/__tests__/host-file-proxy.test.ts +23 -11
  41. package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
  42. package/src/__tests__/llm-catalog-parity.test.ts +0 -25
  43. package/src/__tests__/mcp-auth-routes.test.ts +9 -6
  44. package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
  45. package/src/__tests__/mcp-health-check.test.ts +11 -8
  46. package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
  47. package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
  48. package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
  49. package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
  50. package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
  51. package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
  52. package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
  53. package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
  54. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  55. package/src/__tests__/messaging-send-tool.test.ts +548 -355
  56. package/src/__tests__/model-intents.test.ts +0 -3
  57. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  58. package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
  59. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
  60. package/src/__tests__/script-proxy-certs.test.ts +0 -35
  61. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  62. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  63. package/src/__tests__/speaker-identification.test.ts +56 -0
  64. package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
  65. package/src/__tests__/subagent-tools.test.ts +2 -3
  66. package/src/__tests__/telephony-tts-guard.test.ts +3 -38
  67. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
  68. package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
  69. package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
  70. package/src/__tests__/workspace-git-service.test.ts +0 -26
  71. package/src/acp/__tests__/agent-process.test.ts +4 -258
  72. package/src/acp/__tests__/auth-required.test.ts +0 -96
  73. package/src/acp/__tests__/client-handler.test.ts +2 -90
  74. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
  75. package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
  76. package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
  77. package/src/acp/__tests__/session-manager.test.ts +4 -895
  78. package/src/acp/agent-process.test.ts +1 -4
  79. package/src/acp/agent-process.ts +9 -94
  80. package/src/acp/auth-required.ts +1 -50
  81. package/src/acp/auto-install.test.ts +7 -9
  82. package/src/acp/client-handler.ts +3 -16
  83. package/src/acp/resolve-agent.test.ts +6 -220
  84. package/src/acp/resolve-agent.ts +23 -88
  85. package/src/acp/session-manager.test.ts +3 -196
  86. package/src/acp/session-manager.ts +18 -441
  87. package/src/acp/types.ts +5 -50
  88. package/src/api/events/app-files-changed.ts +3 -4
  89. package/src/api/index.ts +0 -6
  90. package/src/apps/app-store.ts +1 -1
  91. package/src/bundler/app-compiler.ts +0 -15
  92. package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
  93. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
  94. package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
  95. package/src/calls/call-constants.ts +4 -24
  96. package/src/calls/call-controller.ts +199 -499
  97. package/src/calls/call-transport.ts +0 -16
  98. package/src/calls/media-stream-output.ts +0 -32
  99. package/src/calls/media-stream-server.ts +30 -82
  100. package/src/calls/media-stream-stt-session.ts +0 -15
  101. package/src/calls/resolve-call-tts-provider.ts +2 -33
  102. package/src/calls/speaker-identification.ts +233 -0
  103. package/src/calls/tts-call-strategy.ts +5 -9
  104. package/src/calls/types.ts +0 -2
  105. package/src/calls/voice-control-protocol.ts +21 -53
  106. package/src/calls/voice-session-bridge.ts +46 -59
  107. package/src/calls/voice-triage-escalate.ts +56 -175
  108. package/src/cli/commands/apps.help.ts +13 -67
  109. package/src/cli/commands/apps.ts +0 -183
  110. package/src/cli/commands/channels/index.help.ts +2 -70
  111. package/src/cli/commands/channels/index.ts +3 -16
  112. package/src/cli/commands/credentials.help.ts +2 -6
  113. package/src/cli/commands/credentials.ts +2 -2
  114. package/src/cli/commands/mcp.help.ts +8 -8
  115. package/src/cli/commands/mcp.ts +1 -1
  116. package/src/cli/commands/roadmap.help.ts +1 -5
  117. package/src/cli/commands/roadmap.ts +1 -1
  118. package/src/cli/lib/bundled-marketplace.json +90 -103
  119. package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
  120. package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
  121. package/src/config/__tests__/platform-rehydration.test.ts +0 -93
  122. package/src/config/acp-defaults.test.ts +3 -13
  123. package/src/config/acp-defaults.ts +6 -23
  124. package/src/config/acp-schema.ts +2 -30
  125. package/src/config/bundled-skills/acp/SKILL.md +14 -22
  126. package/src/config/bundled-skills/acp/TOOLS.json +1 -5
  127. package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
  128. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  129. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  130. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
  131. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  132. package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
  133. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
  134. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  135. package/src/config/default-profile-catalog.ts +1 -1
  136. package/src/config/env.ts +2 -2
  137. package/src/config/feature-flag-registry.json +12 -8
  138. package/src/config/memory-v3-gate.test.ts +0 -31
  139. package/src/config/memory-v3-gate.ts +16 -20
  140. package/src/config/platform-rehydration.ts +21 -75
  141. package/src/config/schema.ts +2 -2
  142. package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
  143. package/src/config/schemas/calls.ts +28 -1
  144. package/src/config/schemas/live-voice.ts +173 -1
  145. package/src/config/schemas/mcp.ts +1 -1
  146. package/src/config/schemas/memory-retrospective.ts +0 -10
  147. package/src/config/schemas/skills.ts +14 -0
  148. package/src/credential-execution/process-manager.test.ts +19 -18
  149. package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
  150. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
  151. package/src/daemon/app-source-watcher.ts +218 -0
  152. package/src/daemon/config-watcher.ts +194 -13
  153. package/src/daemon/conversation-process.ts +64 -35
  154. package/src/daemon/conversation-skill-tools.ts +31 -13
  155. package/src/daemon/conversation-store.ts +1 -1
  156. package/src/daemon/conversation-surfaces.ts +8 -10
  157. package/src/daemon/conversation-tool-setup.ts +43 -37
  158. package/src/daemon/conversation.ts +3 -0
  159. package/src/daemon/host-bash-proxy.ts +12 -8
  160. package/src/daemon/host-browser-proxy.ts +1 -15
  161. package/src/daemon/host-file-proxy.ts +10 -8
  162. package/src/daemon/host-transfer-proxy.ts +18 -14
  163. package/src/daemon/lifecycle.ts +5 -0
  164. package/src/daemon/mcp-reload-service.ts +5 -5
  165. package/src/daemon/message-types/sync.ts +0 -1
  166. package/src/daemon/providers-setup.ts +1 -1
  167. package/src/daemon/shutdown-handlers.ts +9 -1
  168. package/src/daemon/skill-memory-refresh.ts +0 -64
  169. package/src/daemon/tool-side-effects.ts +18 -7
  170. package/src/hooks/types.ts +0 -33
  171. package/src/inbound/platform-callback-registration.ts +5 -4
  172. package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
  173. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  174. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  175. package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
  176. package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
  177. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
  178. package/src/live-voice/live-voice-session.ts +693 -213
  179. package/src/{calls → live-voice}/progress-narration.ts +2 -2
  180. package/src/live-voice/protocol.ts +2 -0
  181. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  182. package/src/mcp/effective-config.ts +3 -3
  183. package/src/mcp/mcp-header-store.ts +45 -29
  184. package/src/mcp/startup.ts +4 -8
  185. package/src/messaging/provider.ts +3 -10
  186. package/src/messaging/providers/callback-routing.ts +0 -18
  187. package/src/messaging/providers/channel-transport.ts +0 -56
  188. package/src/messaging/providers/discord/transport.ts +0 -17
  189. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
  190. package/src/messaging/providers/slack/adapter.ts +44 -7
  191. package/src/messaging/providers/slack/transport.ts +0 -16
  192. package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
  193. package/src/messaging/providers/telegram-bot/client.ts +26 -0
  194. package/src/messaging/providers/telegram-bot/transport.ts +0 -20
  195. package/src/messaging/providers/whatsapp/adapter.ts +48 -1
  196. package/src/messaging/providers/whatsapp/client.ts +29 -0
  197. package/src/messaging/providers/whatsapp/transport.ts +0 -18
  198. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
  199. package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
  200. package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
  201. package/src/notifications/assistant-reply-producer.ts +3 -8
  202. package/src/notifications/conversation-pairing.ts +16 -36
  203. package/src/notifications/delivered-post-record.ts +1 -11
  204. package/src/notifications/home-feed-side-effect.ts +7 -55
  205. package/src/outbound-proxy/certs.ts +5 -3
  206. package/src/permissions/checker.test.ts +6 -11
  207. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  208. package/src/persistence/conversation-crud.ts +63 -71
  209. package/src/persistence/conversation-lineage.ts +4 -12
  210. package/src/persistence/conversation-title-service.ts +26 -9
  211. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  212. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  213. package/src/persistence/message-reads.ts +0 -30
  214. package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
  215. package/src/persistence/schema/memory-core.ts +0 -5
  216. package/src/platform/client.test.ts +0 -2
  217. package/src/platform/client.ts +10 -2
  218. package/src/plugin-api/constants.ts +0 -2
  219. package/src/plugin-api/index.ts +0 -1
  220. package/src/plugin-api/types.ts +0 -2
  221. package/src/plugins/defaults/index.ts +0 -2
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
  224. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
  225. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
  226. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
  227. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
  228. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
  229. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
  230. package/src/plugins/defaults/memory/hooks/init.ts +0 -10
  231. package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
  232. package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
  233. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
  234. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
  235. package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
  236. package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
  237. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
  238. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
  239. package/src/plugins/defaults/memory/startup.ts +1 -6
  240. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
  241. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  242. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
  243. package/src/plugins/defaults/memory/worker.ts +0 -6
  244. package/src/plugins/mcp-servers.ts +75 -15
  245. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  246. package/src/providers/gemini/client.ts +23 -9
  247. package/src/providers/model-catalog.ts +0 -34
  248. package/src/providers/model-intents.ts +5 -10
  249. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
  250. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
  251. package/src/providers/openai/chat-completions-provider.ts +78 -192
  252. package/src/providers/openai/codex-models.ts +4 -0
  253. package/src/runtime/AGENTS.md +0 -2
  254. package/src/runtime/auth/same-actor.ts +3 -38
  255. package/src/runtime/http-server.ts +151 -160
  256. package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
  257. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
  258. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
  259. package/src/runtime/routes/acp-routes.test.ts +0 -15
  260. package/src/runtime/routes/acp-routes.ts +9 -72
  261. package/src/runtime/routes/app-management-routes.ts +0 -73
  262. package/src/runtime/routes/app-routes.ts +1 -1
  263. package/src/runtime/routes/conversation-list-routes.ts +3 -14
  264. package/src/runtime/routes/conversation-query-routes.ts +0 -40
  265. package/src/runtime/routes/credential-routes.ts +10 -19
  266. package/src/runtime/routes/diagnostics-routes.ts +3 -55
  267. package/src/runtime/routes/index.ts +0 -4
  268. package/src/runtime/routes/log-export-routes.ts +0 -2
  269. package/src/runtime/routes/mcp-auth-routes.ts +47 -39
  270. package/src/runtime/routes/migration-routes.ts +21 -8
  271. package/src/runtime/routes/platform-routes.ts +15 -18
  272. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
  273. package/src/runtime/services/conversation-serializer.ts +3 -16
  274. package/src/runtime/sync/resource-sync-events.ts +1 -2
  275. package/src/schedule/worker.ts +1 -1
  276. package/src/security/ces-rpc-record-backend.ts +0 -7
  277. package/src/subagent/consult-context.ts +25 -12
  278. package/src/tools/acp/list-agents.test.ts +1 -3
  279. package/src/tools/acp/spawn.test.ts +11 -146
  280. package/src/tools/acp/spawn.ts +3 -21
  281. package/src/tools/acp/status.ts +3 -40
  282. package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
  283. package/src/tools/capability-offer.test.ts +2 -22
  284. package/src/tools/capability-offer.ts +1 -16
  285. package/src/tools/computer-use/definitions.ts +2 -4
  286. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
  287. package/src/tools/credentials/metadata-store.ts +0 -100
  288. package/src/tools/host-filesystem/edit.test.ts +1 -2
  289. package/src/tools/host-filesystem/edit.ts +2 -2
  290. package/src/tools/host-filesystem/read.test.ts +1 -2
  291. package/src/tools/host-filesystem/read.ts +2 -2
  292. package/src/tools/host-filesystem/transfer.test.ts +1 -2
  293. package/src/tools/host-filesystem/transfer.ts +2 -2
  294. package/src/tools/host-filesystem/write.test.ts +1 -2
  295. package/src/tools/host-filesystem/write.ts +2 -2
  296. package/src/tools/host-terminal/host-shell.test.ts +0 -16
  297. package/src/tools/host-terminal/host-shell.ts +20 -4
  298. package/src/tools/policy-context.ts +7 -6
  299. package/src/tools/subagent/spawn.ts +8 -5
  300. package/src/tools/watch/watch-retro-report.ts +8 -0
  301. package/src/util/__tests__/language-subtag.test.ts +1 -23
  302. package/src/util/fs-watcher-error.ts +36 -0
  303. package/src/util/language-subtag.ts +0 -16
  304. package/src/util/platform.ts +0 -8
  305. package/src/workspace/git-service.ts +54 -5
  306. package/src/workspace/migrations/registry.ts +0 -6
  307. package/scripts/smoke-desktop-window-theme.ts +0 -186
  308. package/src/__tests__/git-porcelain.test.ts +0 -47
  309. package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
  310. package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
  311. package/src/__tests__/resolve-app.test.ts +0 -135
  312. package/src/__tests__/source-fingerprint.test.ts +0 -184
  313. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
  314. package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
  315. package/src/acp/model-config.test.ts +0 -166
  316. package/src/acp/model-config.ts +0 -138
  317. package/src/api/events/acp-session-model-update.test.ts +0 -88
  318. package/src/api/events/acp-session-model-update.ts +0 -46
  319. package/src/apps/resolve-app.ts +0 -81
  320. package/src/apps/source-fingerprint.ts +0 -266
  321. package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
  322. package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
  323. package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
  324. package/src/calls/barge-in-guard.ts +0 -118
  325. package/src/calls/voice-leg-coordinator.ts +0 -189
  326. package/src/calls/voice-progress-cadence.ts +0 -423
  327. package/src/cli/commands/__tests__/apps.test.ts +0 -245
  328. package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
  329. package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
  330. package/src/cli/commands/channels/send.ts +0 -128
  331. package/src/config/__tests__/platform-identity.test.ts +0 -117
  332. package/src/config/acp-agent-write.ts +0 -43
  333. package/src/config/acp-schema.test.ts +0 -81
  334. package/src/config/platform-identity.ts +0 -144
  335. package/src/config/schemas/__tests__/voice.test.ts +0 -148
  336. package/src/config/schemas/voice.ts +0 -150
  337. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
  338. package/src/daemon/app-change-notify.ts +0 -45
  339. package/src/desktop/__tests__/fake-desktop.ts +0 -119
  340. package/src/desktop/desktop-chrome-policy.test.ts +0 -71
  341. package/src/desktop/desktop-chrome-policy.ts +0 -42
  342. package/src/desktop/desktop-dependencies.test.ts +0 -102
  343. package/src/desktop/desktop-dependencies.ts +0 -306
  344. package/src/desktop/desktop-feature.test.ts +0 -18
  345. package/src/desktop/desktop-feature.ts +0 -19
  346. package/src/desktop/desktop-panel-config.test.ts +0 -135
  347. package/src/desktop/desktop-panel-config.ts +0 -149
  348. package/src/desktop/desktop-session-manager.test.ts +0 -627
  349. package/src/desktop/desktop-session-manager.ts +0 -814
  350. package/src/desktop/desktop-stream-bridge.test.ts +0 -395
  351. package/src/desktop/desktop-stream-bridge.ts +0 -214
  352. package/src/desktop/desktop-wallpaper.test.ts +0 -191
  353. package/src/desktop/desktop-wallpaper.ts +0 -113
  354. package/src/desktop/desktop-window-theme.ts +0 -170
  355. package/src/i18n/AGENTS.md +0 -22
  356. package/src/i18n/__tests__/i18n.test.ts +0 -108
  357. package/src/i18n/index.ts +0 -16
  358. package/src/i18n/locales.ts +0 -93
  359. package/src/i18n/messages.ts +0 -61
  360. package/src/i18n/title.ts +0 -25
  361. package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
  362. package/src/mcp/spec-schema.ts +0 -111
  363. package/src/mcp/workspace-mcp-config.ts +0 -188
  364. package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
  365. package/src/persistence/conversation-title-placeholders.ts +0 -23
  366. package/src/persistence/message-cursor.ts +0 -78
  367. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
  368. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
  369. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
  370. package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
  371. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
  372. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
  373. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
  374. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
  375. package/src/providers/gemini-thought-signature.test.ts +0 -63
  376. package/src/providers/gemini-thought-signature.ts +0 -44
  377. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
  378. package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
  379. package/src/providers/openai/google-thought-signature.ts +0 -320
  380. package/src/runtime/__tests__/channel-send.test.ts +0 -443
  381. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
  382. package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
  383. package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
  384. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
  385. package/src/runtime/channel-send.ts +0 -316
  386. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
  387. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
  388. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
  389. package/src/runtime/routes/channel-send-routes.ts +0 -85
  390. package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
  391. package/src/runtime/routes/desktop-setup-routes.ts +0 -35
  392. package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
  393. package/src/security/ces-http-record-backend.ts +0 -120
  394. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
  395. package/src/skills/workspace-skill-md-signature.ts +0 -50
  396. package/src/tools/acp/status.test.ts +0 -94
  397. package/src/util/ca-bundle.ts +0 -13
  398. package/src/workspace/git-porcelain.ts +0 -66
  399. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
  400. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
  401. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
  402. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
  403. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
  404. /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
@@ -13,17 +13,15 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
13
13
  ]);
14
14
  });
15
15
 
16
- test("claude profile uses the @agentclientprotocol adapter binary on opus", () => {
16
+ test("claude profile uses the @agentclientprotocol adapter binary", () => {
17
17
  expect(DEFAULT_ACP_AGENT_PROFILES.claude).toEqual({
18
18
  command: "claude-agent-acp",
19
19
  args: [],
20
20
  description: "Claude Code (via @agentclientprotocol/claude-agent-acp)",
21
- model: "opus",
22
21
  });
23
22
  });
24
23
 
25
24
  test("codex profile uses the @agentclientprotocol adapter binary", () => {
26
- // No model: Codex aliases are its own, so the adapter's default stands.
27
25
  expect(DEFAULT_ACP_AGENT_PROFILES.codex).toEqual({
28
26
  command: "codex-acp",
29
27
  args: [],
@@ -46,19 +44,11 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
46
44
  describe("DEFAULT_AGENT_NPM_PACKAGES", () => {
47
45
  test("is keyed by command name with the canonical npm package", () => {
48
46
  expect(DEFAULT_AGENT_NPM_PACKAGES).toEqual({
49
- "claude-agent-acp": "@agentclientprotocol/claude-agent-acp@0.75.1",
50
- "codex-acp": "@agentclientprotocol/codex-acp@1.10.0",
47
+ "claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
48
+ "codex-acp": "@agentclientprotocol/codex-acp",
51
49
  });
52
50
  });
53
51
 
54
- test("every package spec pins an exact version", () => {
55
- // A bare name would let a clean install resolve whatever npm calls
56
- // latest, which is what the repo's pinning rule exists to prevent.
57
- for (const spec of Object.values(DEFAULT_AGENT_NPM_PACKAGES)) {
58
- expect(spec).toMatch(/^@?[^@]+@\d+\.\d+\.\d+$/);
59
- }
60
- });
61
-
62
52
  test("every default profile's command has a matching npm package", () => {
63
53
  for (const profile of Object.values(DEFAULT_ACP_AGENT_PROFILES)) {
64
54
  expect(DEFAULT_AGENT_NPM_PACKAGES[profile.command]).toBeDefined();
@@ -1,11 +1,5 @@
1
1
  import type { AcpAgentConfig } from "./acp-schema.js";
2
2
 
3
- /**
4
- * A bundled profile always names its command; a user entry for the same id
5
- * may omit `command` and inherit this one.
6
- */
7
- export type AcpAgentProfile = AcpAgentConfig & { command: string };
8
-
9
3
  // Shared frozen empty args array — these defaults are read by every ACP spawn,
10
4
  // so a single accidental `args.push(...)` from one caller would corrupt every
11
5
  // subsequent read. Cast through `unknown` because `AcpAgentConfig.args` is
@@ -19,22 +13,17 @@ const FROZEN_EMPTY_ARGS = Object.freeze([] as string[]) as unknown as string[];
19
13
  * falls back to this map so common agents like `claude` and `codex` Just Work
20
14
  * without requiring per-user config.
21
15
  *
22
- * A profile's `model` is the model a session of that agent starts on when the
23
- * ask names none, and is the rung `acp.agents.<id>.model` fills: a user config
24
- * entry that names a model overrides it, one that omits it inherits it.
25
- *
26
- * Keyed by agent id. Deeply frozen: the outer object, each profile, and the
27
- * `args` arrays, so mutation throws in strict mode rather than silently
16
+ * Keyed by agent id. Deeply frozen — the outer object, each profile, and the
17
+ * `args` arrays — so mutation throws in strict mode rather than silently
28
18
  * corrupting the shared defaults.
29
19
  */
30
20
  export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
31
- Record<string, AcpAgentProfile>
21
+ Record<string, AcpAgentConfig>
32
22
  > = Object.freeze({
33
23
  claude: Object.freeze({
34
24
  command: "claude-agent-acp",
35
25
  args: FROZEN_EMPTY_ARGS,
36
26
  description: "Claude Code (via @agentclientprotocol/claude-agent-acp)",
37
- model: "opus",
38
27
  }),
39
28
  codex: Object.freeze({
40
29
  command: "codex-acp",
@@ -44,21 +33,15 @@ export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
44
33
  });
45
34
 
46
35
  /**
47
- * Single source of truth for adapter binary → npm package spec. Automatic
36
+ * Single source of truth for adapter binary → npm package name. Automatic
48
37
  * installation and the resolver's install hints use this map, so a new
49
38
  * adapter only needs one entry here.
50
39
  *
51
40
  * Keyed by command name (not agent id) so the mapping follows the binary
52
41
  * regardless of how a user's config aliases an agent.
53
- *
54
- * Values carry an exact version, so a clean install lands the adapter version
55
- * the daemon was tested against instead of whatever npm calls latest today.
56
- * An adapter already on PATH is left alone: installation runs only when
57
- * preflight found no binary at all, so this never moves a version the user
58
- * installed themselves.
59
42
  */
60
43
  export const DEFAULT_AGENT_NPM_PACKAGES: Readonly<Record<string, string>> =
61
44
  Object.freeze({
62
- "claude-agent-acp": "@agentclientprotocol/claude-agent-acp@0.75.1",
63
- "codex-acp": "@agentclientprotocol/codex-acp@1.10.0",
45
+ "claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
46
+ "codex-acp": "@agentclientprotocol/codex-acp",
64
47
  });
@@ -1,15 +1,8 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { DEFAULT_ACP_AGENT_PROFILES } from "./acp-defaults.js";
4
-
5
- export const AcpAgentConfigSchema = z
3
+ const AcpAgentConfigSchema = z
6
4
  .object({
7
- command: z
8
- .string()
9
- .optional()
10
- .describe(
11
- "Command to spawn the ACP agent process. Required unless the id has a bundled profile, whose command is inherited when this is unset",
12
- ),
5
+ command: z.string().describe("Command to spawn the ACP agent process"),
13
6
  args: z
14
7
  .array(z.string())
15
8
  .default([])
@@ -22,12 +15,6 @@ export const AcpAgentConfigSchema = z
22
15
  .record(z.string(), z.string())
23
16
  .optional()
24
17
  .describe("Environment variables set for the agent process"),
25
- model: z
26
- .string()
27
- .optional()
28
- .describe(
29
- "Model sessions of this agent start on when the ask names none (an adapter-reported alias such as 'opus' or 'sonnet' for Claude, not an Assistant catalog id). Overrides the bundled profile's value; unset inherits it.",
30
- ),
31
18
  })
32
19
  .describe("Configuration for an individual ACP agent");
33
20
 
@@ -43,21 +30,6 @@ export const AcpConfigSchema = z
43
30
  ),
44
31
  agents: z
45
32
  .record(z.string(), AcpAgentConfigSchema)
46
- .superRefine((agents, ctx) => {
47
- for (const [id, agent] of Object.entries(agents)) {
48
- if (
49
- agent.command !== undefined ||
50
- Object.hasOwn(DEFAULT_ACP_AGENT_PROFILES, id)
51
- ) {
52
- continue;
53
- }
54
- ctx.addIssue({
55
- code: "custom",
56
- path: [id, "command"],
57
- message: `acp.agents.${id} needs a command: only the bundled ids (${Object.keys(DEFAULT_ACP_AGENT_PROFILES).join(", ")}) inherit one`,
58
- });
59
- }
60
- })
61
33
  .default({})
62
34
  .describe("Map of agent names to their configurations"),
63
35
  })
@@ -23,18 +23,6 @@ Use `acp_spawn` to delegate a coding task to an external agent. The agent runs a
23
23
 
24
24
  Users can refer to agents by natural names: "claude code", "codex cli", and "openai codex" all resolve to the canonical `claude` and `codex` ids (unless the user's config defines an agent literally keyed by that name, which always wins).
25
25
 
26
- ## Choosing a model
27
-
28
- Claude runs on Opus unless the user names a model; every other agent starts on its own default.
29
-
30
- When the user does name one, pass it as `model` on `acp_spawn`. Model names are the agent's own vocabulary, not Assistant model ids: an alias such as `default`, `sonnet`, `opus`, `haiku`, `fable`, or `opusplan` for Claude, or a full model id. Pass what the user said and let the agent resolve it.
31
-
32
- The spawn result reports `requestedModel` and `effectiveModel`. Treat `effectiveModel` as authoritative for the top-level ACP session. Never infer that model from the task text or from a nested subagent. When `requestedModel` is non-null and differs from `effectiveModel`, state which model the session is actually running on and relay `modelWarning` when present.
33
-
34
- If the agent refuses the model, or advertises no model selector, the spawn result says so: relay it in one sentence and carry on, because the session is live on the agent's own model.
35
-
36
- A session runs on the model it started on, so a different model means a new `acp_spawn`. A standing default per agent lives in the config at `acp.agents.<id>.model`.
37
-
38
26
  ## When the user names Claude Code or Codex
39
27
 
40
28
  If they name Claude Code or Codex without asking you to run it here (for example they say that tool will do the work), offer once, in one short sentence, that you can connect and run it in this conversation. Then continue with whatever they were doing.
@@ -47,23 +35,21 @@ If they name Claude Code or Codex without asking you to run it here (for example
47
35
 
48
36
  ACP is always available - default profiles for `claude` and `codex` ship out-of-box, so no config edit is needed to start. First-time setup is just making the adapter binary available, then spawning:
49
37
 
50
- 1. Install the adapter binary if it's missing. This happens automatically: when `acp_spawn` finds the agent's binary missing from PATH, the assistant installs the pinned version via a sandboxed bun global install and proceeds in the same call (see "Automatic adapter availability" below).
38
+ 1. Install the adapter binary if it's missing. This happens automatically: when `acp_spawn` finds the agent's binary missing from PATH, the assistant installs it once via a sandboxed bun global install and proceeds in the same call (see "Automatic adapter availability" below).
51
39
 
52
40
  2. Call `acp_spawn`. Do NOT run `vellum sleep && vellum wake` - that kills the conversation.
53
41
 
54
42
  ## Automatic adapter availability
55
43
 
56
- When an agent's binary is missing from PATH, the assistant installs the adapter version it was built against via a sandboxed bun global install and then runs the real installed binary. The install runs in a fresh empty temporary directory (never the task's project directory), with known secrets stripped from the installer environment and the registry pinned to the public npm registry, so a malicious project directory cannot hijack package resolution or capture a token.
57
-
58
- An adapter already on PATH is left alone, whoever installed it: the install runs only when preflight found no binary at all.
44
+ When `acp_spawn` finds the agent's binary missing from PATH, the assistant installs it once via a sandboxed bun global install and then runs the real installed binary. The install runs in a fresh empty temporary directory (never the task's project directory), with known secrets stripped from the installer environment and the registry pinned to the public npm registry, so a malicious project directory cannot hijack package resolution or capture a token. After this one-time install, the adapter is a normal trusted binary on PATH and every later spawn (and resume) uses it directly.
59
45
 
60
46
  Only the allowlisted out-of-box packages are ever installed this way (`@agentclientprotocol/claude-agent-acp`, `@agentclientprotocol/codex-acp`); user-configured agents with custom commands are never installed automatically.
61
47
 
62
- Manual installation is fallback guidance for unusual setups: bun unavailable, restricted global installs, or an auto-install failure (the failure reason is surfaced in the tool result). Install the pinned version below rather than `@latest`, so the adapter matches what the assistant was built against.
48
+ Manual installation is fallback guidance for unusual setups: bun unavailable, restricted global installs, or an auto-install failure (the failure reason is surfaced in the tool result).
63
49
 
64
50
  ```bash
65
- bun add -g @agentclientprotocol/claude-agent-acp@0.75.1 # claude
66
- bun add -g @agentclientprotocol/codex-acp@1.10.0 # codex
51
+ bun add -g @agentclientprotocol/claude-agent-acp # claude
52
+ bun add -g @agentclientprotocol/codex-acp # codex
67
53
  ```
68
54
 
69
55
  ## Claude setup
@@ -98,14 +84,20 @@ Do NOT put API keys (or any secret) in the workspace config file - secrets never
98
84
 
99
85
  - Two agents are supported out-of-box: `claude` (via the `claude-agent-acp` adapter) and `codex` (via the `codex-acp` adapter).
100
86
  - NEVER use `claude`, `claude -p`, `claude --acp`, or the bare `codex` CLI as the ACP `command`. Claude and Codex only speak the protocol through their dedicated `*-acp` adapters.
101
- - Default profiles for both ship out-of-box. Users only need an `agents.<id>` entry in config if they want to override the defaults (e.g. point to a custom binary path or pass extra args/env). An entry that still runs the bundled adapter, whether it omits `command` or names the same binary by name or full path, inherits the `command`, `description` and `model` it leaves out, so a single-field change such as `acp.agents.claude.model` is all it takes. An entry that points the id at a different binary stands on its own, so it must spell out everything it needs, `command` included.
87
+ - Default profiles for both ship out-of-box. Users only need an `agents.<id>` entry in config if they want to override the defaults (e.g. point to a custom binary path or pass extra args/env). An `acp.agents.<id>` entry replaces the bundled default entirely (no field merge), so any override must spell out the full `command` and `args`, not just the field being changed.
102
88
  - NEVER change an existing ACP config to use a different command. If the config already has `claude-agent-acp` or `codex-acp`, leave it alone.
103
89
 
104
90
  ## Updating an adapter
105
91
 
106
- Adapter upgrades ship with Assistant releases: the pinned version is what a missing adapter is installed at. An adapter already on PATH is never replaced, so a user who upgrades one themselves keeps that version.
92
+ Adapters are installed automatically when missing. To update an installed adapter, ask the user first and use its owning package manager. For bun installations:
93
+
94
+ ```bash
95
+ bun add -g @agentclientprotocol/claude-agent-acp@latest
96
+ # or
97
+ bun add -g @agentclientprotocol/codex-acp@latest
98
+ ```
107
99
 
108
- Codex uses the adapter's bundled dependency by default, within the version range declared by the adapter. If `CODEX_PATH` selects a separate CLI, update that installation separately.
100
+ Codex uses the adapter's bundled dependency by default, within the version range declared by the adapter. If `CODEX_PATH` selects a separate CLI, update that installation separately. Verify the required model with a fresh `acp_spawn` call after updating.
109
101
 
110
102
  ## When to use acp_steer vs acp_spawn
111
103
 
@@ -3,7 +3,7 @@
3
3
  "tools": [
4
4
  {
5
5
  "name": "acp_spawn",
6
- "description": "Spawn an external coding agent (e.g. Claude Code, Codex) via ACP to work on a task. Default profiles ship for `claude` (`claude-agent-acp`) and `codex` (`codex-acp`); the assistant resolves the agent id to the right binary. The result reports `requestedModel` and the adapter-reported `effectiveModel`; treat `effectiveModel` as authoritative for the top-level ACP session. The agent runs as a subprocess and streams results back. Use this when you want to delegate a coding task to an external agent that has its own tools, file editing, and terminal access. If a default agent's binary is missing, the assistant installs it once via a sandboxed bun global install and proceeds in the same call; if that fails (e.g. bun unavailable), an actionable install hint is returned - do NOT alter `agents.<id>.command` to swap binaries.",
6
+ "description": "Spawn an external coding agent (e.g. Claude Code, Codex) via ACP to work on a task. Default profiles ship for `claude` (`claude-agent-acp`) and `codex` (`codex-acp`); the assistant resolves the agent id to the right binary. The agent runs as a subprocess and streams results back. Use this when you want to delegate a coding task to an external agent that has its own tools, file editing, and terminal access. If a default agent's binary is missing, the assistant installs it once via a sandboxed bun global install and proceeds in the same call; if that fails (e.g. bun unavailable), an actionable install hint is returned - do NOT alter `agents.<id>.command` to swap binaries.",
7
7
  "category": "orchestration",
8
8
  "risk": "high",
9
9
  "input_schema": {
@@ -20,10 +20,6 @@
20
20
  "cwd": {
21
21
  "type": "string",
22
22
  "description": "Working directory for the agent. This determines where the agent runs and where its session is stored. Set this to the project/repo root the user wants the agent to work in. Defaults to current conversation's working directory."
23
- },
24
- "model": {
25
- "type": "string",
26
- "description": "Model the agent should run on, passed ONLY when the user named one (e.g. 'opus' for Claude). Left off, a Claude session runs on Opus and every other agent on its own default. If the agent refuses the value, or advertises no model selector, the result says so and the session runs on its own model."
27
23
  }
28
24
  },
29
25
  "required": ["task"]
@@ -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, generated by app_refresh or assistant apps refresh
63
+ dist/ # Compiled output — auto-generated by app_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` and a single compile (`assistant apps refresh` or `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` + `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 when you compile. Structure:
161
+ Apps are multi-file Preact + TSX projects; esbuild bundles automatically. 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. 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`).
182
+ 3. **`app_refresh`** ONCE at the end to compile.
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 compiling 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 `app_refresh` 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,14 +221,11 @@ 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
-
226
224
  ```
227
- assistant apps inspect <name>
228
- assistant apps refresh <name>
225
+ app_refresh(app_id)
229
226
  ```
230
227
 
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.
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.
232
229
 
233
230
  ### 5 — Show the preview card
234
231
 
@@ -247,7 +244,7 @@ Editing an existing app means reusing its `app_id` — never `app_create`. Resol
247
244
  - **`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.
248
245
  - **Full rebrand** — still iteration, edit the existing files.
249
246
 
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.
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.
251
248
 
252
249
  > ⚠️ **`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.
253
250
 
@@ -353,7 +350,7 @@ Slide decks are a different domain — skip app patterns (contextual headers, se
353
350
  ## SKILL COMPLETE WHEN
354
351
 
355
352
  - [ ] Request was scoped: personal build (sandbox) or complex/shippable (handed off to a project folder + coding agent)
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
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
357
354
  - [ ] **Handoff path:** project folder established; coding agent spawned via `acp_spawn({ task, cwd })`; user told work continues in the folder
358
355
 
359
356
  ---
@@ -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 compiling (`app_refresh` or `assistant apps refresh`)
146
+ - Always create the route handler files via `file_write` before calling `app_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). 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).",
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.",
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\", \"slack\", \"telegram\", \"discord\"). Auto-detected if only one is connected."
136
+ "description": "Platform (e.g. \"gmail\"). Auto-detected if only one is connected. Slack is not supported — use the Slack Web API directly."
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 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."
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."
141
141
  },
142
142
  "conversation_id": {
143
143
  "type": "string",
@@ -1,6 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { basename } from "node:path";
3
3
 
4
+ import { isChannelId } from "../../../../channels/types.js";
4
5
  import type { OutboundAttachment } from "../../../../messaging/provider-types.js";
5
6
  import {
6
7
  createDraft,
@@ -15,10 +16,11 @@ import {
15
16
  toOutlookFileAttachments,
16
17
  } from "../../../../messaging/providers/outlook/client.js";
17
18
  import type { OutlookDraftMessage } from "../../../../messaging/providers/outlook/types.js";
18
- import {
19
- isProactivelyAddressable,
20
- sendChannelText,
21
- } from "../../../../runtime/channel-send.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";
22
24
  import {
23
25
  isAbortLikeError,
24
26
  throwIfCancelled,
@@ -27,6 +29,7 @@ import type {
27
29
  ToolContext,
28
30
  ToolExecutionResult,
29
31
  } from "../../../../tools/types.js";
32
+ import { getLogger } from "../../../../util/logger.js";
30
33
  import { guessMimeType } from "../../../../util/mime-type.js";
31
34
  import {
32
35
  err,
@@ -39,6 +42,8 @@ import {
39
42
  resolveProvider,
40
43
  } from "./shared.js";
41
44
 
45
+ const log = getLogger("messaging-send");
46
+
42
47
  /** Read attachment files from disk into in-memory parts for outbound sending. */
43
48
  async function readAttachments(paths: string[]): Promise<OutboundAttachment[]> {
44
49
  return Promise.all(
@@ -53,6 +58,123 @@ async function readAttachments(paths: string[]): Promise<OutboundAttachment[]> {
53
58
  /** Email providers that accept file attachments on outbound sends. */
54
59
  const ATTACHMENT_CAPABLE_PLATFORMS = new Set(["gmail", "outlook"]);
55
60
 
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
+
56
178
  export async function run(
57
179
  input: Record<string, unknown>,
58
180
  context: ToolContext,
@@ -75,62 +197,9 @@ export async function run(
75
197
  throwIfCancelled(context);
76
198
 
77
199
  try {
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
- }
200
+ const provider = await resolveProvider(platform);
132
201
 
133
- // Reject attachments on platforms that can't carry them.
202
+ // Reject attachments on platforms that can't carry them (e.g. Telegram, WhatsApp).
134
203
  if (
135
204
  attachmentPaths?.length &&
136
205
  !ATTACHMENT_CAPABLE_PLATFORMS.has(provider.id)
@@ -357,14 +426,7 @@ export async function run(
357
426
  );
358
427
  }
359
428
 
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
- }
429
+ // Non-email platforms
368
430
  const attachments = attachmentPaths?.length
369
431
  ? await readAttachments(attachmentPaths)
370
432
  : undefined;
@@ -380,6 +442,16 @@ export async function run(
380
442
  const threadSuffix = result.threadId
381
443
  ? `, "thread_id": "${result.threadId}"`
382
444
  : "";
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
+
383
455
  return ok(`Message sent (ID: ${result.id}${threadSuffix}).`);
384
456
  } catch (e) {
385
457
  // 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 and system events
26
+ - `role: "user"` - caller speech (prefixed with `[SPEAKER]` tags) 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,14 +124,7 @@ 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.
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
127
+ because a plausible amount of time has passed. If the next picture shows the
135
128
  step not done, or done to the wrong thing, point at the same place again with
136
129
  a shorter caption and say what you saw. Pointing at the next step while the
137
130
  previous one is still open is how someone ends up two steps behind a mark.