@vellumai/assistant 0.11.2 → 0.11.3-staging.2

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 (720) hide show
  1. package/ARCHITECTURE.md +58 -3
  2. package/docs/trusted-contact-access.md +9 -5
  3. package/docs/vellum-doctor.md +195 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  7. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
  9. package/node_modules/@vellumai/environments/package.json +2 -1
  10. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  11. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  12. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  13. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  14. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  19. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
  20. package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
  21. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  22. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  23. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
  24. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  25. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
  26. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
  27. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  28. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  29. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  30. package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
  31. package/openapi.yaml +589 -30
  32. package/package.json +1 -1
  33. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
  35. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  36. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  37. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  38. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  39. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  40. package/src/__tests__/anthropic-provider.test.ts +165 -30
  41. package/src/__tests__/app-control-flow.test.ts +9 -8
  42. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  43. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  44. package/src/__tests__/attachments-store.test.ts +130 -68
  45. package/src/__tests__/attachments.test.ts +36 -36
  46. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
  48. package/src/__tests__/canned-reply-release.test.ts +86 -0
  49. package/src/__tests__/channel-guardian.test.ts +59 -29
  50. package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
  51. package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
  52. package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
  53. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  54. package/src/__tests__/consult-deadline.test.ts +65 -0
  55. package/src/__tests__/conversation-agent-loop.test.ts +438 -2
  56. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  57. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  58. package/src/__tests__/conversation-error.test.ts +22 -3
  59. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  60. package/src/__tests__/conversation-fork-referential.test.ts +352 -0
  61. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  62. package/src/__tests__/conversation-group-tools.test.ts +32 -2
  63. package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
  64. package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
  65. package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
  66. package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
  67. package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
  68. package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
  69. package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
  70. package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
  71. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
  72. package/src/__tests__/conversation-queue.test.ts +433 -1
  73. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  74. package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
  75. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  76. package/src/__tests__/conversation-store.test.ts +26 -10
  77. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  78. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  79. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  80. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  81. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  82. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  83. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  84. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  85. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  90. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  91. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  92. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  93. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  94. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  95. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  96. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  97. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  98. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  99. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  100. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  101. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  102. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  103. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  104. package/src/__tests__/disk-usage.test.ts +35 -20
  105. package/src/__tests__/document-sync-tags.test.ts +379 -0
  106. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  107. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  108. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  109. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  110. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  111. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  112. package/src/__tests__/file-list-tool.test.ts +12 -10
  113. package/src/__tests__/file-ops-service.test.ts +151 -44
  114. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  115. package/src/__tests__/file-read-tool.test.ts +21 -9
  116. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  117. package/src/__tests__/filesystem-tools.test.ts +82 -66
  118. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  119. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  120. package/src/__tests__/helpers/hub-clients.ts +42 -0
  121. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  122. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  123. package/src/__tests__/history-repair.test.ts +112 -12
  124. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  125. package/src/__tests__/host-bash-routes.test.ts +67 -0
  126. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  127. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  128. package/src/__tests__/host-file-proxy.test.ts +3 -7
  129. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  130. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  131. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  132. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  133. package/src/__tests__/identity-routes.test.ts +13 -13
  134. package/src/__tests__/image-conversion.test.ts +29 -26
  135. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  136. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  137. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  138. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  139. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  140. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  141. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  142. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  143. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  144. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  145. package/src/__tests__/list-messages-queued.test.ts +49 -6
  146. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  147. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  148. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  149. package/src/__tests__/llm-usage-store.test.ts +55 -0
  150. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  151. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  152. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  153. package/src/__tests__/migration-export-http.test.ts +1 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  155. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  156. package/src/__tests__/migration-validate-http.test.ts +1 -1
  157. package/src/__tests__/mtime-cache.test.ts +65 -3
  158. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  159. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  160. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  161. package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
  162. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  163. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  164. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  165. package/src/__tests__/path-policy.test.ts +164 -1
  166. package/src/__tests__/persist-media-references.test.ts +2 -2
  167. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  168. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  169. package/src/__tests__/persona-resolver.test.ts +154 -0
  170. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  171. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  172. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  173. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  174. package/src/__tests__/profiler-routes.test.ts +13 -13
  175. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  176. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  177. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  178. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  179. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  180. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  181. package/src/__tests__/run-due-schedules.test.ts +174 -0
  182. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  183. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  184. package/src/__tests__/schedule-routes.test.ts +730 -8
  185. package/src/__tests__/schedule-store.test.ts +540 -0
  186. package/src/__tests__/schedule-tools.test.ts +103 -0
  187. package/src/__tests__/scheduler-wake.test.ts +5 -0
  188. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  189. package/src/__tests__/secure-keys.test.ts +91 -0
  190. package/src/__tests__/server-history-render.test.ts +97 -7
  191. package/src/__tests__/size-guard.test.ts +8 -8
  192. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  193. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  194. package/src/__tests__/skills-install-staging.test.ts +15 -6
  195. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  196. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  197. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  198. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  199. package/src/__tests__/starter-task-flow.test.ts +5 -4
  200. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  201. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  202. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  203. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  204. package/src/__tests__/subagent-disposal.test.ts +10 -0
  205. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  206. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  207. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  208. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  209. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  210. package/src/__tests__/subagent-persistence.test.ts +141 -0
  211. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  212. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  213. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  214. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  215. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  216. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  217. package/src/__tests__/subagent-tools.test.ts +1901 -108
  218. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  219. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  220. package/src/__tests__/system-prompt.test.ts +82 -7
  221. package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
  222. package/src/__tests__/tool-result-spool.test.ts +74 -18
  223. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  224. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  225. package/src/__tests__/tools-audio-read.test.ts +10 -10
  226. package/src/__tests__/tools-get-route.test.ts +85 -12
  227. package/src/__tests__/tools-list-cli.test.ts +171 -0
  228. package/src/__tests__/top-level-renderer.test.ts +54 -1
  229. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
  230. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  231. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  232. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  233. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  234. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  235. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  236. package/src/__tests__/usage-routes.test.ts +62 -0
  237. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  238. package/src/__tests__/visible-app-context.test.ts +20 -4
  239. package/src/__tests__/web-search-history.test.ts +96 -0
  240. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  241. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  242. package/src/acp/failure-error.ts +3 -5
  243. package/src/agent/attachments.ts +26 -24
  244. package/src/agent/history-repair/history-repair.ts +27 -26
  245. package/src/agent/image-optimize.ts +6 -6
  246. package/src/agent/loop.ts +40 -22
  247. package/src/agent/message-types.ts +3 -3
  248. package/src/api/constants/document-tools.ts +55 -0
  249. package/src/api/events/question-answered.ts +53 -0
  250. package/src/api/events/tool-result.ts +7 -0
  251. package/src/api/index.ts +13 -0
  252. package/src/api/responses/conversation-message.ts +11 -0
  253. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  254. package/src/api/surfaces.ts +36 -8
  255. package/src/approvals/guardian-channel-delivery.ts +83 -0
  256. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  257. package/src/approvals/guardian-request-resolvers.ts +222 -135
  258. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  259. package/src/calls/media-stream-server.ts +9 -1
  260. package/src/calls/voice-session-bridge.ts +38 -0
  261. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  262. package/src/channels/config.ts +7 -2
  263. package/src/channels/gateway-verification-sessions.ts +7 -2
  264. package/src/channels/types.ts +7 -1
  265. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  266. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  267. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  268. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  269. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  270. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  271. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  272. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  273. package/src/cli/commands/conversations.ts +7 -3
  274. package/src/cli/commands/credentials.help.ts +7 -0
  275. package/src/cli/commands/inference-profiles.ts +9 -3
  276. package/src/cli/commands/inference.help.ts +16 -2
  277. package/src/cli/commands/mcp.help.ts +4 -2
  278. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  279. package/src/cli/commands/notifications.ts +3 -6
  280. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  281. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  282. package/src/cli/commands/oauth/connect.test.ts +26 -1
  283. package/src/cli/commands/oauth/connect.ts +4 -1
  284. package/src/cli/commands/oauth/index.help.ts +3 -1
  285. package/src/cli/commands/oauth/providers.ts +3 -0
  286. package/src/cli/commands/plugins.help.ts +25 -2
  287. package/src/cli/commands/plugins.ts +289 -10
  288. package/src/cli/commands/schedules.help.ts +14 -12
  289. package/src/cli/commands/schedules.ts +19 -5
  290. package/src/cli/commands/tools.help.ts +4 -2
  291. package/src/cli/commands/tools.ts +38 -0
  292. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  293. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  294. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  295. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  296. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  297. package/src/cli/lib/install-from-github.ts +61 -0
  298. package/src/cli/lib/install-from-platform.ts +13 -2
  299. package/src/cli/lib/plugin-surfaces.ts +124 -4
  300. package/src/cli/lib/upgrade-plugin.ts +18 -0
  301. package/src/cli.ts +1 -1
  302. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  303. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  304. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  305. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  306. package/src/config/bundled-skills/AGENTS.md +1 -1
  307. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  308. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  309. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  310. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  311. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  312. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  313. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  314. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  315. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  316. package/src/config/default-profile-catalog.ts +6 -2
  317. package/src/config/feature-flag-registry.json +25 -17
  318. package/src/config/llm-resolver.ts +45 -15
  319. package/src/config/loader.ts +70 -1
  320. package/src/config/profile-tool-support.ts +110 -0
  321. package/src/config/sanitize-for-transfer.ts +11 -0
  322. package/src/config/schema.ts +4 -0
  323. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  324. package/src/config/schemas/channels.ts +13 -0
  325. package/src/config/schemas/memory-retrospective.ts +25 -0
  326. package/src/config/schemas/plugin-updates.ts +55 -0
  327. package/src/config/schemas/services.ts +8 -4
  328. package/src/config/schemas/stt.ts +17 -5
  329. package/src/config/schemas/timeouts.ts +1 -1
  330. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  331. package/src/contacts/guardian-delivery-reader.ts +29 -2
  332. package/src/context/compactor.ts +4 -4
  333. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  334. package/src/context/strip-injections.ts +42 -0
  335. package/src/context/tool-result-spool.ts +32 -18
  336. package/src/credential-execution/ces-runtime.ts +3 -0
  337. package/src/credential-execution/client.ts +22 -1
  338. package/src/credential-execution/process-manager.test.ts +99 -0
  339. package/src/credential-execution/process-manager.ts +62 -16
  340. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  341. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  342. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  343. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  344. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  345. package/src/daemon/channel-ui-capability.ts +4 -4
  346. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  347. package/src/daemon/conversation-agent-loop.ts +318 -148
  348. package/src/daemon/conversation-attachments.ts +1 -1
  349. package/src/daemon/conversation-error.ts +8 -1
  350. package/src/daemon/conversation-initial-prompt.ts +16 -38
  351. package/src/daemon/conversation-lifecycle.ts +154 -32
  352. package/src/daemon/conversation-messaging.ts +106 -43
  353. package/src/daemon/conversation-process.ts +131 -33
  354. package/src/daemon/conversation-queue-manager.ts +10 -0
  355. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  356. package/src/daemon/conversation-store.ts +52 -3
  357. package/src/daemon/conversation-surface-state.ts +9 -0
  358. package/src/daemon/conversation-surfaces.ts +403 -210
  359. package/src/daemon/conversation-tool-setup.ts +56 -7
  360. package/src/daemon/conversation-turn-finalize.ts +134 -89
  361. package/src/daemon/conversation.ts +111 -27
  362. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  363. package/src/daemon/disk-pressure-guard.ts +3 -3
  364. package/src/daemon/doordash-steps.ts +5 -5
  365. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  366. package/src/daemon/handlers/config-channels.ts +48 -5
  367. package/src/daemon/handlers/conversation-history.ts +1 -1
  368. package/src/daemon/handlers/conversations.ts +62 -17
  369. package/src/daemon/handlers/shared.ts +12 -0
  370. package/src/daemon/handlers/skills.ts +11 -1
  371. package/src/daemon/identity-helpers.ts +38 -10
  372. package/src/daemon/install-assistant-command.ts +322 -0
  373. package/src/daemon/lifecycle.ts +14 -3
  374. package/src/daemon/message-types/conversations.ts +6 -7
  375. package/src/daemon/message-types/sync.ts +1 -0
  376. package/src/daemon/persist-media-references.ts +23 -14
  377. package/src/daemon/process-message.ts +6 -6
  378. package/src/daemon/tool-setup-types.ts +19 -122
  379. package/src/daemon/tool-side-effects.ts +17 -94
  380. package/src/daemon/trust-context-types.ts +39 -0
  381. package/src/daemon/trust-context.ts +14 -6
  382. package/src/daemon/turn-tail-chain.ts +73 -0
  383. package/src/daemon/web-search-history.ts +47 -33
  384. package/src/heartbeat/heartbeat-service.ts +6 -6
  385. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  386. package/src/ipc/assistant-server.ts +17 -10
  387. package/src/ipc/cli-client.ts +10 -1
  388. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  389. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  390. package/src/ipc/socket-cleanup.ts +9 -7
  391. package/src/ipc/socket-path.ts +4 -96
  392. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  393. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  394. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  395. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  396. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  397. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  398. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  399. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  400. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  401. package/src/live-voice/live-voice-archive.ts +13 -13
  402. package/src/live-voice/live-voice-connection.ts +7 -1
  403. package/src/live-voice/live-voice-photo.ts +169 -0
  404. package/src/live-voice/live-voice-session.ts +108 -4
  405. package/src/live-voice/protocol.ts +89 -1
  406. package/src/mcp/client.ts +10 -0
  407. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  408. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  409. package/src/messaging/providers/callback-routing.ts +1 -0
  410. package/src/messaging/providers/discord/api.ts +312 -0
  411. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  412. package/src/messaging/providers/discord/render.test.ts +204 -0
  413. package/src/messaging/providers/discord/render.ts +195 -0
  414. package/src/messaging/providers/discord/send.test.ts +179 -0
  415. package/src/messaging/providers/discord/send.ts +212 -0
  416. package/src/messaging/providers/discord/transport.ts +81 -0
  417. package/src/messaging/providers/index.ts +3 -1
  418. package/src/messaging/providers/retry-policy.test.ts +247 -0
  419. package/src/messaging/providers/retry-policy.ts +206 -0
  420. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  421. package/src/messaging/providers/slack/mention-source.ts +287 -0
  422. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  423. package/src/messaging/providers/whatsapp/api.ts +47 -136
  424. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  425. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  426. package/src/monitoring/db-integrity-sample.ts +6 -1
  427. package/src/monitoring/plugin-auto-update.ts +359 -0
  428. package/src/monitoring/resource-sampler.ts +21 -5
  429. package/src/monitoring/worker.ts +13 -0
  430. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  431. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  432. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  433. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  434. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  435. package/src/notifications/adapters/platform.ts +31 -2
  436. package/src/notifications/assistant-reply-producer.ts +79 -32
  437. package/src/notifications/broadcaster.ts +30 -10
  438. package/src/notifications/copy-composer.ts +69 -0
  439. package/src/notifications/emit-signal.ts +82 -15
  440. package/src/notifications/events-store.ts +10 -2
  441. package/src/notifications/home-feed-side-effect.ts +6 -3
  442. package/src/notifications/notification-utils.ts +173 -5
  443. package/src/notifications/signal.ts +12 -0
  444. package/src/oauth/AGENTS.md +4 -2
  445. package/src/oauth/byo-connection.ts +6 -1
  446. package/src/oauth/credential-token-resolver.ts +3 -10
  447. package/src/oauth/manual-token-connection.ts +29 -64
  448. package/src/oauth/manual-token-providers.ts +62 -0
  449. package/src/oauth/provider-serializer.ts +13 -0
  450. package/src/oauth/seed-providers.ts +62 -11
  451. package/src/onboarding/onboarding-events-store.ts +91 -1
  452. package/src/permissions/question-prompter.test.ts +4 -0
  453. package/src/permissions/question-prompter.ts +21 -4
  454. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  455. package/src/persistence/attachments-store.ts +15 -15
  456. package/src/persistence/conversation-attention-store.ts +30 -10
  457. package/src/persistence/conversation-bootstrap.ts +17 -0
  458. package/src/persistence/conversation-crud.ts +363 -97
  459. package/src/persistence/conversation-group-migration.ts +1 -1
  460. package/src/persistence/conversation-key-store.ts +38 -10
  461. package/src/persistence/conversation-lineage.ts +226 -0
  462. package/src/persistence/conversation-plugin-facade.ts +28 -3
  463. package/src/persistence/conversation-queries.ts +207 -33
  464. package/src/persistence/conversation-types.ts +107 -1
  465. package/src/persistence/jobs-store.ts +32 -8
  466. package/src/persistence/llm-usage-store.ts +22 -0
  467. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  468. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  469. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  470. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  471. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  472. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  473. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  474. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  475. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  476. package/src/persistence/schema/conversations.ts +32 -0
  477. package/src/persistence/schema/infrastructure.ts +3 -0
  478. package/src/persistence/steps.ts +30 -0
  479. package/src/persistence/subagent-store.ts +148 -6
  480. package/src/platform/managed-speech.test.ts +20 -0
  481. package/src/platform/managed-speech.ts +9 -0
  482. package/src/plugin-api/index.ts +8 -0
  483. package/src/plugin-api/webhook-url.ts +151 -0
  484. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  485. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  486. package/src/plugins/collect-source-versions.ts +4 -27
  487. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  488. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  489. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  490. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  491. package/src/plugins/defaults/index.ts +1 -1
  492. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  493. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  494. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  495. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  496. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  497. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  498. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  499. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  500. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  501. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  502. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  503. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  504. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  505. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  506. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  507. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  508. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  509. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  510. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  511. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  512. package/src/plugins/external-plugin-loader.ts +17 -8
  513. package/src/plugins/installed-plugin-dirs.ts +101 -0
  514. package/src/plugins/mtime-cache.ts +34 -22
  515. package/src/plugins/types.ts +69 -8
  516. package/src/prompts/persona-resolver.ts +6 -2
  517. package/src/prompts/system-prompt.ts +72 -17
  518. package/src/providers/__tests__/inference.test.ts +40 -0
  519. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  520. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  521. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  522. package/src/providers/anthropic/client.ts +112 -95
  523. package/src/providers/connection-resolution.ts +35 -1
  524. package/src/providers/gemini/client.ts +5 -5
  525. package/src/providers/inference/adapter-factory.ts +16 -10
  526. package/src/providers/inference/auth.ts +16 -0
  527. package/src/providers/inference/connection-availability.ts +164 -12
  528. package/src/providers/inference/connections.ts +25 -1
  529. package/src/providers/media-resolve.ts +21 -11
  530. package/src/providers/model-catalog.ts +15 -0
  531. package/src/providers/openai/chat-completions-provider.ts +4 -4
  532. package/src/providers/openai/responses-provider.ts +12 -25
  533. package/src/providers/registry.ts +9 -4
  534. package/src/providers/retry.ts +25 -0
  535. package/src/providers/server-tool-pairing.ts +134 -0
  536. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  537. package/src/providers/speech-to-text/deepgram.ts +1 -0
  538. package/src/providers/speech-to-text/resolve.ts +80 -10
  539. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  540. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  541. package/src/providers/types.ts +26 -6
  542. package/src/providers/vellum-model-routing.ts +6 -7
  543. package/src/runtime/AGENTS.md +2 -0
  544. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  545. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  546. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  547. package/src/runtime/agent-wake.ts +63 -24
  548. package/src/runtime/assistant-event-hub.ts +87 -12
  549. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  550. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  551. package/src/runtime/auth/same-actor.ts +56 -8
  552. package/src/runtime/auth/scopes.ts +12 -0
  553. package/src/runtime/auth/types.ts +2 -0
  554. package/src/runtime/background-job-runner.ts +5 -27
  555. package/src/runtime/channel-readiness-service.ts +59 -3
  556. package/src/runtime/channel-readiness-types.ts +18 -0
  557. package/src/runtime/desktop-presence.ts +69 -0
  558. package/src/runtime/http-server.ts +12 -0
  559. package/src/runtime/local-actor-identity.ts +11 -9
  560. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  561. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  562. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  563. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  564. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  565. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  566. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  567. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  568. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  569. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  570. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  571. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  572. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  573. package/src/runtime/routes/archive-utils.ts +16 -12
  574. package/src/runtime/routes/attachment-routes.ts +13 -6
  575. package/src/runtime/routes/canned-reply-release.ts +54 -0
  576. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  577. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  578. package/src/runtime/routes/client-routes.ts +114 -2
  579. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  580. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  581. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  582. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  583. package/src/runtime/routes/conversation-routes.ts +360 -293
  584. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  585. package/src/runtime/routes/debug-routes.ts +4 -1
  586. package/src/runtime/routes/default-provider-routes.ts +7 -11
  587. package/src/runtime/routes/documents-routes.ts +44 -1
  588. package/src/runtime/routes/events-routes.ts +28 -1
  589. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  590. package/src/runtime/routes/host-bash-routes.ts +2 -0
  591. package/src/runtime/routes/host-browser-routes.ts +2 -0
  592. package/src/runtime/routes/host-cu-routes.ts +2 -0
  593. package/src/runtime/routes/host-file-routes.ts +2 -0
  594. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  595. package/src/runtime/routes/identity-routes.ts +6 -6
  596. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  597. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  598. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  599. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  600. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  601. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  602. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  603. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  604. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  605. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  606. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  607. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  608. package/src/runtime/routes/log-export-routes.ts +1 -1
  609. package/src/runtime/routes/notification-routes.ts +0 -14
  610. package/src/runtime/routes/oauth-providers.ts +1 -0
  611. package/src/runtime/routes/plugins-routes.ts +106 -1
  612. package/src/runtime/routes/profiler-routes.ts +4 -2
  613. package/src/runtime/routes/question-routes.ts +28 -10
  614. package/src/runtime/routes/retrospective-routes.ts +9 -3
  615. package/src/runtime/routes/schedule-routes.ts +210 -12
  616. package/src/runtime/routes/secret-routes.ts +40 -23
  617. package/src/runtime/routes/settings-routes.ts +41 -13
  618. package/src/runtime/routes/skills-routes.ts +79 -0
  619. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  620. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  621. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  622. package/src/runtime/services/conversation-serializer.ts +24 -9
  623. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  624. package/src/runtime/sync/resource-sync-events.ts +79 -1
  625. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  626. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  627. package/src/runtime/verification-outbound-actions.ts +250 -222
  628. package/src/runtime/verification-templates.ts +41 -30
  629. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  630. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  631. package/src/schedule/inference-profile.ts +64 -3
  632. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  633. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  634. package/src/schedule/plugin-schedules-gate.ts +23 -0
  635. package/src/schedule/schedule-store.ts +520 -26
  636. package/src/schedule/scheduler.ts +38 -0
  637. package/src/security/credential-backend.ts +51 -4
  638. package/src/security/encrypted-store.ts +64 -42
  639. package/src/security/secure-keys.ts +55 -7
  640. package/src/skills/catalog-install.ts +20 -7
  641. package/src/skills/inline-command-runner.ts +2 -7
  642. package/src/skills/skill-history.test.ts +269 -0
  643. package/src/skills/skill-history.ts +250 -0
  644. package/src/skills/skillssh-registry.ts +1 -1
  645. package/src/stt/daemon-batch-transcriber.ts +8 -6
  646. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  647. package/src/subagent/consult-prompt.ts +3 -1
  648. package/src/subagent/consult-transcript.ts +1 -1
  649. package/src/subagent/manager.ts +304 -44
  650. package/src/subagent/progress-events.ts +35 -0
  651. package/src/subagent/role-resolution.ts +88 -0
  652. package/src/subagent/types.ts +302 -67
  653. package/src/subagent/validate-allowlists.ts +3 -4
  654. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  655. package/src/telegram/webhook-health.ts +101 -3
  656. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  657. package/src/telemetry/activation-funnel.ts +6 -1
  658. package/src/telemetry/live-voice-funnel.ts +75 -0
  659. package/src/telemetry/telemetry-event-sources.ts +6 -0
  660. package/src/telemetry/telemetry-wire-source.json +1 -1
  661. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  662. package/src/telemetry/types.ts +23 -4
  663. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  664. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  665. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  666. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  667. package/src/tools/browser/browser-execution.ts +1 -1
  668. package/src/tools/calls/call-start.ts +8 -5
  669. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  670. package/src/tools/document/document-tool.ts +11 -4
  671. package/src/tools/execution-timeout.ts +9 -2
  672. package/src/tools/executor.ts +7 -6
  673. package/src/tools/filesystem/edit.ts +1 -1
  674. package/src/tools/filesystem/list.ts +1 -1
  675. package/src/tools/filesystem/read.ts +7 -13
  676. package/src/tools/filesystem/search.ts +23 -21
  677. package/src/tools/filesystem/write.ts +4 -1
  678. package/src/tools/host-filesystem/edit.ts +7 -2
  679. package/src/tools/host-filesystem/read.ts +21 -8
  680. package/src/tools/host-filesystem/write.ts +10 -2
  681. package/src/tools/host-terminal/host-shell.ts +2 -2
  682. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  683. package/src/tools/permission-checker.ts +8 -3
  684. package/src/tools/schedule/delete.ts +13 -1
  685. package/src/tools/schedule/list.ts +14 -3
  686. package/src/tools/schedule/update.ts +56 -33
  687. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  688. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  689. package/src/tools/shared/filesystem/image-read.ts +12 -12
  690. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  691. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  692. package/src/tools/shared/input-misuse.ts +72 -0
  693. package/src/tools/skills/scaffold-managed.ts +82 -0
  694. package/src/tools/skills/skill-tool-factory.ts +14 -1
  695. package/src/tools/subagent/consult-deadline.ts +12 -7
  696. package/src/tools/subagent/message.ts +3 -1
  697. package/src/tools/subagent/read.ts +66 -4
  698. package/src/tools/subagent/spawn.ts +500 -78
  699. package/src/tools/tool-approval-handler.ts +45 -4
  700. package/src/tools/types.ts +22 -3
  701. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  702. package/src/tools/ui-surface/visual-validation.ts +2 -0
  703. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  704. package/src/usage/subagent-attribution.ts +108 -0
  705. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  706. package/src/util/abort-reasons.ts +16 -0
  707. package/src/util/ansi.ts +44 -0
  708. package/src/util/disk-usage.ts +17 -13
  709. package/src/util/image-conversion.ts +20 -13
  710. package/src/util/schedule-source-key.ts +23 -0
  711. package/src/util/worker-process.ts +7 -1
  712. package/src/workspace/git-service.ts +33 -9
  713. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  714. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  715. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  716. package/src/workspace/migrations/registry.ts +4 -0
  717. package/src/workspace/top-level-renderer.ts +15 -2
  718. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  719. package/src/daemon/install-symlink.ts +0 -207
  720. package/src/notifications/deferred-emit.ts +0 -147
