@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
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Tests for the `assistant` command installer.
3
+ *
4
+ * The two layouts that matter are the compiled desktop bundle (a binary beside
5
+ * the daemon) and the npm-installed runtime the desktop app actually ships (a
6
+ * bun entry under `node_modules`, which needs a wrapper that pins bun). A repo
7
+ * checkout must install nothing: developers own their PATH.
8
+ */
9
+
10
+ import {
11
+ chmodSync,
12
+ existsSync,
13
+ lstatSync,
14
+ mkdirSync,
15
+ mkdtempSync,
16
+ readFileSync,
17
+ rmSync,
18
+ symlinkSync,
19
+ writeFileSync,
20
+ } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
24
+
25
+ import {
26
+ installCommandAt,
27
+ resolveAssistantCommandTarget,
28
+ } from "../install-assistant-command.js";
29
+
30
+ let root: string;
31
+
32
+ beforeEach(() => {
33
+ root = mkdtempSync(join(tmpdir(), "assistant-command-"));
34
+ });
35
+
36
+ afterEach(() => {
37
+ rmSync(root, { recursive: true, force: true });
38
+ });
39
+
40
+ /** Build `<root>/<prefix>/node_modules/@vellumai/assistant/src/daemon`. */
41
+ function seedInstalledLayout(options: { withBin: boolean }): string {
42
+ const nodeModules = join(root, "cli", "latest", "node_modules");
43
+ const moduleDir = join(
44
+ nodeModules,
45
+ "@vellumai",
46
+ "assistant",
47
+ "src",
48
+ "daemon",
49
+ );
50
+ mkdirSync(moduleDir, { recursive: true });
51
+ writeFileSync(join(moduleDir, "..", "index.ts"), "#!/usr/bin/env bun\n");
52
+
53
+ if (options.withBin) {
54
+ mkdirSync(join(nodeModules, ".bin"), { recursive: true });
55
+ writeFileSync(
56
+ join(nodeModules, ".bin", "assistant"),
57
+ "#!/usr/bin/env bun\n",
58
+ );
59
+ }
60
+ return moduleDir;
61
+ }
62
+
63
+ describe("resolveAssistantCommandTarget", () => {
64
+ test("npm install resolves the .bin/assistant shim under a pinned bun", () => {
65
+ const moduleDir = seedInstalledLayout({ withBin: true });
66
+
67
+ const target = resolveAssistantCommandTarget({
68
+ execPath: "/Applications/Vellum.app/Contents/Resources/bun",
69
+ moduleDir,
70
+ });
71
+
72
+ expect(target).toEqual({
73
+ kind: "bun-entry",
74
+ bun: "/Applications/Vellum.app/Contents/Resources/bun",
75
+ entry: join(root, "cli", "latest", "node_modules", ".bin", "assistant"),
76
+ });
77
+ });
78
+
79
+ test("npm install without the meta package falls back to the CLI entry", () => {
80
+ const moduleDir = seedInstalledLayout({ withBin: false });
81
+
82
+ const target = resolveAssistantCommandTarget({
83
+ execPath: "/usr/local/bin/bun",
84
+ moduleDir,
85
+ });
86
+
87
+ expect(target).toEqual({
88
+ kind: "bun-entry",
89
+ bun: "/usr/local/bin/bun",
90
+ entry: join(
91
+ root,
92
+ "cli",
93
+ "latest",
94
+ "node_modules",
95
+ "@vellumai",
96
+ "assistant",
97
+ "src",
98
+ "index.ts",
99
+ ),
100
+ });
101
+ });
102
+
103
+ test("repo checkout installs nothing", () => {
104
+ const moduleDir = join(root, "repo", "assistant", "src", "daemon");
105
+ mkdirSync(moduleDir, { recursive: true });
106
+ writeFileSync(join(moduleDir, "..", "index.ts"), "#!/usr/bin/env bun\n");
107
+
108
+ expect(
109
+ resolveAssistantCommandTarget({
110
+ execPath: "/usr/local/bin/bun",
111
+ moduleDir,
112
+ }),
113
+ ).toBeNull();
114
+ });
115
+
116
+ test("compiled bundle resolves the sibling binary", () => {
117
+ const macosDir = join(root, "Vellum.app", "Contents", "MacOS");
118
+ mkdirSync(macosDir, { recursive: true });
119
+ writeFileSync(join(macosDir, "vellum-assistant"), "");
120
+
121
+ const target = resolveAssistantCommandTarget({
122
+ execPath: join(macosDir, "vellum-daemon"),
123
+ moduleDir: join(root, "irrelevant"),
124
+ });
125
+
126
+ expect(target).toEqual({
127
+ kind: "binary",
128
+ binary: join(macosDir, "vellum-assistant"),
129
+ });
130
+ });
131
+
132
+ test("a compiled runtime outside a bundle installs nothing", () => {
133
+ const moduleDir = seedInstalledLayout({ withBin: true });
134
+
135
+ expect(
136
+ resolveAssistantCommandTarget({
137
+ execPath: "/opt/vellum/vellum-daemon",
138
+ moduleDir,
139
+ }),
140
+ ).toBeNull();
141
+ });
142
+ });
143
+
144
+ describe("installCommandAt", () => {
145
+ const target = {
146
+ kind: "bun-entry" as const,
147
+ bun: "/Applications/Vellum Staging.app/Contents/Resources/bun",
148
+ entry:
149
+ "/Users/x/Library/Application Support/@vellumai/macos/.bin/assistant",
150
+ };
151
+
152
+ test("writes an executable wrapper that quotes paths with spaces", () => {
153
+ const commandPath = join(root, "bin", "assistant");
154
+
155
+ expect(installCommandAt(commandPath, target)).toBe(true);
156
+
157
+ const content = readFileSync(commandPath, "utf-8");
158
+ expect(content.startsWith("#!/bin/sh\n")).toBe(true);
159
+ expect(content).toContain(
160
+ `exec '/Applications/Vellum Staging.app/Contents/Resources/bun' ` +
161
+ `'/Users/x/Library/Application Support/@vellumai/macos/.bin/assistant' "$@"`,
162
+ );
163
+ expect(lstatSync(commandPath).mode & 0o111).toBeGreaterThan(0);
164
+ });
165
+
166
+ test("running the wrapper forwards arguments to the pinned interpreter", () => {
167
+ const interpreter = join(root, "fake bun");
168
+ writeFileSync(interpreter, '#!/bin/sh\necho "$@"\n');
169
+ chmodSync(interpreter, 0o755);
170
+
171
+ const commandPath = join(root, "assistant");
172
+ installCommandAt(commandPath, {
173
+ kind: "bun-entry",
174
+ bun: interpreter,
175
+ entry: join(root, "entry with space.ts"),
176
+ });
177
+
178
+ const proc = Bun.spawnSync({ cmd: [commandPath, "browser", "status"] });
179
+ expect(proc.exitCode).toBe(0);
180
+ expect(proc.stdout.toString().trim()).toBe(
181
+ `${join(root, "entry with space.ts")} browser status`,
182
+ );
183
+ });
184
+
185
+ test("is idempotent and rewrites a stale wrapper", () => {
186
+ const commandPath = join(root, "assistant");
187
+
188
+ expect(installCommandAt(commandPath, target)).toBe(true);
189
+ const first = readFileSync(commandPath, "utf-8");
190
+ expect(installCommandAt(commandPath, target)).toBe(true);
191
+ expect(readFileSync(commandPath, "utf-8")).toBe(first);
192
+
193
+ const moved = { ...target, bun: "/opt/homebrew/bin/bun" };
194
+ expect(installCommandAt(commandPath, moved)).toBe(true);
195
+ expect(readFileSync(commandPath, "utf-8")).toContain(
196
+ "/opt/homebrew/bin/bun",
197
+ );
198
+ });
199
+
200
+ test("replaces a stale symlink", () => {
201
+ const commandPath = join(root, "assistant");
202
+ symlinkSync("/nonexistent/old-target", commandPath);
203
+
204
+ expect(installCommandAt(commandPath, target)).toBe(true);
205
+ expect(lstatSync(commandPath).isFile()).toBe(true);
206
+ });
207
+
208
+ test("never clobbers a file it does not own", () => {
209
+ const commandPath = join(root, "assistant");
210
+ writeFileSync(commandPath, "#!/bin/sh\n# a developer's own build\n");
211
+
212
+ expect(installCommandAt(commandPath, target)).toBe(false);
213
+ expect(readFileSync(commandPath, "utf-8")).toContain("developer's own");
214
+ });
215
+
216
+ test("reports failure instead of throwing on an unusable path", () => {
217
+ const commandPath = join(root, "assistant");
218
+ mkdirSync(commandPath);
219
+
220
+ expect(installCommandAt(commandPath, target)).toBe(false);
221
+ expect(existsSync(commandPath)).toBe(true);
222
+ });
223
+
224
+ test("symlinks a compiled binary target", () => {
225
+ const binary = join(root, "vellum-assistant");
226
+ writeFileSync(binary, "");
227
+ const commandPath = join(root, "bin", "assistant");
228
+
229
+ expect(installCommandAt(commandPath, { kind: "binary", binary })).toBe(
230
+ true,
231
+ );
232
+ expect(lstatSync(commandPath).isSymbolicLink()).toBe(true);
233
+ });
234
+ });
@@ -7,7 +7,6 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
7
7
 
