@vellumai/assistant 0.12.1 → 0.12.2-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (548) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/openapi.yaml +327 -52
  6. package/package.json +3 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  254. package/src/desktop/desktop-chrome-policy.ts +47 -0
  255. package/src/desktop/desktop-chrome-session.ts +37 -0
  256. package/src/desktop/desktop-dependencies.test.ts +102 -0
  257. package/src/desktop/desktop-dependencies.ts +308 -0
  258. package/src/desktop/desktop-feature.test.ts +18 -0
  259. package/src/desktop/desktop-feature.ts +19 -0
  260. package/src/desktop/desktop-panel-config.test.ts +135 -0
  261. package/src/desktop/desktop-panel-config.ts +149 -0
  262. package/src/desktop/desktop-session-manager.test.ts +730 -0
  263. package/src/desktop/desktop-session-manager.ts +850 -0
  264. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  265. package/src/desktop/desktop-stream-bridge.ts +214 -0
  266. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  267. package/src/desktop/desktop-wallpaper.ts +113 -0
  268. package/src/desktop/desktop-window-drag.ts +177 -0
  269. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  270. package/src/desktop/desktop-window-manager-config.ts +238 -0
  271. package/src/desktop/desktop-window-theme.ts +170 -0
  272. package/src/heartbeat/heartbeat-service.ts +21 -3
  273. package/src/home/__tests__/feed-writer.test.ts +22 -0
  274. package/src/home/feed-writer.ts +11 -2
  275. package/src/hooks/types.ts +33 -0
  276. package/src/i18n/AGENTS.md +22 -0
  277. package/src/i18n/__tests__/i18n.test.ts +108 -0
  278. package/src/i18n/index.ts +16 -0
  279. package/src/i18n/locales.ts +93 -0
  280. package/src/i18n/messages.ts +61 -0
  281. package/src/i18n/title.ts +25 -0
  282. package/src/inbound/platform-callback-registration.ts +4 -5
  283. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  284. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  285. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  286. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  287. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  288. package/src/live-voice/live-voice-photo.ts +25 -6
  289. package/src/live-voice/live-voice-session.ts +213 -693
  290. package/src/live-voice/protocol.ts +0 -2
  291. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  292. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  293. package/src/mcp/effective-config.ts +3 -3
  294. package/src/mcp/mcp-header-store.ts +29 -45
  295. package/src/mcp/spec-schema.ts +111 -0
  296. package/src/mcp/startup.ts +8 -4
  297. package/src/mcp/workspace-mcp-config.ts +188 -0
  298. package/src/messaging/provider.ts +10 -3
  299. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  300. package/src/messaging/providers/callback-routing.ts +18 -0
  301. package/src/messaging/providers/channel-transport.ts +56 -0
  302. package/src/messaging/providers/discord/transport.ts +17 -0
  303. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  304. package/src/messaging/providers/slack/adapter.ts +7 -44
  305. package/src/messaging/providers/slack/transport.ts +16 -0
  306. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  307. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  308. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  309. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  310. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  311. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  312. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  313. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  314. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  315. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  316. package/src/notifications/adapters/macos.ts +3 -0
  317. package/src/notifications/assistant-reply-producer.ts +8 -3
  318. package/src/notifications/conversation-pairing.ts +36 -16
  319. package/src/notifications/delivered-post-record.ts +11 -1
  320. package/src/notifications/guardian-feed-projection.ts +125 -5
  321. package/src/notifications/home-feed-side-effect.ts +55 -7
  322. package/src/notifications/schedule-result-producer.ts +66 -22
  323. package/src/oauth/AGENTS.md +2 -0
  324. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  325. package/src/oauth/byo-connection.test.ts +105 -0
  326. package/src/oauth/byo-connection.ts +36 -6
  327. package/src/oauth/connection-resolver.test.ts +73 -0
  328. package/src/oauth/connection-resolver.ts +95 -2
  329. package/src/oauth/scope-utils.ts +24 -0
  330. package/src/oauth/seed-providers.ts +204 -25
  331. package/src/outbound-proxy/certs.ts +3 -5
  332. package/src/permissions/checker.test.ts +11 -6
  333. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  334. package/src/persistence/conversation-crud.ts +71 -63
  335. package/src/persistence/conversation-lineage.ts +12 -4
  336. package/src/persistence/conversation-queries.ts +2 -2
  337. package/src/persistence/conversation-title-placeholders.ts +23 -0
  338. package/src/persistence/conversation-title-service.ts +9 -26
  339. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  340. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  341. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  342. package/src/persistence/message-cursor.ts +78 -0
  343. package/src/persistence/message-reads.ts +30 -0
  344. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  345. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  346. package/src/persistence/schema/memory-core.ts +5 -0
  347. package/src/platform/client.test.ts +4 -2
  348. package/src/platform/client.ts +2 -10
  349. package/src/plugin-api/constants.ts +2 -0
  350. package/src/plugin-api/index.ts +1 -0
  351. package/src/plugin-api/types.ts +2 -0
  352. package/src/plugins/defaults/index.ts +2 -0
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  358. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  359. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  360. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  361. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  362. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  363. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  364. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  365. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  366. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  367. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  368. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  369. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  370. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  371. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  372. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  374. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  375. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  376. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  377. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  378. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  379. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  380. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  381. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  382. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  383. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  384. package/src/plugins/defaults/memory/startup.ts +28 -2
  385. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  386. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  387. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  388. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  389. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  390. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  391. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  392. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  393. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  394. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  395. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  396. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  397. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  398. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  399. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  400. package/src/plugins/defaults/memory/worker.ts +6 -0
  401. package/src/plugins/mcp-servers.ts +15 -75
  402. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  403. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  404. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  405. package/src/providers/anthropic/client.ts +0 -47
  406. package/src/providers/gemini/client.ts +9 -23
  407. package/src/providers/gemini-thought-signature.test.ts +63 -0
  408. package/src/providers/gemini-thought-signature.ts +44 -0
  409. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  410. package/src/providers/inference/auth.ts +2 -3
  411. package/src/providers/inference/connection-availability.ts +27 -8
  412. package/src/providers/inference/endpoint-probe.ts +10 -1
  413. package/src/providers/inference/resolve-auth.ts +22 -4
  414. package/src/providers/inference/service-account-token.ts +248 -0
  415. package/src/providers/model-catalog.ts +34 -0
  416. package/src/providers/model-intents.ts +10 -5
  417. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  418. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  419. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  420. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  421. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  422. package/src/providers/openai/chat-completions-provider.ts +212 -79
  423. package/src/providers/openai/codex-models.ts +0 -4
  424. package/src/providers/openai/google-thought-signature.ts +320 -0
  425. package/src/providers/opencode/client.test.ts +73 -4
  426. package/src/providers/opencode/client.ts +60 -3
  427. package/src/providers/openrouter/client.ts +20 -0
  428. package/src/providers/outbound-request-sanitize.ts +96 -0
  429. package/src/providers/retry.ts +30 -19
  430. package/src/providers/unparseable-tool-args.ts +5 -1
  431. package/src/runtime/AGENTS.md +3 -1
  432. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  433. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  434. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  435. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  436. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  437. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  438. package/src/runtime/agent-wake.ts +2 -1
  439. package/src/runtime/assistant-event-hub.ts +21 -3
  440. package/src/runtime/auth/same-actor.ts +38 -3
  441. package/src/runtime/channel-send.ts +316 -0
  442. package/src/runtime/host-observe.ts +11 -1
  443. package/src/runtime/http-server.ts +160 -151
  444. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  445. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  446. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  447. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  448. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  449. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  450. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  451. package/src/runtime/routes/acp-routes.test.ts +15 -0
  452. package/src/runtime/routes/acp-routes.ts +72 -9
  453. package/src/runtime/routes/app-management-routes.ts +73 -0
  454. package/src/runtime/routes/app-routes.ts +1 -1
  455. package/src/runtime/routes/channel-send-routes.ts +85 -0
  456. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  457. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  458. package/src/runtime/routes/conversation-routes.ts +16 -3
  459. package/src/runtime/routes/credential-routes.ts +19 -10
  460. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  461. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  462. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  463. package/src/runtime/routes/events-routes.ts +4 -0
  464. package/src/runtime/routes/host-cu-routes.ts +6 -0
  465. package/src/runtime/routes/index.ts +4 -2
  466. package/src/runtime/routes/log-export-routes.ts +2 -0
  467. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  468. package/src/runtime/routes/migration-routes.ts +6 -20
  469. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  470. package/src/runtime/routes/platform-routes.ts +18 -15
  471. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  472. package/src/runtime/routes/workspace-routes.ts +46 -63
  473. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  474. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  475. package/src/runtime/routes/workspace-utils.ts +17 -0
  476. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  477. package/src/runtime/services/conversation-serializer.ts +16 -3
  478. package/src/runtime/sync/resource-sync-events.ts +2 -1
  479. package/src/schedule/run-script.ts +2 -1
  480. package/src/schedule/schedule-store.ts +9 -2
  481. package/src/schedule/worker.ts +1 -1
  482. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  483. package/src/security/ces-http-record-backend.ts +120 -0
  484. package/src/security/ces-rpc-record-backend.ts +7 -0
  485. package/src/security/untrusted-content.ts +3 -1
  486. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  487. package/src/skills/workspace-skill-md-signature.ts +50 -0
  488. package/src/subagent/consult-context.ts +12 -25
  489. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  490. package/src/tools/acp/list-agents.test.ts +3 -1
  491. package/src/tools/acp/spawn.test.ts +146 -11
  492. package/src/tools/acp/spawn.ts +21 -3
  493. package/src/tools/acp/status.test.ts +94 -0
  494. package/src/tools/acp/status.ts +40 -3
  495. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/browser/browser-execution.ts +1 -1
  497. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  498. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  499. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  500. package/src/tools/browser/network-recorder.ts +2 -1
  501. package/src/tools/capability-offer.test.ts +22 -2
  502. package/src/tools/capability-offer.ts +16 -1
  503. package/src/tools/computer-use/definitions.ts +121 -2
  504. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  505. package/src/tools/credentials/metadata-store.ts +100 -0
  506. package/src/tools/filesystem/search.ts +2 -1
  507. package/src/tools/host-filesystem/edit.test.ts +2 -1
  508. package/src/tools/host-filesystem/edit.ts +2 -2
  509. package/src/tools/host-filesystem/read.test.ts +2 -1
  510. package/src/tools/host-filesystem/read.ts +2 -2
  511. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  512. package/src/tools/host-filesystem/transfer.ts +2 -2
  513. package/src/tools/host-filesystem/write.test.ts +2 -1
  514. package/src/tools/host-filesystem/write.ts +2 -2
  515. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  516. package/src/tools/host-terminal/host-shell.ts +4 -20
  517. package/src/tools/network/web-search.ts +6 -1
  518. package/src/tools/policy-context.ts +6 -7
  519. package/src/tools/skills/scaffold-managed.ts +61 -3
  520. package/src/tools/subagent/spawn.ts +5 -8
  521. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  522. package/src/tools/terminal/sanitized-bash.ts +105 -0
  523. package/src/tools/watch/watch-retro-report.ts +0 -8
  524. package/src/types/host-capabilities.ts +5 -3
  525. package/src/util/__tests__/language-subtag.test.ts +23 -1
  526. package/src/util/ca-bundle.ts +13 -0
  527. package/src/util/language-subtag.ts +16 -0
  528. package/src/util/platform.ts +8 -0
  529. package/src/util/unicode.ts +15 -1
  530. package/src/watch/watch-timeline.ts +2 -2
  531. package/src/workflows/run-manager.ts +2 -1
  532. package/src/workspace/git-porcelain.ts +66 -0
  533. package/src/workspace/git-service.ts +6 -55
  534. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  535. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  536. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  537. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  538. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  539. package/src/workspace/migrations/registry.ts +6 -0
  540. package/src/__tests__/app-source-watcher.test.ts +0 -218
  541. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  542. package/src/__tests__/speaker-identification.test.ts +0 -56
  543. package/src/calls/speaker-identification.ts +0 -233
  544. package/src/daemon/app-source-watcher.ts +0 -218
  545. package/src/messaging/providers/whatsapp/client.ts +0 -29
  546. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  547. package/src/util/fs-watcher-error.ts +0 -36
  548. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -0,0 +1,95 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ // The addressing capability resolves a named chat into exactly what each