@@ -116,6 +116,7 @@ import {
116
116
  isConversationProcessing,
117
117
  isHiddenMessageMetadata,
118
118
  isProviderErrorMetadata,
119
+ isSuppressedQueuedMessage,
119
120
  isSystemCardMetadata,
120
121
  type MessageRow,
121
122
  recordConversationPersistedSeq,
@@ -172,6 +173,7 @@ import {
172
173
  emitCannedMessageComplete,
173
174
  persistCannedAssistantCard,
174
175
  } from "./canned-message-complete.js";
176
+ import { scheduleCannedReplyRelease } from "./canned-reply-release.js";
175
177
  import { buildChannelMetadata } from "./channel-metadata.js";
176
178
  import {
177
179
  BadRequestError,
@@ -639,7 +641,7 @@ async function tryConsumeGuardianReply(params: {
639
641
  attachments,
640
642
  });
641
643
 
642
- const cleanUserMessage = createUserMessage(content, attachments);
644
+ const cleanUserMessage = await createUserMessage(content, attachments);
643
645
  const llmUserMessage = enrichMessageWithSourcePaths(
644
646
  cleanUserMessage,
645
647
  attachments,
@@ -691,6 +693,68 @@ async function tryConsumeGuardianReply(params: {
691
693
  return { consumed: true, messageId };
692
694
  }
693
695
 
696
+ /**
697
+ * Read the notification discriminators a client uses to keep daemon-injected
698
+ * rows out of the rendered transcript off a message's parsed metadata.
699
+ *
700
+ * One extraction shared by the persisted-history rows and the queued rows
701
+ * synthesized from the in-memory queue, so a row carries the same flags
702
+ * whichever side of the drain it is fetched from.
703
+ */
704
+ function extractNotificationDiscriminators(
705
+ meta: Record<string, unknown>,
706
+ ): Pick<
707
+ ConversationMessage,
708
+ "subagentNotification" | "acpNotification" | "backgroundEventNotification"
709
+ > {
710
+ const discriminators: Pick<
711
+ ConversationMessage,
712
+ "subagentNotification" | "acpNotification" | "backgroundEventNotification"
713
+ > = {};
714
+
715
+ // Every wake persists a `<background_event source="...">` trigger row
716
+ // (see `persistWakeTriggerMessage`) that the LLM reads. Flag any such
717
+ // row so clients hide it from the transcript like a subagent/ACP
718
+ // notification: the user-facing "Conversation Woke" card (or, for a
719
+ // backgrounded bash run, the inline terminal card) carries the status.
720
+ if (isBackgroundEventMetadata(meta)) {
721
+ discriminators.backgroundEventNotification = true;
722
+ }
723
+
724
+ const subagent = meta.subagentNotification as
725
+ | Record<string, unknown>
726
+ | undefined;
727
+ if (
728
+ subagent &&
729
+ typeof subagent.subagentId === "string" &&
730
+ typeof subagent.label === "string"
731
+ ) {
732
+ discriminators.subagentNotification = {
733
+ subagentId: subagent.subagentId,
734
+ label: subagent.label,
735
+ status:
736
+ typeof subagent.status === "string" ? subagent.status : "completed",
737
+ ...(typeof subagent.error === "string" ? { error: subagent.error } : {}),
738
+ ...(typeof subagent.conversationId === "string"
739
+ ? { conversationId: subagent.conversationId }
740
+ : {}),
741
+ ...(typeof subagent.objective === "string"
742
+ ? { objective: subagent.objective }
743
+ : {}),
744
+ };
745
+ }
746
+
747
+ const acp = meta.acpNotification as Record<string, unknown> | undefined;
748
+ if (acp && typeof acp.acpSessionId === "string") {
749
+ discriminators.acpNotification = {
750
+ acpSessionId: acp.acpSessionId,
751
+ ...(typeof acp.agent === "string" ? { agent: acp.agent } : {}),
752
+ };
753
+ }
754
+
755
+ return discriminators;
756
+ }
757
+
694
758
  /**
695
759
  * Render the live conversation's in-memory message queue into history rows.
696
760
  *
@@ -713,12 +777,17 @@ function buildQueuedMessagePayloads(
713
777
  return [];
714
778
  }
715
779
 
716
- // Hidden sends are suppressed from the transcript at every stage — echo,
717
- // persisted row, and here the in-memory queue window: a latest-page fetch
718
- // while the item still awaits drain must not surface it as a queued bubble.
780
+ // Rows that never render as a user bubble are suppressed at every stage —
781
+ // echo, persisted row, and here the in-memory queue window: a latest-page
782
+ // fetch while the item still awaits drain must not surface it as a queued
783
+ // bubble. That covers hidden sends and the daemon's own injected
784
+ // notifications (a subagent's completion summary enqueued into a busy
785
+ // parent, an ACP run outcome, a wake trigger) — the user follows those
786
+ // through their inline cards, never as a queued message they could steer or
787
+ // cancel.
719
788
  return conversation
720
789
  .snapshotQueuedMessages()
721
- .filter((item) => !isHiddenMessageMetadata(item.metadata))
790
+ .filter((item) => !isSuppressedQueuedMessage(item.metadata))
722
791
  .map((item, index) => {
723
792
  const text = item.displayContent ?? item.content;
724
793
  const attachments: RuntimeAttachmentMetadata[] = item.attachments.map(
@@ -755,15 +824,19 @@ function buildQueuedMessagePayloads(
755
824
  ...(item.clientMessageId
756
825
  ? { clientMessageId: item.clientMessageId }
757
826
  : {}),
827
+ // The filter above already drops every daemon-injected notification,
828
+ // so these flags are redundant today. Carrying them keeps a queued row
829
+ // self-describing on the wire, matching its persisted counterpart.
830
+ ...extractNotificationDiscriminators(item.metadata ?? {}),
758
831
  queueStatus: "queued" as const,
759
832
  queuePosition: index + 1,
760
833
  };
761
834
  });
762
835
  }
763
836
 
764
- export function handleListMessages({
837
+ export async function handleListMessages({
765
838
  queryParams,
766
- }: RouteHandlerArgs): Record<string, unknown> {
839
+ }: RouteHandlerArgs): Promise<Record<string, unknown>> {
767
840
  const conversationId = queryParams?.conversationId;
768
841
  const conversationKey = queryParams?.conversationKey;
769
842
 
@@ -920,23 +993,14 @@ export function handleListMessages({
920
993
  // was queued or its persistence was delayed (long assistant generation),
921
994
  // sentAt captures the actual event time. Falls back to createdAt.
922
995
  let sentAt: number | undefined;
923
- let subagentNotification:
924
- | {
925
- subagentId: string;
926
- label: string;
927
- status: string;
928
- error?: string;
929
- conversationId?: string;
930
- }
931
- | undefined;
932
- let acpNotification: { acpSessionId: string; agent?: string } | undefined;
933
- let backgroundEventNotification: boolean | undefined;
996
+ let notifications: ReturnType<typeof extractNotificationDiscriminators> =
997
+ {};
934
998
  let backgroundToolCompletion: ConversationMessage["backgroundToolCompletion"];
935
999
  let systemCard: boolean | undefined;
936
1000
  let providerError: ConversationMessage["providerError"];
937
1001
  if (msg.metadata) {
938
1002
  try {
939
- const meta = JSON.parse(msg.metadata);
1003
+ const meta = JSON.parse(msg.metadata) as Record<string, unknown>;
940
1004
  if (typeof meta.sentAt === "number") {
941
1005
  sentAt = meta.sentAt;
942
1006
  }
@@ -958,14 +1022,7 @@ export function handleListMessages({
958
1022
  : {}),
959
1023
  };
960
1024
  }
961
- // Every wake persists a `<background_event source="...">` trigger row
962
- // (see `persistWakeTriggerMessage`) that the LLM reads. Flag any such
963
- // row so clients hide it from the transcript like a subagent/ACP
964
- // notification — the user-facing "Conversation Woke" card (or, for a
965
- // backgrounded bash run, the inline terminal card) carries the status.
966
- if (isBackgroundEventMetadata(meta)) {
967
- backgroundEventNotification = true;
968
- }
1025
+ notifications = extractNotificationDiscriminators(meta);
969
1026
  // `persistWakeTriggerMessage` stamps the structured completion onto the
970
1027
  // same wake row, letting the web rebuild a terminal inline card from
971
1028
  // history after a restart (the in-memory completed ring does not survive).
@@ -975,32 +1032,6 @@ export function handleListMessages({
975
1032
  if (completionParse.success) {
976
1033
  backgroundToolCompletion = completionParse.data;
977
1034
  }
978
- if (meta.subagentNotification) {
979
- const n = meta.subagentNotification;
980
- if (typeof n.subagentId === "string" && typeof n.label === "string") {
981
- subagentNotification = {
982
- subagentId: n.subagentId,
983
- label: n.label,
984
- status: typeof n.status === "string" ? n.status : "completed",
985
- ...(typeof n.error === "string" ? { error: n.error } : {}),
986
- ...(typeof n.conversationId === "string"
987
- ? { conversationId: n.conversationId }
988
- : {}),
989
- ...(typeof n.objective === "string"
990
- ? { objective: n.objective }
991
- : {}),
992
- };
993
- }
994
- }
995
- if (meta.acpNotification) {
996
- const n = meta.acpNotification;
997
- if (typeof n.acpSessionId === "string") {
998
- acpNotification = {
999
- acpSessionId: n.acpSessionId,
1000
- ...(typeof n.agent === "string" ? { agent: n.agent } : {}),
1001
- };
1002
- }
1003
- }
1004
1035
  } catch {
1005
1036
  // Ignore malformed metadata
1006
1037
  }
@@ -1025,9 +1056,9 @@ export function handleListMessages({
1025
1056
  content,
1026
1057
  createdAt: msg.createdAt,
1027
1058
  sentAt,
1028
- subagentNotification,
1029
- acpNotification,
1030
- backgroundEventNotification,
1059
+ subagentNotification: notifications.subagentNotification,
1060
+ acpNotification: notifications.acpNotification,
1061
+ backgroundEventNotification: notifications.backgroundEventNotification,
1031
1062
  backgroundToolCompletion,
1032
1063
  systemCard,
1033
1064
  providerError,
@@ -1046,196 +1077,207 @@ export function handleListMessages({
1046
1077
  );
1047
1078
  const pendingQuestions = collectPendingQuestions(resolvedConversationId);
1048
1079
 
1049
- const messages: RuntimeMessagePayload[] = parsed.map((m) => {
1050
- const mergedMessageIds = m.id ? (mergedIdMap.get(m.id) ?? []) : [];
1051
-
1052
- // Hydrate the row's attachments from the DB. A metadata-only query avoids
1053
- // loading large base64 blobs for non-image attachments (documents, audio);
1054
- // full data is fetched only for images so the client can generate
1055
- // thumbnails for inline display on history restore. Merged messages
1056
- // (consecutive assistant merge) are queried too so their attachments
1057
- // aren't lost before DB compaction relinks them.
1058
- let msgAttachments: RuntimeAttachmentMetadata[] = [];
1059
- if (m.id) {
1060
- const idsToQuery = [m.id, ...mergedMessageIds];
1061
- const linked = idsToQuery.flatMap((id) =>
1062
- getAttachmentMetadataForMessage(id),
1063
- );
1064
- if (linked.length > 0) {
1065
- msgAttachments = linked.map((a) => {
1066
- // Hydrate image rows for inline thumbnails. Legacy HEIC can be
1067
- // stored under application/octet-stream (empty File.type fallback),
1068
- // so `.heic`/`.heif` rows are hydrated by filename too;
1069
- // normalizeImageBase64 sniffs the bytes and rewrites only genuine
1070
- // HEIF, which Chromium-based clients cannot decode. Filename and
1071
- // sizeBytes keep describing the stored original, which
1072
- // /attachments/:id/content serves verbatim for downloads.
1073
- const isImage = a.mimeType.startsWith("image/");
1074
- const isLegacyHeic = !isImage && isHeicFilename(a.originalFilename);
1075
- const full =
1076
- isImage || isLegacyHeic
1077
- ? getAttachmentById(a.id, { hydrateFileData: true })
1078
- : null;
1079
- const display = full?.dataBase64
1080
- ? normalizeImageBase64(a.mimeType, full.dataBase64)
1081
- : null;
1082
- // Image rows carry data even when unconverted (thumbnails); a
1083
- // non-image row only becomes renderable once conversion yields a
1084
- // JPEG, so it stays metadata-only when conversion is unavailable.
1085
- const useDisplay =
1086
- display && (isImage || display.converted) ? display : null;
1087
- return {
1088
- id: a.id,
1089
- filename: a.originalFilename,
1090
- mimeType: useDisplay?.mimeType ?? a.mimeType,
1091
- sizeBytes: a.sizeBytes,
1092
- kind: useDisplay?.converted
1093
- ? classifyKind(useDisplay.mimeType)
1094
- : a.kind,
1095
- ...(useDisplay ? { data: useDisplay.dataBase64 } : {}),
1096
- ...(a.thumbnailBase64 ? { thumbnailData: a.thumbnailBase64 } : {}),
1097
- fileBacked: true,
1098
- };
1099
- });
1080
+ const messages: RuntimeMessagePayload[] = await Promise.all(
1081
+ parsed.map(async (m) => {
1082
+ const mergedMessageIds = m.id ? (mergedIdMap.get(m.id) ?? []) : [];
1083
+
1084
+ // Hydrate the row's attachments from the DB. A metadata-only query avoids
1085
+ // loading large base64 blobs for non-image attachments (documents, audio);
1086
+ // full data is fetched only for images so the client can generate
1087
+ // thumbnails for inline display on history restore. Merged messages
1088
+ // (consecutive assistant merge) are queried too so their attachments
1089
+ // aren't lost before DB compaction relinks them.
1090
+ let msgAttachments: RuntimeAttachmentMetadata[] = [];
1091
+ if (m.id) {
1092
+ const idsToQuery = [m.id, ...mergedMessageIds];
1093
+ const linked = idsToQuery.flatMap((id) =>
1094
+ getAttachmentMetadataForMessage(id),
1095
+ );
1096
+ if (linked.length > 0) {
1097
+ msgAttachments = await Promise.all(
1098
+ linked.map(async (a) => {
1099
+ // Hydrate image rows for inline thumbnails. Legacy HEIC can be
1100
+ // stored under application/octet-stream (empty File.type fallback),
1101
+ // so `.heic`/`.heif` rows are hydrated by filename too;
1102
+ // normalizeImageBase64 sniffs the bytes and rewrites only genuine
1103
+ // HEIF, which Chromium-based clients cannot decode. Filename and
1104
+ // sizeBytes keep describing the stored original, which
1105
+ // /attachments/:id/content serves verbatim for downloads.
1106
+ const isImage = a.mimeType.startsWith("image/");
1107
+ const isLegacyHeic =
1108
+ !isImage && isHeicFilename(a.originalFilename);
1109
+ const full =
1110
+ isImage || isLegacyHeic
1111
+ ? getAttachmentById(a.id, { hydrateFileData: true })
1112
+ : null;
1113
+ const display = full?.dataBase64
1114
+ ? await normalizeImageBase64(a.mimeType, full.dataBase64)
1115
+ : null;
1116
+ // Image rows carry data even when unconverted (thumbnails); a
1117
+ // non-image row only becomes renderable once conversion yields a
1118
+ // JPEG, so it stays metadata-only when conversion is unavailable.
1119
+ const useDisplay =
1120
+ display && (isImage || display.converted) ? display : null;
1121
+ return {
1122
+ id: a.id,
1123
+ filename: a.originalFilename,
1124
+ mimeType: useDisplay?.mimeType ?? a.mimeType,
1125
+ sizeBytes: a.sizeBytes,
1126
+ kind: useDisplay?.converted
1127
+ ? classifyKind(useDisplay.mimeType)
1128
+ : a.kind,
1129
+ ...(useDisplay ? { data: useDisplay.dataBase64 } : {}),
1130
+ ...(a.thumbnailBase64
1131
+ ? { thumbnailData: a.thumbnailBase64 }
1132
+ : {}),
1133
+ fileBacked: true,
1134
+ };
1135
+ }),
1136
+ );
1137
+ }
1100
1138
  }
1101
- }
1102
1139
 
1103
- // Align the hydrated rows with the file-block refs, then render. Rendering
1104
- // after alignment lets renderHistoryContent inline each `attachment` block
1105
- // during its single content walk, so `contentBlocks` comes back ready to
1106
- // ship with no post-processing. The aligned reorder/rewrite keeps the
1107
- // legacy `attachments` array and `contentOrder` positions consistent.
1108
- const attachmentRefs = collectAttachmentRefs(m.content);
1109
- const aligned = alignAttachments(attachmentRefs, msgAttachments);
1110
- msgAttachments = aligned.attachments;
1111
- const attachmentBlocks = attachmentRefs.map((_ref, refIdx) => {
1112
- const att = aligned.refIndexToAttachment.get(refIdx);
1113
- return att ? toAttachmentBlockRef(att) : null;
1114
- });
1115
- const rendered = renderHistoryContent(
1116
- m.content,
1117
- attachmentBlocks,
1118
- m.id ?? undefined,
1119
- );
1140
+ // Align the hydrated rows with the file-block refs, then render. Rendering
1141
+ // after alignment lets renderHistoryContent inline each `attachment` block
1142
+ // during its single content walk, so `contentBlocks` comes back ready to
1143
+ // ship with no post-processing. The aligned reorder/rewrite keeps the
1144
+ // legacy `attachments` array and `contentOrder` positions consistent.
1145
+ const attachmentRefs = collectAttachmentRefs(m.content);
1146
+ const aligned = alignAttachments(attachmentRefs, msgAttachments);
1147
+ msgAttachments = aligned.attachments;
1148
+ const attachmentBlocks = attachmentRefs.map((_ref, refIdx) => {
1149
+ const att = aligned.refIndexToAttachment.get(refIdx);
1150
+ return att ? toAttachmentBlockRef(att) : null;
1151
+ });
1152
+ const rendered = renderHistoryContent(
1153
+ m.content,
1154
+ attachmentBlocks,
1155
+ m.id ?? undefined,
1156
+ );
1120
1157
 
1121
- const toolCalls = enrichToolCallsWithQuestion(
1122
- enrichToolCallsWithConfirmation(rendered.toolCalls, {
1123
- workspaceDir,
1124
- pendingConfirmations,
1125
- }),
1126
- { pendingQuestions },
1127
- );
1158
+ const toolCalls = enrichToolCallsWithQuestion(
1159
+ enrichToolCallsWithConfirmation(rendered.toolCalls, {
1160
+ workspaceDir,
1161
+ pendingConfirmations,
1162
+ }),
1163
+ { pendingQuestions },
1164
+ );
1128
1165
 
1129
- // Strip <no_response/> markers from assistant messages so web/API clients
1130
- // never see the raw sentinel. Only assistant messages produce it; user
1131
- // messages are untouched. The filter is applied consistently to the
1132
- // segments, the contentOrder text refs, and the text blocks of
1133
- // contentBlocks.
1134
- let textSegments = rendered.textSegments;
1135
- let contentOrder = rendered.contentOrder;
1136
- let contentBlocks = rendered.contentBlocks;
1137
- if (m.role === "assistant") {
1138
- const keepIndices: number[] = [];
1139
- const filteredSegments: string[] = [];
1140
- for (let i = 0; i < rendered.textSegments.length; i++) {
1141
- const cleaned = rendered.textSegments[i]
1142
- .replace(NO_RESPONSE_INLINE_RE, "")
1143
- .trim();
1144
- if (cleaned.length > 0) {
1145
- keepIndices.push(i);
1146
- filteredSegments.push(cleaned);
1166
+ // Strip <no_response/> markers from assistant messages so web/API clients
1167
+ // never see the raw sentinel. Only assistant messages produce it; user
1168
+ // messages are untouched. The filter is applied consistently to the
1169
+ // segments, the contentOrder text refs, and the text blocks of
1170
+ // contentBlocks.
1171
+ let textSegments = rendered.textSegments;
1172
+ let contentOrder = rendered.contentOrder;
1173
+ let contentBlocks = rendered.contentBlocks;
1174
+ if (m.role === "assistant") {
1175
+ const keepIndices: number[] = [];
1176
+ const filteredSegments: string[] = [];
1177
+ for (let i = 0; i < rendered.textSegments.length; i++) {
1178
+ const cleaned = rendered.textSegments[i]
1179
+ .replace(NO_RESPONSE_INLINE_RE, "")
1180
+ .trim();
1181
+ if (cleaned.length > 0) {
1182
+ keepIndices.push(i);
1183
+ filteredSegments.push(cleaned);
1184
+ }
1147
1185
  }
1186
+ const indexMap = new Map<number, number>();
1187
+ keepIndices.forEach((oldIdx, newIdx) => indexMap.set(oldIdx, newIdx));
1188
+ contentOrder = rendered.contentOrder
1189
+ .map((entry) => {
1190
+ const tm = entry.match(/^text:(\d+)$/);
1191
+ if (!tm) {
1192
+ return entry;
1193
+ }
1194
+ const newIdx = indexMap.get(Number(tm[1]));
1195
+ return newIdx !== undefined ? `text:${newIdx}` : undefined;
1196
+ })
1197
+ .filter((e): e is string => e !== undefined);
1198
+ textSegments = filteredSegments;
1199
+ contentBlocks = rendered.contentBlocks
1200
+ .map((block) =>
1201
+ block.type === "text"
1202
+ ? {
1203
+ type: "text" as const,
1204
+ text: block.text.replace(NO_RESPONSE_INLINE_RE, "").trim(),
1205
+ }
1206
+ : block,
1207
+ )
1208
+ .filter((block) => block.type !== "text" || block.text.length > 0);
1148
1209
  }
1149
- const indexMap = new Map<number, number>();
1150
- keepIndices.forEach((oldIdx, newIdx) => indexMap.set(oldIdx, newIdx));
1151
- contentOrder = rendered.contentOrder
1152
- .map((entry) => {
1153
- const tm = entry.match(/^text:(\d+)$/);
1154
- if (!tm) {
1155
- return entry;
1156
- }
1157
- const newIdx = indexMap.get(Number(tm[1]));
1158
- return newIdx !== undefined ? `text:${newIdx}` : undefined;
1159
- })
1160
- .filter((e): e is string => e !== undefined);
1161
- textSegments = filteredSegments;
1162
- contentBlocks = rendered.contentBlocks
1163
- .map((block) =>
1164
- block.type === "text"
1165
- ? {
1166
- type: "text" as const,
1167
- text: block.text.replace(NO_RESPONSE_INLINE_RE, "").trim(),
1168
- }
1169
- : block,
1170
- )
1171
- .filter((block) => block.type !== "text" || block.text.length > 0);
1172
- }
1173
1210
 
1174
- // Ensure every hydrated attachment has a corresponding content block.
1175
- // renderHistoryContent inlines attachment blocks only when it has
1176
- // file-block refs with matching DB rows; directives (assistant-authored
1177
- // <vellum-attachment/> tags) don't leave a file block after stripping,
1178
- // so their attachments end up in the flat `attachments` array but not in
1179
- // `contentBlocks`. Append any that are missing so the canonical
1180
- // projection is complete.
1181
- const existingAttachmentIds = new Set(
1182
- contentBlocks
1183
- .filter(
1184
- (b): b is Extract<ConversationContentBlock, { type: "attachment" }> =>
1185
- b.type === "attachment",
1186
- )
1187
- .map((b) => b.attachment.id),
1188
- );
1189
- for (const att of msgAttachments) {
1190
- if (!existingAttachmentIds.has(att.id)) {
1191
- contentBlocks.push({
1192
- type: "attachment",
1193
- attachment: toAttachmentBlockRef(att),
1194
- });
1211
+ // Ensure every hydrated attachment has a corresponding content block.
1212
+ // renderHistoryContent inlines attachment blocks only when it has
1213
+ // file-block refs with matching DB rows; directives (assistant-authored
1214
+ // <vellum-attachment/> tags) don't leave a file block after stripping,
1215
+ // so their attachments end up in the flat `attachments` array but not in
1216
+ // `contentBlocks`. Append any that are missing so the canonical
1217
+ // projection is complete.
1218
+ const existingAttachmentIds = new Set(
1219
+ contentBlocks
1220
+ .filter(
1221
+ (
1222
+ b,
1223
+ ): b is Extract<ConversationContentBlock, { type: "attachment" }> =>
1224
+ b.type === "attachment",
1225
+ )
1226
+ .map((b) => b.attachment.id),
1227
+ );
1228
+ for (const att of msgAttachments) {
1229
+ if (!existingAttachmentIds.has(att.id)) {
1230
+ contentBlocks.push({
1231
+ type: "attachment",
1232
+ attachment: toAttachmentBlockRef(att),
1233
+ });
1234
+ }
1195
1235
  }
1196
- }
1197
1236
 
1198
- const alignedContentOrder = aligned.rewriteContentOrder(contentOrder);
1237
+ const alignedContentOrder = aligned.rewriteContentOrder(contentOrder);
1199
1238
 
1200
- // Use sentAt (actual event time) for the display timestamp when available,
1201
- // falling back to createdAt (persistence time). Clients use this display
1202
- // timestamp as their pagination cursor after memory-pressure trimming,
1203
- // while server-side pagination filters on createdAt. The mismatch is
1204
- // benign it may return slightly extra data on a page boundary but never
1205
- // loses messages.
1206
- const displayTimestamp = m.sentAt ?? m.createdAt;
1207
- return {
1208
- id: m.id ?? "",
1209
- ...(mergedMessageIds.length > 0 ? { mergedMessageIds } : {}),
1210
- ...(m.clientMessageId ? { clientMessageId: m.clientMessageId } : {}),
1211
- role: m.role,
1212
- timestamp: new Date(displayTimestamp).toISOString(),
1213
- attachments: msgAttachments,
1214
- ...(toolCalls.length > 0 ? { toolCalls } : {}),
1215
- ...(rendered.surfaces.length > 0 ? { surfaces: rendered.surfaces } : {}),
1216
- ...(textSegments.length > 0 ? { textSegments } : {}),
1217
- ...(rendered.thinkingSegments.length > 0
1218
- ? { thinkingSegments: rendered.thinkingSegments }
1219
- : {}),
1220
- ...(alignedContentOrder.length > 0
1221
- ? { contentOrder: alignedContentOrder }
1222
- : {}),
1223
- contentBlocks,
1224
- ...(m.subagentNotification
1225
- ? { subagentNotification: m.subagentNotification }
1226
- : {}),
1227
- ...(m.acpNotification ? { acpNotification: m.acpNotification } : {}),
1228
- ...(m.backgroundEventNotification
1229
- ? { backgroundEventNotification: true }
1230
- : {}),
1231
- ...(m.backgroundToolCompletion
1232
- ? { backgroundToolCompletion: m.backgroundToolCompletion }
1233
- : {}),
1234
- ...(m.systemCard ? { systemCard: true } : {}),
1235
- ...(m.providerError ? { providerError: m.providerError } : {}),
1236
- ...(m.slackMessage ? { slackMessage: m.slackMessage } : {}),
1237
- };
1238
- });
1239
+ // Use sentAt (actual event time) for the display timestamp when available,
1240
+ // falling back to createdAt (persistence time). Clients use this display
1241
+ // timestamp as their pagination cursor after memory-pressure trimming,
1242
+ // while server-side pagination filters on createdAt. The mismatch is
1243
+ // benign: it may return slightly extra data on a page boundary but never
1244
+ // loses messages.
1245
+ const displayTimestamp = m.sentAt ?? m.createdAt;
1246
+ return {
1247
+ id: m.id ?? "",
1248
+ ...(mergedMessageIds.length > 0 ? { mergedMessageIds } : {}),
1249
+ ...(m.clientMessageId ? { clientMessageId: m.clientMessageId } : {}),
1250
+ role: m.role,
1251
+ timestamp: new Date(displayTimestamp).toISOString(),
1252
+ attachments: msgAttachments,
1253
+ ...(toolCalls.length > 0 ? { toolCalls } : {}),
1254
+ ...(rendered.surfaces.length > 0
1255
+ ? { surfaces: rendered.surfaces }
1256
+ : {}),
1257
+ ...(textSegments.length > 0 ? { textSegments } : {}),
1258
+ ...(rendered.thinkingSegments.length > 0
1259
+ ? { thinkingSegments: rendered.thinkingSegments }
1260
+ : {}),
1261
+ ...(alignedContentOrder.length > 0
1262
+ ? { contentOrder: alignedContentOrder }
1263
+ : {}),
1264
+ contentBlocks,
1265
+ ...(m.subagentNotification
1266
+ ? { subagentNotification: m.subagentNotification }
1267
+ : {}),
1268
+ ...(m.acpNotification ? { acpNotification: m.acpNotification } : {}),
1269
+ ...(m.backgroundEventNotification
1270
+ ? { backgroundEventNotification: true }
1271
+ : {}),
1272
+ ...(m.backgroundToolCompletion
1273
+ ? { backgroundToolCompletion: m.backgroundToolCompletion }
1274
+ : {}),
1275
+ ...(m.systemCard ? { systemCard: true } : {}),
1276
+ ...(m.providerError ? { providerError: m.providerError } : {}),
1277
+ ...(m.slackMessage ? { slackMessage: m.slackMessage } : {}),
1278
+ };
1279
+ }),
1280
+ );
1239
1281
 
1240
1282
  // Snapshot↔stream alignment token: the `seq` of the last event whose
1241
1283
  // content is durably persisted for this conversation, read from the
@@ -1579,7 +1621,7 @@ export async function handleSendMessage(
1579
1621
  typeof body.clientTimezone === "string"
1580
1622
  ? (canonicalizeTimeZone(body.clientTimezone) ?? undefined)
1581
1623
  : undefined;
1582
- // Client OS surface ("web" | "ios" | "macos" | "android"), reported
1624
+ // Client OS surface ("web" | "ios" | "macos" | "windows" | "android"), reported
1583
1625
  // separately from the transport `interface`. Validated against the dedicated
1584
1626
  // `ClientOs` value set (NOT the interface vocabulary) and only kept when it
1585
1627
  // resolves — it drives the per-turn `client_os:` context line, never
@@ -1704,6 +1746,11 @@ export async function handleSendMessage(
1704
1746
  mapping = getOrCreateConversation(resolvedConversationKey, {
1705
1747
  conversationType: "standard",
1706
1748
  title: onboardingTitle,
1749
+ // This route already resolved the channel the message arrived on, and
1750
+ // this is the seam that materializes the row, so the conversation is
1751
+ // attributed from the moment it exists rather than on its first
1752
+ // message.
1753
+ origin: sourceChannel,
1707
1754
  });
1708
1755
  }
1709
1756
 
@@ -1868,6 +1915,15 @@ export async function handleSendMessage(
1868
1915
  conversation.setTrustContext({ trustClass: "guardian", sourceChannel });
1869
1916
  }
1870
1917
 
1918
+ // The trust this request's turn runs under, captured here rather than read
1919
+ // back at the agent loop below. Every branch above has just written it and
1920
+ // nothing awaits in between, so this is the resolved sender. Between here
1921
+ // and the loop the conversation slot is writable by paths that do not own
1922
+ // this turn (channel ingress for another actor, live-voice hydration,
1923
+ // pointer elevation, the voice bridge), and the loop would otherwise
1924
+ // re-read it and run this turn as whoever wrote last.
1925
+ const turnTrustContext = conversation.trustContext;
1926
+
1871
1927
  const isInteractive = isInteractiveInterface(sourceInterface);
1872
1928
  // Translate the dev-bypass actor principal to the real guardian principal
1873
1929
  // before the same-actor host-proxy gate so web/iOS turns match the macOS
@@ -1999,6 +2055,7 @@ export async function handleSendMessage(
1999
2055
  requestId: uuidv7(),
2000
2056
  metadata: greetingMeta,
2001
2057
  clientMessageId,
2058
+ ...(clientOs ? { requestClientOs: clientOs } : {}),
2002
2059
  });
2003
2060
 
2004
2061
  const conversationId = mapping.conversationId;
@@ -2037,34 +2094,37 @@ export async function handleSendMessage(
2037
2094
  }
2038
2095
  }
2039
2096
 
2040
- setTimeout(() => {
2041
- broadcastMessage({
2042
- type: "user_message_echo",
2043
- text: rawContent,
2044
- conversationId,
2045
- messageId: persisted.id,
2046
- clientMessageId,
2047
- });
2048
- broadcastMessage({
2049
- type: "assistant_text_delta",
2050
- text: cannedGreeting,
2051
- conversationId,
2052
- });
2053
- emitCannedMessageComplete(
2054
- broadcastMessage,
2055
- conversationId,
2056
- persistedAssistant.id,
2057
- );
2058
- // Rows persisted before this deferred burst; advance the
2059
- // snapshot↔stream anchor past the events just emitted so `/messages`
2060
- // never returns these rows behind a stale anchor.
2061
- recordConversationPersistedSeq(conversationId, getCurrentSeq());
2062
- publishConversationMessagesChanged(conversationId, originClientId);
2063
- conversation.setProcessing(false);
2064
- void conversation.kickDrainQueue("loop_complete", "canned_greeting");
2065
-
2066
- conversation.warmPromptCache();
2067
- }, 0);
2097
+ scheduleCannedReplyRelease({
2098
+ conversation,
2099
+ origin: "canned_greeting",
2100
+ emit: () => {
2101
+ broadcastMessage({
2102
+ type: "user_message_echo",
2103
+ text: rawContent,
2104
+ conversationId,
2105
+ messageId: persisted.id,
2106
+ clientMessageId,
2107
+ });
2108
+ broadcastMessage({
2109
+ type: "assistant_text_delta",
2110
+ text: cannedGreeting,
2111
+ conversationId,
2112
+ });
2113
+ emitCannedMessageComplete(
2114
+ broadcastMessage,
2115
+ conversationId,
2116
+ persistedAssistant.id,
2117
+ );
2118
+ // Rows persisted before this deferred burst; advance the
2119
+ // snapshot↔stream anchor past the events just emitted so `/messages`
2120
+ // never returns these rows behind a stale anchor.
2121
+ recordConversationPersistedSeq(conversationId, getCurrentSeq());
2122
+ publishConversationMessagesChanged(conversationId, originClientId);
2123
+ },
2124
+ afterRelease: () => {
2125
+ conversation.warmPromptCache();
2126
+ },
2127
+ });
2068
2128
 
2069
2129
  log.info(
2070
2130
  { conversationId, personalized: !!body.onboarding },
@@ -2329,6 +2389,7 @@ export async function handleSendMessage(
2329
2389
  requestId: uuidv7(),
2330
2390
  metadata: withClientMetadata(slashMeta, clientMetadata),
2331
2391
  clientMessageId,
2392
+ ...(clientOs ? { requestClientOs: clientOs } : {}),
2332
2393
  });
2333
2394
  if (persisted.deduplicated) {
2334
2395
  return {
@@ -2372,33 +2433,35 @@ export async function handleSendMessage(
2372
2433
  // starts processing.
2373
2434
  const conversationId = mapping.conversationId;
2374
2435
  const message = slashResult.message;
2375
- setTimeout(() => {
2376
- broadcastMessage({
2377
- type: "user_message_echo",
2378
- text: rawContent,
2379
- conversationId,
2380
- messageId: persisted.id,
2381
- clientMessageId,
2382
- });
2383
- if (modelInfoEvent) {
2384
- broadcastMessage(modelInfoEvent);
2385
- }
2386
- broadcastMessage({
2387
- type: "assistant_text_delta",
2388
- text: message,
2389
- conversationId,
2390
- });
2391
- emitCannedMessageComplete(
2392
- broadcastMessage,
2393
- conversationId,
2394
- persistedAssistant.id,
2395
- );
2396
- // Same anchor advance as the canned-greeting path above.
2397
- recordConversationPersistedSeq(conversationId, getCurrentSeq());
2398
- publishConversationMessagesChanged(conversationId, originClientId);
2399
- conversation.setProcessing(false);
2400
- void conversation.kickDrainQueue("loop_complete", "slash_command");
2401
- }, 0);
2436
+ scheduleCannedReplyRelease({
2437
+ conversation,
2438
+ origin: "slash_command",
2439
+ emit: () => {
2440
+ broadcastMessage({
2441
+ type: "user_message_echo",
2442
+ text: rawContent,
2443
+ conversationId,
2444
+ messageId: persisted.id,
2445
+ clientMessageId,
2446
+ });
2447
+ if (modelInfoEvent) {
2448
+ broadcastMessage(modelInfoEvent);
2449
+ }
2450
+ broadcastMessage({
2451
+ type: "assistant_text_delta",
2452
+ text: message,
2453
+ conversationId,
2454
+ });
2455
+ emitCannedMessageComplete(
2456
+ broadcastMessage,
2457
+ conversationId,
2458
+ persistedAssistant.id,
2459
+ );
2460
+ // Same anchor advance as the canned-greeting path above.
2461
+ recordConversationPersistedSeq(conversationId, getCurrentSeq());
2462
+ publishConversationMessagesChanged(conversationId, originClientId);
2463
+ },
2464
+ });
2402
2465
 
2403
2466
  cleanupDeferred = true;
2404
2467
  return response;
@@ -2428,6 +2491,7 @@ export async function handleSendMessage(
2428
2491
  requestId: uuidv7(),
2429
2492
  metadata: withClientMetadata(slashMeta, clientMetadata),
2430
2493
  clientMessageId,
2494
+ ...(clientOs ? { requestClientOs: clientOs } : {}),
2431
2495
  });
2432
2496
  } catch (err) {
2433
2497
  // The fire-and-forget compaction below owns clearing `processing`, but a
@@ -2522,6 +2586,7 @@ export async function handleSendMessage(
2522
2586
  requestId: uuidv7(),
2523
2587
  metadata: withClientMetadata(slashMeta, clientMetadata),
2524
2588
  clientMessageId,
2589
+ ...(clientOs ? { requestClientOs: clientOs } : {}),
2525
2590
  });
2526
2591
  if (persisted.deduplicated) {
2527
2592
  return {
@@ -2591,6 +2656,7 @@ export async function handleSendMessage(
2591
2656
  ),
2592
2657
  scripted: body.scripted,
2593
2658
  clientMessageId,
2659
+ ...(clientOs ? { requestClientOs: clientOs } : {}),
2594
2660
  });
2595
2661
 
2596
2662
  const messageId = persistResult.id;
@@ -2634,6 +2700,7 @@ export async function handleSendMessage(
2634
2700
  onEvent: broadcastMessage,
2635
2701
  isInteractive,
2636
2702
  isUserMessage: true,
2703
+ turnTrustContext,
2637
2704
  ...(body.hidden === true ? { isHiddenPrompt: true } : {}),
2638
2705
  })
2639
2706
  .catch((err) => {
@@ -3095,7 +3162,7 @@ export const ROUTES: RouteDefinition[] = [
3095
3162
  .string()
3096
3163
  .optional()
3097
3164
  .describe(
3098
- 'Client OS surface ("web" | "ios" | "macos" | "android"), reported separately from `interface`. Drives the per-turn `client_os` context only; does not affect transport/host-proxy capabilities.',
3165
+ 'Client OS surface ("web" | "ios" | "macos" | "windows" | "android"), reported separately from `interface`. Drives the per-turn `client_os` context only; does not affect transport/host-proxy capabilities.',
3099
3166
  ),
3100
3167
  visibleAppId: z
3101
3168
  .string()