8
8
  import { makeMockLogger } from "../../__tests__/helpers/mock-logger.js";
9
9
  import { setConfig } from "../../__tests__/helpers/set-config.js";
10
- import type { InflightContentWriter } from "../inflight-message-content.js";
11
10
 
12
11
  // The finalize module's import graph reaches the memory indexer; keep it inert
13
12
  // so no embedding backend is touched.
@@ -78,17 +77,16 @@ async function runTail(overrides: {
78
77
  replyDeliveredInAppOnly?: boolean;
79
78
  }): Promise<void> {
80
79
  await runDeferredTurnTail({
81
- ctx: { conversationId: CONVERSATION_ID, messages: [] },
80
+ conversationId: CONVERSATION_ID,
82
81
  state: {
83
82
  deferredFinalizeEffects: overrides.deferredFinalizeEffects ?? [],
84
83
  lastAssistantMessageId:
85
84
  "lastAssistantMessageId" in overrides
86
85
  ? overrides.lastAssistantMessageId
87
86
  : ASSISTANT_MESSAGE_ID,
88
- inflightWriters: new Map<string, InflightContentWriter>(),
89
87
  },
90
88
  rlog,
91
- generationCompletedAt: Date.now(),
89
+ criticalSectionMs: 0,
92
90
  turnCompleted: overrides.turnCompleted,
93
91
  userMessageId:
94
92
  "userMessageId" in overrides ? overrides.userMessageId : USER_MESSAGE_ID,
@@ -1,9 +1,9 @@
1
1
  /**
2
- * `runDeferredTurnTail` runs after the terminal SSE, so the user can delete the
3
- * conversation while its awaited bookkeeping is still draining. These tests pin
4
- * that the tail's two filesystem steps (tool-result spooling and the JSONL disk
5
- * mirror) read the conversation fresh and skip once it is gone, rather than
6
- * rebuilding a deleted conversation's directory from in-memory history.
2
+ * `settleTurnContent` runs after the terminal SSE, so the user can delete the
3
+ * conversation before its content-settling steps run. These tests pin that the
4
+ * two filesystem steps (tool-result spooling and the JSONL disk mirror) read the
5
+ * conversation fresh and skip once it is gone, rather than rebuilding a deleted
6
+ * conversation's directory from in-memory history.
7
7
  */
8
8
  import { beforeEach, describe, expect, mock, test } from "bun:test";
9
9
 
@@ -19,10 +19,9 @@ setConfig("memory", { enabled: false, v2: { enabled: false } });
19
19
 
20
20
  const CONVERSATION_ID = "conv-tail-delete-1";
21
21
  const ASSISTANT_MESSAGE_ID = "msg-assistant-1";
22
- const USER_MESSAGE_ID = "msg-user-1";
23
22
  const CONVERSATION_DIR = "/tmp/vellum-test/conv-tail-delete-1";
24
23
 
25
- /** Flipped by a deferred finalize effect to model a mid-tail deletion. */
24
+ /** Flipped by a test to model a deletion that landed before the settle ran. */
26
25
  let conversationDeleted = false;
27
26
  let getConversationCalls = 0;
28
27
  const resolvedDirCalls: string[] = [];
@@ -56,6 +55,7 @@ function makeConversation(): ConversationRow {
56
55
  originInterface: null,
57
56
  forkParentConversationId: null,
58
57
  forkParentMessageId: null,
58
+ forkStrategy: null,
59
59
  isAutoTitle: 0,
60
60
  scheduleJobId: null,
61
61
  lastMessageAt: null,
@@ -115,29 +115,22 @@ mock.module("../../persistence/conversation-disk-view.js", () => ({
115
115
  },
116
116
  }));
117
117
 
118
- const { runDeferredTurnTail } =
119
- await import("../conversation-turn-finalize.js");
118
+ const { settleTurnContent } = await import("../conversation-turn-finalize.js");
120
119
 
121
120
  const rlog = makeMockLogger() as Parameters<
122
- typeof runDeferredTurnTail
121
+ typeof settleTurnContent
123
122
  >[0]["rlog"];
124
123
 
125
- /** Runs the tail with one deferred effect standing in for the turn's bookkeeping. */
126
- async function runTail(
127
- deferredEffect: () => Promise<void>,
128
- ): Promise<Message[]> {
124
+ /** Settles one turn's content and hands back the history it left behind. */
125
+ async function runSettle(): Promise<Message[]> {
129
126
  const ctx = { conversationId: CONVERSATION_ID, messages: [] as Message[] };
130
- await runDeferredTurnTail({
127
+ await settleTurnContent({
131
128
  ctx,
132
129
  state: {
133
- deferredFinalizeEffects: [deferredEffect],
134
130
  lastAssistantMessageId: ASSISTANT_MESSAGE_ID,
135
131
  inflightWriters: new Map<string, InflightContentWriter>(),
136
132
  },
137
133
  rlog,
138
- generationCompletedAt: Date.now(),
139
- turnCompleted: true,
140
- userMessageId: USER_MESSAGE_ID,
141
134
  });
142
135
  return ctx.messages;
143
136
  }
@@ -150,11 +143,11 @@ beforeEach(() => {
150
143
  diskSyncCalls.length = 0;
151
144
  });
152
145
 
153
- describe("runDeferredTurnTail conversation deleted mid-tail", () => {
146
+ describe("settleTurnContent conversation deleted after the terminal SSE", () => {
154
147
  test("skips the filesystem steps when the conversation is gone", async () => {
155
- const messages = await runTail(async () => {
156
- conversationDeleted = true;
157
- });
148
+ conversationDeleted = true;
149
+
150
+ const messages = await runSettle();
158
151
 
159
152
  expect(resolvedDirCalls).toEqual([]);
160
153
  expect(truncateCalls).toEqual([]);
@@ -162,16 +155,14 @@ describe("runDeferredTurnTail conversation deleted mid-tail", () => {
162
155
  expect(messages).toEqual([]);
163
156
  });
164
157
 
165
- test("reads the conversation once more before the filesystem steps", async () => {
166
- await runTail(async () => {
167
- conversationDeleted = true;
168
- });
158
+ test("reads the conversation itself rather than trusting an earlier read", async () => {
159
+ await runSettle();
169
160
 
170
- expect(getConversationCalls).toBe(2);
161
+ expect(getConversationCalls).toBe(1);
171
162
  });
172
163
 
173
164
  test("runs the filesystem steps when the conversation survives", async () => {
174
- const messages = await runTail(async () => {});
165
+ const messages = await runSettle();
175
166
 
176
167
  expect(resolvedDirCalls).toEqual([CONVERSATION_ID]);
177
168
  expect(truncateCalls).toEqual([CONVERSATION_DIR]);
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Minimal capability view {@link conversationSupportsDynamicUi} reads. The live
3
- * `Conversation` (whose fields are the richer `ChannelCapabilities`), the tool
4
- * executor's `ToolSetupContext`, and partial test doubles all satisfy it
5
- * structurally, so the helper stays a dependency-free leaf callable from any
6
- * layer without importing `Conversation`.
3
+ * `Conversation` (whose fields are the richer `ChannelCapabilities`) and
4
+ * partial test doubles both satisfy it structurally, so the helper stays a
5
+ * dependency-free leaf callable from any layer without importing
6
+ * `Conversation`.
7
7
  */
8
8
  export interface DynamicUiCapabilityView {
9
9
  readonly currentTurnChannelCapabilities?: {
@@ -11,6 +11,7 @@ import type pino from "pino";
11
11
  import { v4 as uuid } from "uuid";
12
12
 
13
13
  import type { AgentEvent } from "../agent/loop.js";
14
+ import type { AnsweredQuestion } from "../api/events/question-answered.js";
14
15
  import type { AssistantEvent } from "../api/index.js";
15
16
  import type {
16
17
  TurnChannelContext,
@@ -334,6 +335,12 @@ export interface EventHandlerState {
334
335
  * tools (in handleToolResult) and native server tools (server_tool_complete).
335
336
  */
336
337
  readonly toolActivityMetadata: Map<string, ToolActivityMetadata>;
338
+ /**
339
+ * Answered `ask_question` records keyed by tool_use_id, captured when the
340
+ * result lands so the questions and the user's answers are persisted on the
341
+ * tool's content block and the answered card survives a history reopen.
342
+ */
343
+ readonly toolAnsweredQuestions: Map<string, AnsweredQuestion>;
337
344
  /** tool_use_ids emitted in the current turn (populated in handleToolUse, cleared after annotation). */
338
345
  currentTurnToolUseIds: string[];
339
346
  /** Wall-clock time (ms since epoch) when the agent loop turn started, used as the display timestamp for assistant messages. */
@@ -602,6 +609,7 @@ export function createEventHandlerState(): EventHandlerState {
602
609
  toolConfirmationOutcomes: new Map(),
603
610
  toolRiskOutcomes: new Map(),
604
611
  toolActivityMetadata: new Map(),
612
+ toolAnsweredQuestions: new Map(),
605
613
  currentTurnToolUseIds: [],
606
614
  turnStartedAt: Date.now(),
607
615
  serverToolStartedAt: new Map(),
@@ -837,6 +845,12 @@ export function buildPersistedAssistantContent(
837
845
  display: surface.display,
838
846
  ...(surface.persistent ? { persistent: true } : {}),
839
847
  ...(surface.toolCallId ? { toolCallId: surface.toolCallId } : {}),
848
+ // A surface answered before this write lands owns no persisted block for
849
+ // `markSurfaceCompleted` to patch, so its completion rides the snapshot.
850
+ ...(surface.completed ? { completed: true } : {}),
851
+ ...(surface.completionSummary
852
+ ? { completionSummary: surface.completionSummary }
853
+ : {}),
840
854
  } as unknown as ContentBlock);
841
855
  }
842
856
  return withSurfaces.map((block) => {
@@ -2005,7 +2019,7 @@ export async function finalizePendingToolResultRow(
2005
2019
  );
2006
2020
  const contentJson = JSON.stringify(
2007
2021
  conv != null
2008
- ? referenceMediaBlocksForPersist(
2022
+ ? await referenceMediaBlocksForPersist(
2009
2023
  conversationId,
2010
2024
  conv.createdAt,
2011
2025
  rowId,
@@ -2277,6 +2291,20 @@ export async function handleToolResult(
2277
2291
  state.toolActivityMetadata.set(event.toolUseId, event.activityMetadata);
2278
2292
  }
2279
2293
 
2294
+ // Capture the answered `ask_question` record and stamp it on the durable
2295
+ // tool_use block right away. The end-of-turn annotation re-stamps it, but
2296
+ // that only runs once every tool in the turn has finished: a user who
2297
+ // switches conversations while a sibling tool is still running would
2298
+ // otherwise reload into history that has lost their answer.
2299
+ if (event.answeredQuestion) {
2300
+ state.toolAnsweredQuestions.set(event.toolUseId, event.answeredQuestion);
2301
+ recordAnsweredQuestionOnPersistedMessage(
2302
+ state,
2303
+ event.toolUseId,
2304
+ event.answeredQuestion,
2305
+ );
2306
+ }
2307
+
2280
2308
  const toolName = state.toolUseIdToName.get(event.toolUseId);
2281
2309
  if (toolName === "file_write" || toolName === "bash") {
2282
2310
  deps.ctx.markWorkspaceTopLevelDirty();
@@ -2360,6 +2388,7 @@ export async function handleToolResult(
2360
2388
  approvalReason: event.approvalReason,
2361
2389
  riskThreshold: event.riskThreshold,
2362
2390
  activityMetadata: event.activityMetadata,
2391
+ answeredQuestion: event.answeredQuestion,
2363
2392
  errorCode: event.errorCode,
2364
2393
  completedAt,
2365
2394
  });
@@ -2380,18 +2409,23 @@ export async function handleToolResult(
2380
2409
  }
2381
2410
 
2382
2411
  /**
2383
- * Stamp `_startedAt` onto the in-flight tool_use block in the persisted
2384
- * assistant message the moment the tool begins. The block is already durable
2385
- * (message_complete precedes tool events), so without this a `/messages`
2386
- * snapshot fetched mid-tool would carry no start time and clients could not
2387
- * render a running elapsed-time counter until the whole turn finished. The
2388
- * full timing + risk annotation still happens in
2389
- * `annotatePersistedAssistantMessage` once every tool in the turn completes.
2412
+ * Stamp vellum-internal metadata onto an in-flight tool_use block in the
2413
+ * persisted assistant message, ahead of the turn's end-of-turn annotation. The
2414
+ * block is already durable (message_complete precedes tool events), so without
2415
+ * an early stamp a `/messages` snapshot fetched mid-turn carries none of it and
2416
+ * clients render a degraded row until every tool in the turn has finished.
2417
+ *
2418
+ * `apply` mutates the block record and returns false when the value is already
2419
+ * stamped, which skips the write. The write itself is best-effort:
2420
+ * `annotatePersistedAssistantMessage` re-stamps from the same state maps once
2421
+ * the turn's tools complete, so a transient `SQLITE_BUSY` here must not abort
2422
+ * the turn. `what` names the field for the failure log.
2390
2423
  */
2391
- function recordToolStartOnPersistedMessage(
2424
+ function stampToolUseBlockEarly(
2392
2425
  state: EventHandlerState,
2393
2426
  toolUseId: string,
2394
- startedAt: number,
2427
+ what: string,
2428
+ apply: (block: Record<string, unknown>) => boolean,
2395
2429
  ): void {
2396
2430
  const messageId = state.lastAssistantMessageId;
2397
2431
  if (!messageId) {
@@ -2413,19 +2447,15 @@ function recordToolStartOnPersistedMessage(
2413
2447
  if (rec.id !== toolUseId) {
2414
2448
  continue;
2415
2449
  }
2416
- if (rec._startedAt === startedAt) {
2450
+ if (!apply(rec)) {
2417
2451
  return;
2418
2452
  }
2419
- rec._startedAt = startedAt;
2420
- // Best-effort early stamp: `annotatePersistedAssistantMessage` re-stamps
2421
- // once every tool in the turn completes, so a transient `SQLITE_BUSY` here
2422
- // must not abort the turn — the end-of-turn write recovers it.
2423
2453
  try {
2424
2454
  updateMessageContent(messageId, JSON.stringify(content));
2425
2455
  } catch (err) {
2426
2456
  log.error(
2427
2457
  { err, messageId },
2428
- "stamping tool start time failed; end-of-turn annotation will recover",
2458
+ `stamping ${what} failed; end-of-turn annotation will recover`,
2429
2459
  );
2430
2460
  }
2431
2461
  return;
@@ -2433,57 +2463,73 @@ function recordToolStartOnPersistedMessage(
2433
2463
  }
2434
2464
 
2435
2465
  /**
2436
- * Stamp `_previewStartedAt` (the first-byte timestamp) onto the durable
2437
- * tool_use block, mirroring `recordToolStartOnPersistedMessage`. Called from
2438
- * `handleToolUse` rather than `handleToolUsePreviewStart`: the block only exists
2439
- * once message_complete has written it, which happens after the preview event
2440
- * but before the tool event. Without this a `/messages` snapshot fetched
2441
- * mid-tool would lose the perceived-start anchor and clients would fall back to
2442
- * execution start — hiding the input-streaming gap the user actually waited
2443
- * through.
2466
+ * Stamp `_startedAt` the moment the tool begins, so a mid-tool `/messages`
2467
+ * snapshot can render a running elapsed-time counter.
2468
+ */
2469
+ function recordToolStartOnPersistedMessage(
2470
+ state: EventHandlerState,
2471
+ toolUseId: string,
2472
+ startedAt: number,
2473
+ ): void {
2474
+ stampToolUseBlockEarly(state, toolUseId, "tool start time", (rec) => {
2475
+ if (rec._startedAt === startedAt) {
2476
+ return false;
2477
+ }
2478
+ rec._startedAt = startedAt;
2479
+ return true;
2480
+ });
2481
+ }
2482
+
2483
+ /**
2484
+ * Stamp `_previewStartedAt` (the first-byte timestamp). Called from
2485
+ * `handleToolUse` rather than `handleToolUsePreviewStart`: the block only
2486
+ * exists once message_complete has written it, which happens after the preview
2487
+ * event but before the tool event. Without it a mid-tool `/messages` snapshot
2488
+ * loses the perceived-start anchor and clients fall back to execution start,
2489
+ * hiding the input-streaming gap the user actually waited through.
2444
2490
  */
2445
2491
  function recordToolPreviewStartOnPersistedMessage(
2446
2492
  state: EventHandlerState,
2447
2493
  toolUseId: string,
2448
2494
  previewStartedAt: number,
2449
2495
  ): void {
2450
- const messageId = state.lastAssistantMessageId;
2451
- if (!messageId) {
2452
- return;
2453
- }
2454
-
2455
- const row = getMessageById(messageId);
2456
- if (!row) {
2457
- return;
2458
- }
2459
-
2460
- const content: ContentBlock[] = row.content;
2461
-
2462
- for (const block of content) {
2463
- if (block.type !== "tool_use") {
2464
- continue;
2465
- }
2466
- const rec = block as unknown as Record<string, unknown>;
2467
- if (rec.id !== toolUseId) {
2468
- continue;
2469
- }
2496
+ stampToolUseBlockEarly(state, toolUseId, "tool preview-start time", (rec) => {
2470
2497
  if (rec._previewStartedAt === previewStartedAt) {
2471
- return;
2498
+ return false;
2472
2499
  }
2473
2500
  rec._previewStartedAt = previewStartedAt;
2474
- // Best-effort early stamp, mirroring `recordToolStartOnPersistedMessage`:
2475
- // `annotatePersistedAssistantMessage` re-stamps at end of turn, so a
2476
- // transient `SQLITE_BUSY` here must not abort the turn.
2477
- try {
2478
- updateMessageContent(messageId, JSON.stringify(content));
2479
- } catch (err) {
2480
- log.error(
2481
- { err, messageId },
2482
- "stamping tool preview-start time failed; end-of-turn annotation will recover",
2483
- );
2501
+ return true;
2502
+ });
2503
+ }
2504
+
2505
+ /**
2506
+ * Stamp `_answeredQuestion` the moment an `ask_question` prompt settles, so a
2507
+ * user who leaves the conversation while a sibling tool is still running comes
2508
+ * back to their answer rather than to a question that lost its response.
2509
+ */
2510
+ function recordAnsweredQuestionOnPersistedMessage(
2511
+ state: EventHandlerState,
2512
+ toolUseId: string,
2513
+ answeredQuestion: AnsweredQuestion,
2514
+ ): void {
2515
+ stampToolUseBlockEarly(state, toolUseId, "answered question", (rec) => {
2516
+ // Narrow the persisted marker rather than casting it: the row is data read
2517
+ // back from the database, so its shape is an assumption until checked. Any
2518
+ // value that is not a record with a string `requestId` is treated as absent
2519
+ // and overwritten, which is the safe direction for a dedup check.
2520
+ const existing: unknown = rec._answeredQuestion;
2521
+ const existingRequestId =
2522
+ typeof existing === "object" &&
2523
+ existing !== null &&
2524
+ typeof (existing as { requestId?: unknown }).requestId === "string"
2525
+ ? (existing as { requestId: string }).requestId
2526
+ : undefined;
2527
+ if (existingRequestId === answeredQuestion.requestId) {
2528
+ return false;
2484
2529
  }
2485
- return;
2486
- }
2530
+ rec._answeredQuestion = answeredQuestion;
2531
+ return true;
2532
+ });
2487
2533
  }
2488
2534
 
2489
2535
  /**
@@ -2583,6 +2629,11 @@ function annotatePersistedAssistantMessage(
2583
2629
  rec._activityMetadata = activity;
2584
2630
  modified = true;
2585
2631
  }
2632
+ const answeredQuestion = state.toolAnsweredQuestions.get(id);
2633
+ if (answeredQuestion) {
2634
+ rec._answeredQuestion = answeredQuestion;
2635
+ modified = true;
2636
+ }
2586
2637
  }
2587
2638
  }
2588
2639
 
@@ -2608,6 +2659,12 @@ function annotatePersistedAssistantMessage(
2608
2659
  ...(surface.activationMoment
2609
2660
  ? { activationMoment: surface.activationMoment }
2610
2661
  : {}),
2662
+ // A surface answered before this write lands owns no persisted block for
2663
+ // `markSurfaceCompleted` to patch, so its completion rides the snapshot.
2664
+ ...(surface.completed ? { completed: true } : {}),
2665
+ ...(surface.completionSummary
2666
+ ? { completionSummary: surface.completionSummary }
2667
+ : {}),
2611
2668
  } as unknown as ContentBlock);
2612
2669
  }
2613
2670
  modified = true;
@@ -2874,10 +2931,9 @@ export async function handleMessageComplete(
2874
2931
  // The row was reserved at `llm_call_started` (with channel metadata
2875
2932
  // stamped at that point) and `state.lastAssistantMessageId` carries its
2876
2933
  // id. Flush the final content via `updateContent` instead of inserting a
2877
- // new row. No `syncToDisk` flag here the orchestrator separately
2878
- // invokes `syncMessageToDisk` on `state.lastAssistantMessageId` after
2879
- // the loop completes (see
2880
- // `conversation-agent-loop.ts::syncLastAssistantMessageToDisk`).
2934
+ // new row. No `syncToDisk` flag here: the orchestrator separately invokes
2935
+ // `syncMessageToDisk` on `state.lastAssistantMessageId` after the loop
2936
+ // completes (see `conversation-turn-finalize.ts::settleTurnContent`).
2881
2937
  const assistantMessageId = state.lastAssistantMessageId;
2882
2938
  if (!assistantMessageId) {
2883
2939
  throw new Error(