3
+ // transport's own operations read: the callback URL `channelForCallback`
4
+ // resolves and the params `deliver` consumes. Resolution is local, so these
5
+ // assertions make no platform call; the tests import the transports'
6
+ // modules, which pull in their provider-API senders, so the senders'
7
+ // credential lookups are stubbed to keep the import inert.
8
+ import { mock } from "bun:test";
9
+
10
+ const actualSecureKeys = await import("../../../security/secure-keys.js");
11
+ mock.module("../../../security/secure-keys.js", () => ({
12
+ ...actualSecureKeys,
13
+ getSecureKeyAsync: async () => null,
14
+ getSecureKeyResultAsync: async () => ({ value: null }),
15
+ }));
16
+
17
+ const { channelForCallback } = await import("../callback-routing.js");
18
+ const { discordTransport } = await import("../discord/transport.js");
19
+ const { slackTransport } = await import("../slack/transport.js");
20
+ const { telegramTransport } = await import("../telegram-bot/transport.js");
21
+ const { whatsappTransport } = await import("../whatsapp/transport.js");
22
+
23
+ describe("addressFor resolves to the channel's own routing state", () => {
24
+ test("slack: a chat, optionally under a thread", () => {
25
+ const room = slackTransport.addressFor!({ kind: "chat", chatId: "C123" });
26
+ expect(room).toEqual({
27
+ ctx: { callbackUrl: "/deliver/slack", params: {} },
28
+ chatId: "C123",
29
+ });
30
+ const thread = slackTransport.addressFor!({
31
+ kind: "chat",
32
+ chatId: "C123",
33
+ threadId: "1690000000.000001",
34
+ });
35
+ expect(thread).toEqual({
36
+ ctx: {
37
+ callbackUrl: "/deliver/slack?threadTs=1690000000.000001",
38
+ params: { threadTs: "1690000000.000001" },
39
+ },
40
+ chatId: "C123",
41
+ threadId: "1690000000.000001",
42
+ });
43
+ expect(channelForCallback(thread!.ctx.callbackUrl)).toBe("slack");
44
+ });
45
+
46
+ test("telegram: a chat with a topic; binds on send", () => {
47
+ expect(
48
+ telegramTransport.addressFor!({
49
+ kind: "chat",
50
+ chatId: "123",
51
+ threadId: "42",
52
+ }),
53
+ ).toEqual({
54
+ ctx: {
55
+ callbackUrl: "/deliver/telegram?threadId=42",
56
+ params: { threadId: "42" },
57
+ },
58
+ chatId: "123",
59
+ threadId: "42",
60
+ });
61
+ expect(telegramTransport.bindsChatOnProactiveSend).toBe(true);
62
+ });
63
+
64
+ test("discord: a chat with a thread, which is its own channel", () => {
65
+ const thread = discordTransport.addressFor!({
66
+ kind: "chat",
67
+ chatId: "C1",
68
+ threadId: "T1",
69
+ });
70
+ expect(thread).toEqual({
71
+ ctx: {
72
+ callbackUrl: "/deliver/discord?threadId=T1",
73
+ params: { threadId: "T1" },
74
+ },
75
+ chatId: "C1",
76
+ threadId: "T1",
77
+ });
78
+ expect(channelForCallback(thread!.ctx.callbackUrl)).toBe("discord");
79
+ expect(discordTransport.bindsChatOnProactiveSend).toBeUndefined();
80
+ });
81
+
82
+ test("whatsapp: the number is the chat; no threads; binds on send", () => {
83
+ expect(
84
+ whatsappTransport.addressFor!({
85
+ kind: "chat",
86
+ chatId: "12125550100",
87
+ threadId: "ignored",
88
+ }),
89
+ ).toEqual({
90
+ ctx: { callbackUrl: "/deliver/whatsapp", params: {} },
91
+ chatId: "12125550100",
92
+ });
93
+ expect(whatsappTransport.bindsChatOnProactiveSend).toBe(true);
94
+ });
95
+ });
@@ -1,4 +1,5 @@
1
1
  import type { ChannelId } from "../../channels/types.js";
