@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
@@ -5,7 +5,7 @@
5
5
  * The voice front door owns hold, escalation, and direct-response behavior.
6
6
  */
7
7
 
8
- import type { VoiceProgressConfig } from "../config/schemas/voice.js";
8
+ import type { LiveVoiceProgressConfig } from "../config/schemas/live-voice.js";
9
9
  import {
10
10
  extractToolUse,
11
11
  getConfiguredProvider,
@@ -216,7 +216,7 @@ export function effectiveSpokenTextMaxChars(
216
216
  }
217
217
 
218
218
  export function createVoiceProgressNarrator(options: {
219
- config: VoiceProgressConfig;
219
+ config: LiveVoiceProgressConfig;
220
220
  /** Provider resolver, injectable for tests. */
221
221
  getProvider?: () => Promise<Provider | null>;
222
222
  }): VoiceProgressNarrator {
@@ -627,6 +627,8 @@ export interface LiveVoiceMetricsServerFrame extends LiveVoiceServerFrameBase {
627
627
  * condition as the two fields above.
628
628
  */
629
629
  readonly endpointDecisionSource?: "front-door" | "provider";
630
+ /** Which floor-holding ack actually spoke during the turn, if any. */
631
+ readonly ackSpoken?: "first_delta" | "tool_use";
630
632
  /**
631
633
  * Spoken progress narrations during the turn. Present only when at least
632
634
  * one progress update spoke (otherwise the field is absent, keeping frames
@@ -4,8 +4,8 @@
4
4
  * The properties worth pinning are the ones a refactor could quietly
5
5
  * break, each of which fails silently rather than loudly:
6
6
  *
7
- * 1. Plugin servers reach the manager at all. Reading only the workspace
8
- * `mcp.json` leaves a plugin's tools missing with nothing logged.
7
+ * 1. Plugin servers reach the manager at all. Reading only `config.mcp`
8
+ * leaves a plugin's tools missing with nothing logged.
9
9
  * 2. A workspace entry of the same id wins, so a plugin cannot redirect a
10
10
  * server the user configured by hand.
11
11
  * 3. Every server is attributed. `source` is what `McpClient` reads to
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * The MCP configuration the daemon actually runs.
3
3
  *
4
- * Two sources contribute servers: the workspace `mcp.json`, which the
4
+ * Two sources contribute servers: the workspace `config.json`, which the
5
5
  * user owns, and each installed plugin's `mcp.json`, which its author
6
6
  * owns. Both must reach the MCP manager, so every consumer that starts
7
- * servers builds its config through here rather than reading the
8
- * workspace file directly — a path that only reads the workspace half
7
+ * servers builds its config through here rather than reading
8
+ * `config.mcp` directly — a path that only reads the workspace half
9
9
  * leaves a plugin's tools silently missing.
10
10
  *
11
11
  * Every server comes out attributed with its {@link McpServerSource}, so
@@ -3,21 +3,18 @@
3
3
  *
4
4
  * Follows the same pattern as mcp-oauth-provider.ts: headers are stored in
5
5
  * the secure credential store (CES or encrypted file fallback) rather than
6
- * in plaintext mcp.json, keeping secrets out of workspace config files.
6
+ * in plaintext config.json, keeping secrets out of workspace config files.
7
7
  *
8
8
  * Key format: mcp:{serverId}:headers — stores JSON-serialized Record<string, string>.
9
9
  */
10
10
 
11
+ import { loadRawConfig, saveRawConfig } from "../config/loader.js";
11
12
  import {
12
13
  deleteSecureKeyAsync,
13
14
  getSecureKeyAsync,
14
15
  setSecureKeyAsync,
15
16
  } from "../security/secure-keys.js";
16
17
  import { getLogger } from "../util/logger.js";
17
- import {
18
- loadWorkspaceMcpConfig,
19
- saveWorkspaceMcpConfig,
20
- } from "./workspace-mcp-config.js";
21
18
 
22
19
  const log = getLogger("mcp-header-store");
23
20
 
@@ -76,45 +73,64 @@ export async function deleteMcpHeaders(serverId: string): Promise<boolean> {
76
73
  }
77
74
 
78
75
  /**
79
- * Move leftover plaintext headers from mcp.json into the credential store
80
- * and rewrite the file without them. Safe to call on every MCP reload.
76
+ * One-time lazy migration: move any plaintext headers from config.json
77
+ * transport entries into the credential store and strip them from config.
78
+ * Safe to call on every MCP reload — no-ops when no legacy headers remain.
81
79
  */
82
80
  export async function migrateLegacyMcpHeaders(): Promise<void> {
83
- const config = loadWorkspaceMcpConfig();
84
- const servers = config.servers;
85
- let dirty = false;
81
+ const raw = loadRawConfig();
82
+ const mcpConfig = raw.mcp as
83
+ | { servers?: Record<string, Record<string, unknown>> }
84
+ | undefined;
85
+ const servers = mcpConfig?.servers;
86
+ if (!servers) {
87
+ return;
88
+ }
86
89
 
90
+ let configDirty = false;
87
91
  for (const [id, server] of Object.entries(servers)) {
88
- const transport = server.transport;
89
- if (transport.type !== "sse" && transport.type !== "streamable-http") {
92
+ const transport = server?.transport as Record<string, unknown> | undefined;
93
+ if (
94
+ !transport ||
95
+ (transport.type !== "sse" && transport.type !== "streamable-http")
96
+ ) {
90
97
  continue;
91
98
  }
92
- const leftoverHeaders = transport.headers;
93
- if (!leftoverHeaders || Object.keys(leftoverHeaders).length === 0) {
99
+ const legacyHeaders = transport.headers as
100
+ | Record<string, string>
101
+ | undefined;
102
+ if (!legacyHeaders || Object.keys(legacyHeaders).length === 0) {
94
103
  continue;
95
104
  }
96
105
 
106
+ // Only migrate if credential store doesn't already have headers for
107
+ // this server (idempotent — safe to re-run after partial failure).
97
108
  const existing = await getMcpHeaders(id);
98
- if (!existing) {
99
- const ok = await setMcpHeaders(id, leftoverHeaders);
100
- if (!ok) {
101
- log.warn(
102
- { serverId: id },
103
- "Skipping leftover header migration: credential store write failed; will retry on next reload",
104
- );
105
- continue;
106
- }
109
+ if (existing) {
110
+ // Credential store already has headers; just strip the config copy.
111
+ delete transport.headers;
112
+ configDirty = true;
113
+ continue;
114
+ }
115
+
116
+ const ok = await setMcpHeaders(id, legacyHeaders);
117
+ if (ok) {
118
+ delete transport.headers;
119
+ configDirty = true;
107
120
  log.info(
108
121
  { serverId: id },
109
- "Moved leftover MCP headers to credential store",
122
+ "Migrated legacy MCP headers to credential store",
123
+ );
124
+ } else {
125
+ log.warn(
126
+ { serverId: id },
127
+ "Skipping legacy header migration — credential store write failed; will retry on next reload",
110
128
  );
111
129
  }
112
- delete transport.headers;
113
- dirty = true;
114
130
  }
115
131
 
116
- if (dirty) {
117
- saveWorkspaceMcpConfig(config);
118
- log.info("mcp.json updated: leftover MCP headers removed after migration");
132
+ if (configDirty) {
133
+ saveRawConfig(raw);
134
+ log.info("Config updated: legacy MCP headers removed after migration");
119
135
  }
120
136
  }
@@ -13,14 +13,13 @@
13
13
  * further process that starts hosting turns has one call to make.
14
14
  */
15
15
 
16
- import { invalidateConfigCache } from "../config/loader.js";
16
+ import { getConfig, invalidateConfigCache } from "../config/loader.js";
17
17
  import type { McpConfig } from "../config/schemas/mcp.js";
18
18
  import { createMcpToolsFromServer } from "../tools/mcp/mcp-tool-factory.js";
19
19
  import { registerMcpTools, unregisterAllMcpTools } from "../tools/registry.js";
20
20
  import { getLogger } from "../util/logger.js";
21
21
  import { buildEffectiveMcpConfig } from "./effective-config.js";
22
22
  import { getMcpServerManager, stopMcpServerManager } from "./manager.js";
23
- import { loadWorkspaceMcpConfig } from "./workspace-mcp-config.js";
24
23
 
25
24
  const log = getLogger("mcp-startup");
26
25
 
@@ -37,8 +36,7 @@ const log = getLogger("mcp-startup");
37
36
  * is a startup step in processes whose other work must not be held hostage to
38
37
  * a third-party server being up.
39
38
  *
40
- * @param workspaceMcpConfig The projected workspace MCP config. Callers
41
- * that omit it read `/workspace/mcp.json`.
39
+ * @param workspaceMcpConfig The `mcp` block of the assistant config, if any.
42
40
  * @returns The number of tools registered across all servers.
43
41
  */
44
42
  export async function startConfiguredMcpServers(
@@ -46,9 +44,7 @@ export async function startConfiguredMcpServers(
46
44
  ): Promise<number> {
47
45
  let registered = 0;
48
46
  try {
49
- const mcpConfig = buildEffectiveMcpConfig(
50
- workspaceMcpConfig ?? loadWorkspaceMcpConfig(),
51
- );
47
+ const mcpConfig = buildEffectiveMcpConfig(workspaceMcpConfig);
52
48
  if (Object.keys(mcpConfig.servers).length === 0) {
53
49
  return 0;
54
50
  }
@@ -101,5 +97,5 @@ export async function restartConfiguredMcpServers(): Promise<number> {
101
97
  }
102
98
  unregisterAllMcpTools();
103
99
  invalidateConfigCache();
104
- return startConfiguredMcpServers(loadWorkspaceMcpConfig());
100
+ return startConfiguredMcpServers(getConfig().mcp);
105
101
  }
@@ -56,22 +56,15 @@ export interface MessagingProvider {
56
56
  query: string,
57
57
  options?: SearchOptions,
58
58
  ): Promise<SearchResult>;
59
-
60
- // ── Optional operations (platforms implement what they support) ───
61
-
62
- /**
63
- * Send through the provider's own API. A provider whose channel has a
64
- * direct transport omits this: its sends go through the transport, which
65
- * is the one send implementation per addressable channel, and the
66
- * messaging tool routes there before it looks for this method.
67
- */
68
- sendMessage?(
59
+ sendMessage(
69
60
  connection: OAuthConnection | undefined,
70
61
  conversationId: string,
71
62
  text: string,
72
63
  options?: SendOptions,
73
64
  ): Promise<SendResult>;
74
65
 
66
+ // ── Optional operations (platforms implement what they support) ───
67
+
75
68
  getThreadReplies?(
76
69
  connection: OAuthConnection | undefined,
77
70
  conversationId: string,
@@ -1,5 +1,4 @@
1
1
  import type { ChannelId } from "../../channels/types.js";
2
- import type { CallbackContext } from "./channel-transport.js";
3
2
 
4
3
  /**
5
4
  * Channels whose outbound replies the assistant delivers directly to the
@@ -23,23 +22,6 @@ export type DirectDeliveryChannel = (typeof DIRECT_DELIVERY_CHANNELS)[number];
23
22
 
24
23
  const CALLBACK_PREFIX = "/deliver/";
25
24
 
26
- /**
27
- * The callback context a channel's operations read for a direct delivery
28
- * that no inbound message brought a callback for: the channel's `/deliver`
29
- * path with its per-channel params, spelled the way `channelForCallback`
30
- * resolves it and the way the transports read it.
31
- */
32
- export function directDeliveryContext(
33
- channel: DirectDeliveryChannel,
34
- params: Readonly<Record<string, string>> = {},
35
- ): CallbackContext {
36
- const query = new URLSearchParams(params).toString();
37
- return {
38
- callbackUrl: `${CALLBACK_PREFIX}${channel}${query ? `?${query}` : ""}`,
39
- params,
40
- };
41
- }
42
-
43
25
  /**
44
26
  * Resolve a gateway callback URL to the direct-delivery channel that owns it, or
45
27
  * `undefined` when no channel delivers it directly.
@@ -97,35 +97,6 @@ export interface ReactionTarget {
97
97
  readonly action: "add" | "remove";
98
98
  }
99
99
 
100
- /**
101
- * Where a proactive send goes, in the caller's vocabulary rather than any
102
- * channel's: a chat the caller already knows, optionally a thread within it.
103
- * How a thread is spelled is each channel's own business.
104
- *
105
- * Tagged with its kind so a second way of naming a destination is additive
106
- * rather than a reinterpretation of `chatId`. Reaching a person who has not
107
- * been named as a chat is deliberately not a kind here: on Slack and Discord
108
- * it costs a platform call whose result must be resolved at send time rather
109
- * than stored, and nothing asks for it yet.
110
- */
111
- export type ProactiveTarget = {
112
- readonly kind: "chat";
113
- readonly chatId: string;
114
- readonly threadId?: string;
115
- };
116
-
117
- /**
118
- * A proactive target resolved into what this channel's operations read: the
119
- * callback context carrying the channel's routing state, the chat id
120
- * `deliver` posts to, and the thread the post lands in, absent when it is
121
- * not in one.
122
- */
123
- export interface ProactiveAddress {
124
- readonly ctx: CallbackContext;
125
- readonly chatId: string;
126
- readonly threadId?: string;
127
- }
128
-
129
100
  /**
130
101
  * Direct outbound delivery for one channel, wrapping the channel's provider-API
131
102
  * send functions behind a uniform surface. Transports are registered statically
@@ -140,33 +111,6 @@ export interface ChannelTransport {
140
111
  /** Canonical source channel id, e.g. `"slack"`. */
141
112
  readonly channel: ChannelId;
142
113
 
143
- /**
144
- * Resolve a target the caller names into this channel's own routing state,
145
- * for a send that no inbound message brought a callback for.
146
- *
147
- * Implementing it is the whole of declaring that the channel can be
148
- * addressed from a named chat. A transport that can only answer on the
149
- * callback an inbound message carried omits it, and a caller reads the
150
- * omission as "not addressable" rather than switching on the channel's
151
- * name. Returning `undefined` for a target this channel cannot address is
152
- * the same answer for that target alone.
153
- *
154
- * Resolution is local: it reads the target and the channel's own
155
- * vocabulary, and makes no platform call. A destination that has to be
156
- * resolved over the network belongs behind its own capability, so this one
157
- * stays cheap enough to call on every send.
158
- */
159
- addressFor?(target: ProactiveTarget): ProactiveAddress | undefined;
160
-
161
- /**
162
- * Whether a proactive send to a chat must also bind that chat's inbound
163
- * conversation, so the next message from the chat resolves to the same
164
- * conversation the post lives in. Declared by a channel whose inbound
165
- * conversations are keyed per chat and can be reset between sends; omitted
166
- * by one whose inbound binding is made at ingress and keyed by thread.
167
- */
168
- readonly bindsChatOnProactiveSend?: boolean;
169
-
170
114
  /** Deliver a rendered reply (text / approval / attachments). */
171
115
  deliver(
172
116
  ctx: CallbackContext,
@@ -1,7 +1,6 @@
1
1
  import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
2
2
 
3
3
  import { getLogger } from "../../../util/logger.js";
4
- import { directDeliveryContext } from "../callback-routing.js";
5
4
  import type {
6
5
  CallbackContext,
7
6
  ChannelTransport,
@@ -52,22 +51,6 @@ async function sendTarget(
52
51
  export const discordTransport: ChannelTransport = {
53
52
  channel: "discord",
54
53
 
55
- /**
56
- * A chat is a channel id, with `threadId` naming the thread, which is its
57
- * own channel. Reaching a person who has not been named as a chat would
58
- * mean opening their DM first, a platform call this resolution does not
59
- * make; the inbound `dm` param below is how an event-carried callback says
60
- * its `chatId` is a recipient rather than a room.
61
- */
62
- addressFor(target) {
63
- const threadId = target.threadId?.trim();
64
- return {
65
- ctx: directDeliveryContext("discord", threadId ? { threadId } : {}),
66
- chatId: target.chatId,
67
- ...(threadId ? { threadId } : {}),
68
- };
69
- },
70
-
71
54
  // Discord clears a typing indicator after ten seconds.
72
55
  activityRefreshMs: 8_000,
73
56
 
@@ -162,11 +162,10 @@ describe("Slack adapter token routing", () => {
162
162
  globalThis.fetch = originalFetch;
163
163
  });
164
164
 
165
- test("bot-token only: reads authenticate with the bot token (regression guard for pre-user-token behavior)", async () => {
165
+ test("bot-token only: reads and writes both authenticate with the bot token (regression guard for pre-user-token behavior)", async () => {
166
166
  // With only a bot token stored, reads must fall back to the bot token
167
167
  // so the adapter keeps working for installs that haven't re-consented
168
- // the user scope. Writes are not the adapter's: they go through the
169
- // channel transport's senders, which always post as the bot.
168
+ // the user scope. Writes stay on the bot token always.
170
169
  const resolved = await slackProvider.resolveConnection!();
171
170
  expect(resolved).toBeUndefined();
172
171
 
@@ -177,12 +176,18 @@ describe("Slack adapter token routing", () => {
177
176
  );
178
177
  expect(readCall).toBeDefined();
179
178
  expect(readCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
179
+
180
+ // Write path: sendMessage → /chat.postMessage must also use bot token.
181
+ await slackProvider.sendMessage(undefined, "C123", "hello");
182
+ const writeCall = captured.find((c) => c.url.includes("/chat.postMessage"));
183
+ expect(writeCall).toBeDefined();
184
+ expect(writeCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
180
185
  });
181
186
 
182
- test("bot + user tokens: reads authenticate with the user token", async () => {
187
+ test("bot + user tokens: reads authenticate with the user token, writes with the bot token", async () => {
183
188
  // With both tokens stored, reads MUST flip to the user token so the
184
- // adapter can see channels the user is in but the bot isn't. Posts are
185
- // the transport's and come from the bot identity there.
189
+ // adapter can see channels the user is in but the bot isn't. Writes
190
+ // MUST stay on the bot token so posts come from the bot identity.
186
191
  getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
187
192
  if (key === credentialKey("slack_channel", "bot_token")) {
188
193
  return BOT_TOKEN;
@@ -212,6 +217,12 @@ describe("Slack adapter token routing", () => {
212
217
  expect(historyCall).toBeDefined();
213
218
  expect(historyCall!.authorization).toBe(`Bearer ${USER_TOKEN}`);
214
219
 
220
+ // Writes: sendMessage → bot token.
221
+ await slackProvider.sendMessage(undefined, "C123", "hello");
222
+ const sendCall = captured.find((c) => c.url.includes("/chat.postMessage"));
223
+ expect(sendCall).toBeDefined();
224
+ expect(sendCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
225
+
215
226
  // markRead → user token. conversations.mark sets the read cursor for
216
227
  // the authenticated identity, so it must use the same token whose unread
217
228
  // counts the adapter exposes.
@@ -28,6 +28,8 @@ import type {
28
28
  Message,
29
29
  SearchOptions,
30
30
  SearchResult,
31
+ SendOptions,
32
+ SendResult,
31
33
  } from "../../provider-types.js";
32
34
  import { resolveSlackAuth } from "./auth.js";
33
35
  import * as slack from "./client.js";
@@ -142,12 +144,24 @@ function getReadAuth(connection?: OAuthConnection): OAuthConnection | string {
142
144
  return getSlackAuth(connection);
143
145
  }
144
146
 
145
- // SAFETY: content-creating writes (posts, updates, deletes, reactions) go
146
- // through the channel transport's senders, which authenticate with the bot
147
- // token so posts come from the bot identity. State-changing methods that
148
- // target the authenticated identity's own state (e.g. conversations.mark)
149
- // use the read auth so the cursor matches the perspective the adapter
150
- // exposes.
147
+ // SAFETY: content-creating writes (postMessage, updateMessage, deleteMessage,
148
+ // reactions) MUST use the bot token. Using the user token would post as the
149
+ // user, not as the bot. State-changing methods that target the authenticated
150
+ // identity's own state (e.g. conversations.mark) should use the read auth so
151
+ // the cursor matches the perspective the adapter exposes.
152
+ /**
153
+ * Resolve auth for content-creating write operations (postMessage and any
154
+ * future reactions, joins, leaves, updates, or deletes).
155
+ */
156
+ function getWriteAuth(connection?: OAuthConnection): OAuthConnection | string {
157
+ if (connection) {
158
+ return connection;
159
+ }
160
+ if (_cachedSlackWriteAuth) {
161
+ return _cachedSlackWriteAuth;
162
+ }
163
+ return getSlackAuth(connection);
164
+ }
151
165
 
152
166
  /**
153
167
  * Resolve the bot token (raw string) and pass it to `fn`. Returns the
@@ -157,7 +171,7 @@ function getReadAuth(connection?: OAuthConnection): OAuthConnection | string {
157
171
  * (`OAuthConnection.withToken`) for callers that need a raw token to hand
158
172
  * to a non-Slack-client API call — currently `downloadSlackFile` for inline
159
173
  * file/image fetches. Slack-client method calls should keep going through
160
- * `getReadAuth` and pass the union through.
174
+ * `getReadAuth` / `getWriteAuth` and pass the union through.
161
175
  */
162
176
  export async function withSlackBotToken<T>(
163
177
  account: string | undefined,
@@ -816,6 +830,29 @@ export const slackProvider: MessagingProvider = {
816
830
  };
817
831
  },
818
832
 
833
+ async sendMessage(
834
+ connection: OAuthConnection | undefined,
835
+ conversationId: string,
836
+ text: string,
837
+ options?: SendOptions,
838
+ ): Promise<SendResult> {
839
+ const auth = getWriteAuth(connection);
840
+ const resp = await slack.postMessage(
841
+ auth,
842
+ conversationId,
843
+ text,
844
+ options?.threadId,
845
+ );
846
+ // The thread reported is the one the post landed in, which is the one
847
+ // requested, since chat.postMessage threads exactly where it is told.
848
+ return {
849
+ id: resp.ts,
850
+ timestamp: parseFloat(resp.ts) * 1000,
851
+ conversationId: resp.channel,
852
+ ...(options?.threadId ? { threadId: options.threadId } : {}),
853
+ };
854
+ },
855
+
819
856
  async getThreadReplies(
820
857
  connection: OAuthConnection | undefined,
821
858
  conversationId: string,
@@ -3,7 +3,6 @@ import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
3
3
 
4
4
  import { extractThreadTsFromCallbackUrl } from "../../../channels/slack-callback-url.js";
5
5
  import { getLogger } from "../../../util/logger.js";
6
- import { directDeliveryContext } from "../callback-routing.js";
7
6
  import type { ChannelTransport } from "../channel-transport.js";
8
7
  import { SLACK_STREAM_MARKDOWN_LIMIT } from "./api.js";
9
8
  import {
@@ -26,21 +25,6 @@ function mutedBlocks(text: string): KnownBlock[] {
26
25
  export const slackTransport: ChannelTransport = {
27
26
  channel: "slack",
28
27
 
29
- /**
30
- * A chat is a channel or DM id, with `threadTs` naming the thread to post
31
- * under. Reaching a person who has not been named as a chat would mean
32
- * opening the DM first (`conversations.open`), a platform call this
33
- * resolution does not make.
34
- */
35
- addressFor(target) {
36
- const threadTs = target.threadId?.trim();
37
- return {
38
- ctx: directDeliveryContext("slack", threadTs ? { threadTs } : {}),
39
- chatId: target.chatId,
40
- ...(threadTs ? { threadId: threadTs } : {}),
41
- };
42
- },
43
-
44
28
  describeReactionEmoji: describeSlackReactionEmoji,
45
29
 
46
30
  async react(target) {
@@ -6,6 +6,9 @@
6
6
 
7
7
  import type { OAuthConnection } from "../../../oauth/connection.js";
8
8
  import { getConnectionByProvider } from "../../../oauth/oauth-store.js";
9
+ import { getOrCreateConversation } from "../../../persistence/conversation-key-store.js";
10
+ import { buildScopedConversationKey } from "../../../persistence/delivery-crud.js";
11
+ import { upsertOutboundBinding } from "../../../persistence/external-conversation-store.js";
9
12
  import { credentialKey } from "../../../security/credential-key.js";
10
13
  import { getSecureKeyAsync } from "../../../security/secure-keys.js";
11
14
  import type { MessagingProvider } from "../../provider.js";
@@ -17,6 +20,8 @@ import type {
17
20
  Message,
18
21
  SearchOptions,
19
22
  SearchResult,
23
+ SendOptions,
24
+ SendResult,
20
25
  } from "../../provider-types.js";
21
26
  import * as telegram from "./client.js";
22
27
 
@@ -29,7 +34,7 @@ export const telegramBotMessagingProvider: MessagingProvider = {
29
34
  id: "telegram",
30
35
  displayName: "Telegram",
31
36
  credentialService: "telegram",
32
- capabilities: new Set(),
37
+ capabilities: new Set(["send"]),
33
38
 
34
39
  async isConnected(): Promise<boolean> {
35
40
  const conn = getConnectionByProvider("telegram");
@@ -90,6 +95,48 @@ export const telegramBotMessagingProvider: MessagingProvider = {
90
95
  }
91
96
  },
92
97
 
98
+ async sendMessage(
99
+ _connection: OAuthConnection | undefined,
100
+ conversationId: string,
101
+ text: string,
102
+ _options?: SendOptions,
103
+ ): Promise<SendResult> {
104
+ const sent = await telegram.sendMessage(conversationId, text);
105
+ if (!sent.lastMessageId) {
106
+ throw new Error(
107
+ "Telegram accepted the message but returned no message id",
108
+ );
109
+ }
110
+
111
+ // Upsert external conversation binding so deleted/reset syncs are
112
+ // resurrected when an outbound message is sent. This ensures the
113
+ // conversation key mapping and binding exist for the next inbound.
114
+ try {
115
+ const sourceChannel = "telegram";
116
+ const conversationKey = buildScopedConversationKey(
117
+ sourceChannel,
118
+ conversationId,
119
+ );
120
+ const { conversationId: internalId } =
121
+ getOrCreateConversation(conversationKey);
122
+ upsertOutboundBinding({
123
+ conversationId: internalId,
124
+ sourceChannel,
125
+ externalChatId: conversationId,
126
+ });
127
+ } catch {
128
+ // Best-effort — don't fail the send if binding upsert fails
129
+ }
130
+
131
+ // The id is the one Telegram assigned, so a later reaction, edit, or
132
+ // delete carrying that id resolves to this send.
133
+ return {
134
+ id: sent.lastMessageId,
135
+ timestamp: Date.now(),
136
+ conversationId,
137
+ };
138
+ },
139
+
93
140
  async listConversations(
94
141
  _connection?: OAuthConnection,
95
142
  _options?: ListOptions,
@@ -41,3 +41,29 @@ export async function getMe(botToken: string): Promise<TelegramGetMeResponse> {
41
41
 
42
42
  return resp.json() as Promise<TelegramGetMeResponse>;
43
43
  }
44
+
45
+ /** Result returned by sendMessage. */
46
+ export interface TelegramSendResult {
47
+ ok: boolean;
48
+ /**
49
+ * The `message_id` Telegram assigned to the last chunk sent. Telegram
50
+ * returns the sent Message on every successful `sendMessage`, so this is
51
+ * absent only when the API answered with an unexpected shape.
52
+ */
53
+ lastMessageId?: string;
54
+ }
55
+
56
+ /**
57
+ * Send a Telegram text message via the Bot API directly.
58
+ *
59
+ * Delegates to sendTelegramReply which handles text splitting (Telegram's
60
+ * sendMessage API has a 4096-char limit per call).
61
+ */
62
+ export async function sendMessage(
63
+ chatId: string,
64
+ text: string,
65
+ ): Promise<TelegramSendResult> {
66
+ const { sendTelegramReply } = await import("./send.js");
67
+ const sent = await sendTelegramReply(chatId, text);
68
+ return { ok: true, lastMessageId: sent.lastMessageId };
69
+ }