2
+ import type { CallbackContext } from "./channel-transport.js";
2
3
 
3
4
  /**
4
5
  * Channels whose outbound replies the assistant delivers directly to the
@@ -22,6 +23,23 @@ export type DirectDeliveryChannel = (typeof DIRECT_DELIVERY_CHANNELS)[number];
22
23
 
23
24
  const CALLBACK_PREFIX = "/deliver/";
24
25
 
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
+
25
43
  /**
26
44
  * Resolve a gateway callback URL to the direct-delivery channel that owns it, or
27
45
  * `undefined` when no channel delivers it directly.
@@ -97,6 +97,35 @@ 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
+
100
129
  /**
101
130
  * Direct outbound delivery for one channel, wrapping the channel's provider-API
102
131
  * send functions behind a uniform surface. Transports are registered statically
@@ -111,6 +140,33 @@ export interface ChannelTransport {
111
140
  /** Canonical source channel id, e.g. `"slack"`. */
112
141
  readonly channel: ChannelId;
113
142
 
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
+
114
170
  /** Deliver a rendered reply (text / approval / attachments). */
115
171
  deliver(
116
172
  ctx: CallbackContext,
@@ -1,6 +1,7 @@
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";
4
5
  import type {
5
6
  CallbackContext,
6
7
  ChannelTransport,
@@ -51,6 +52,22 @@ async function sendTarget(
51
52
  export const discordTransport: ChannelTransport = {
52
53
  channel: "discord",
53
54
 
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
+
54
71
  // Discord clears a typing indicator after ten seconds.
55
72
  activityRefreshMs: 8_000,
56
73
 
@@ -162,10 +162,11 @@ describe("Slack adapter token routing", () => {
162
162
  globalThis.fetch = originalFetch;
163
163
  });
164
164
 
165
- test("bot-token only: reads and writes both authenticate with the bot token (regression guard for pre-user-token behavior)", async () => {
165
+ test("bot-token only: reads 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 stay on the bot token always.
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.
169
170
  const resolved = await slackProvider.resolveConnection!();
170
171
  expect(resolved).toBeUndefined();
171
172
 
@@ -176,18 +177,12 @@ describe("Slack adapter token routing", () => {
176
177
  );
177
178
  expect(readCall).toBeDefined();
178
179
  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}`);
185
180
  });
186
181
 
187
- test("bot + user tokens: reads authenticate with the user token, writes with the bot token", async () => {
182
+ test("bot + user tokens: reads authenticate with the user token", async () => {
188
183
  // With both tokens stored, reads MUST flip to the user token so the
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.
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.
191
186
  getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
192
187
  if (key === credentialKey("slack_channel", "bot_token")) {
193
188
  return BOT_TOKEN;
@@ -217,12 +212,6 @@ describe("Slack adapter token routing", () => {
217
212
  expect(historyCall).toBeDefined();
218
213
  expect(historyCall!.authorization).toBe(`Bearer ${USER_TOKEN}`);
219
214
 
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
-
226
215
  // markRead → user token. conversations.mark sets the read cursor for
227
216
  // the authenticated identity, so it must use the same token whose unread
228
217
  // counts the adapter exposes.
@@ -28,8 +28,6 @@ import type {
28
28
  Message,
29
29
  SearchOptions,
30
30
  SearchResult,
31
- SendOptions,
32
- SendResult,
33
31
  } from "../../provider-types.js";
34
32
  import { resolveSlackAuth } from "./auth.js";
35
33
  import * as slack from "./client.js";
@@ -144,24 +142,12 @@ function getReadAuth(connection?: OAuthConnection): OAuthConnection | string {
144
142
  return getSlackAuth(connection);
145
143
  }
146
144
 
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
- }
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.
165
151
 
166
152
  /**
167
153
  * Resolve the bot token (raw string) and pass it to `fn`. Returns the
@@ -171,7 +157,7 @@ function getWriteAuth(connection?: OAuthConnection): OAuthConnection | string {
171
157
  * (`OAuthConnection.withToken`) for callers that need a raw token to hand
172
158
  * to a non-Slack-client API call — currently `downloadSlackFile` for inline
173
159
  * file/image fetches. Slack-client method calls should keep going through
174
- * `getReadAuth` / `getWriteAuth` and pass the union through.
160
+ * `getReadAuth` and pass the union through.
175
161
  */
176
162
  export async function withSlackBotToken<T>(
177
163
  account: string | undefined,
@@ -830,29 +816,6 @@ export const slackProvider: MessagingProvider = {
830
816
  };
831
817
  },
832
818
 
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
-
856
819
  async getThreadReplies(
857
820
  connection: OAuthConnection | undefined,
858
821
  conversationId: string,
@@ -3,6 +3,7 @@ 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";
6
7
  import type { ChannelTransport } from "../channel-transport.js";
7
8
  import { SLACK_STREAM_MARKDOWN_LIMIT } from "./api.js";
8
9
  import {
@@ -25,6 +26,21 @@ function mutedBlocks(text: string): KnownBlock[] {
25
26
  export const slackTransport: ChannelTransport = {
26
27
  channel: "slack",
27
28
 
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
+
28
44
  describeReactionEmoji: describeSlackReactionEmoji,
29
45
 
30
46
  async react(target) {
@@ -6,9 +6,6 @@
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";
12
9
  import { credentialKey } from "../../../security/credential-key.js";
13
10
  import { getSecureKeyAsync } from "../../../security/secure-keys.js";
14
11
  import type { MessagingProvider } from "../../provider.js";
@@ -20,8 +17,6 @@ import type {
20
17
  Message,
21
18
  SearchOptions,
22
19
  SearchResult,
23
- SendOptions,
24
- SendResult,
25
20
  } from "../../provider-types.js";
26
21
  import * as telegram from "./client.js";
27
22
 
@@ -34,7 +29,7 @@ export const telegramBotMessagingProvider: MessagingProvider = {
34
29
  id: "telegram",
35
30
  displayName: "Telegram",
36
31
  credentialService: "telegram",
37
- capabilities: new Set(["send"]),
32
+ capabilities: new Set(),
38
33
 
39
34
  async isConnected(): Promise<boolean> {
40
35
  const conn = getConnectionByProvider("telegram");
@@ -95,48 +90,6 @@ export const telegramBotMessagingProvider: MessagingProvider = {
95
90
  }
96
91
  },
97
92
 
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
-
140
93
  async listConversations(
141
94
  _connection?: OAuthConnection,
142
95
  _options?: ListOptions,
@@ -41,29 +41,3 @@ 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
- }
@@ -2,6 +2,7 @@ import type { StreamPlan, StreamPlanStep } from "@vellumai/gateway-client";
2
2
  import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
3
3
 
4
4
  import { getLogger } from "../../../util/logger.js";
5
+ import { directDeliveryContext } from "../callback-routing.js";
5
6
  import type {
6
7
  CallbackContext,
7
8
  ChannelTransport,
@@ -66,6 +67,25 @@ function mintDraftId(): number {
66
67
  export const telegramTransport: ChannelTransport = {
67
68
  channel: "telegram",
68
69
 
70
+ /**
71
+ * A chat is a chat id, with `threadId` naming a forum topic. A person's DM
72
+ * chat id is their user id on Telegram, so reaching one is naming that
73
+ * chat and needs no resolution of its own.
74
+ */
75
+ addressFor(target) {
76
+ const threadId = target.threadId?.trim();
77
+ return {
78
+ ctx: directDeliveryContext("telegram", threadId ? { threadId } : {}),
79
+ chatId: target.chatId,
80
+ ...(threadId ? { threadId } : {}),
81
+ };
82
+ },
83
+
84
+ // A Telegram chat's inbound conversation is keyed per chat and can be
85
+ // reset or deleted between sends; a proactive post re-binds it so the
86
+ // next inbound from the chat lands where the post lives.
87
+ bindsChatOnProactiveSend: true,
88
+
69
89
  // Telegram clears a chat action after about five seconds.
70
90
  activityRefreshMs: 4_000,
71
91
 
@@ -5,8 +5,6 @@
5
5
  */
6
6
 
7
7
  import type { OAuthConnection } from "../../../oauth/connection.js";
8
- import { getOrCreateConversation } from "../../../persistence/conversation-key-store.js";
9
- import { upsertOutboundBinding } from "../../../persistence/external-conversation-store.js";
10
8
  import { credentialKey } from "../../../security/credential-key.js";
11
9
  import { getSecureKeyAsync } from "../../../security/secure-keys.js";
12
10
  import type { MessagingProvider } from "../../provider.js";
@@ -18,10 +16,7 @@ import type {
18
16
  Message,
19
17
  SearchOptions,
20
18
  SearchResult,
21
- SendOptions,
22
- SendResult,
23
19
  } from "../../provider-types.js";
24
- import * as whatsapp from "./client.js";
25
20
 
26
21
  /** Check whether WhatsApp credentials are stored. */
27
22
  async function hasWhatsAppCredentials(): Promise<boolean> {
@@ -41,7 +36,7 @@ export const whatsappMessagingProvider: MessagingProvider = {
41
36
  id: "whatsapp",
42
37
  displayName: "WhatsApp",
43
38
  credentialService: "whatsapp",
44
- capabilities: new Set(["send"]),
39
+ capabilities: new Set(),
45
40
 
46
41
  async isConnected(): Promise<boolean> {
47
42
  return hasWhatsAppCredentials();
@@ -74,48 +69,6 @@ export const whatsappMessagingProvider: MessagingProvider = {
74
69
  };
75
70
  },
76
71
 
77
- async sendMessage(
78
- _connection: OAuthConnection | undefined,
79
- conversationId: string,
80
- text: string,
81
- options?: SendOptions,
82
- ): Promise<SendResult> {
83
- const assistantId = options?.assistantId;
84
-
85
- const sent = await whatsapp.sendMessage(conversationId, text);
86
- if (!sent.messageId) {
87
- throw new Error(
88
- "WhatsApp accepted the message but returned no message id",
89
- );
90
- }
91
-
92
- // Upsert external conversation binding so the conversation key mapping
93
- // exists for the next inbound WhatsApp message from this number.
94
- try {
95
- const sourceChannel = "whatsapp";
96
- const conversationKey = `asst:${assistantId ?? "self"}:${sourceChannel}:${conversationId}`;
97
- const { conversationId: internalId } =
98
- getOrCreateConversation(conversationKey);
99
- if (!assistantId || assistantId === "self") {
100
- upsertOutboundBinding({
101
- conversationId: internalId,
102
- sourceChannel,
103
- externalChatId: conversationId,
104
- });
105
- }
106
- } catch {
107
- // Best-effort — don't fail the send if binding upsert fails
108
- }
109
-
110
- // The id is the one the Cloud API assigned, so a later reaction or
111
- // delete carrying that id resolves to this send.
112
- return {
113
- id: sent.messageId,
114
- timestamp: Date.now(),
115
- conversationId,
116
- };
117
- },
118
-
119
72
  async listConversations(
120
73
  _connection?: OAuthConnection,
121
74
  _options?: ListOptions,
@@ -1,6 +1,7 @@
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";
4
5
  import type { ChannelTransport } from "../channel-transport.js";
5
6
  import { sendWhatsAppAttachments, sendWhatsAppReply } from "./send.js";
6
7
 
@@ -9,6 +10,23 @@ const log = getLogger("whatsapp-transport");
9
10
  export const whatsappTransport: ChannelTransport = {
10
11
  channel: "whatsapp",
11
12
 
13
+ /**
14
+ * A chat is the phone number, which is also how a person is named, so
15
+ * reaching one needs no resolution of its own. There are no threads, so a
16
+ * target's thread is not carried and the post lands in the chat itself.
17
+ */
18
+ addressFor(target) {
19
+ return {
20
+ ctx: directDeliveryContext("whatsapp"),
21
+ chatId: target.chatId,
22
+ };
23
+ },
24
+
25
+ // A WhatsApp number's inbound conversation is keyed per chat and can be
26
+ // reset between sends; a proactive post re-binds it so the next inbound
27
+ // from the number lands where the post lives.
28
+ bindsChatOnProactiveSend: true,
29
+
12
30
  async deliver(_ctx, payload) {
13
31
  const { chatId, text, attachments, approval } = payload;
14
32
 
@@ -54,4 +54,12 @@ describe("workspace git heartbeat on the monitoring worker", () => {
54
54
  expect(worker).toContain("await workspaceGitHeartbeat?.stop()");
55
55
  expect(lifecycle).not.toContain("startWorkspaceHeartbeatService");
56
56
  });
57
+
58
+ test("daemon lifecycle does not stop the in-process heartbeat singleton", () => {
59
+ const shutdown = readFileSync(
60
+ join(SRC_ROOT, "daemon/shutdown-handlers.ts"),
61
+ "utf8",
62
+ );
63
+ expect(shutdown).not.toContain("stopWorkspaceHeartbeatService");
64
+ });
57
65
  });