@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,240 @@
1
+ /**
2
+ * Tests for persisting an answered `ask_question` prompt onto its tool_use
3
+ * block, so the question and the user's answer stay in the transcript instead
4
+ * of disappearing with the interactive card.
5
+ *
6
+ * Covers the write half of the round-trip:
7
+ * handleToolResult(event carrying answeredQuestion)
8
+ * → immediate `_answeredQuestion` stamp on the persisted row
9
+ * → state.toolAnsweredQuestions captures it
10
+ * → annotatePersistedAssistantMessage re-stamps at end of turn
11
+ *
12
+ * The read half (renderHistoryContent in handlers/shared.ts) lives in
13
+ * server-history-render.test.ts.
14
+ */
15
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
16
+
17
+ let mockedRowContent = "";
18
+ const updates: Array<{ id: string; content: string }> = [];
19
+
20
+ mock.module("../persistence/conversation-crud.js", () => ({
21
+ setConversationProcessingStartedAt: () => {},
22
+ isConversationProcessing: () => false,
23
+ addMessage: () => ({ id: "mock-msg-id" }),
24
+ getMessageById: (id: string) =>
25
+ mockedRowContent ? { id, content: JSON.parse(mockedRowContent) } : null,
26
+ updateMessageContent: (id: string, content: string) => {
27
+ updates.push({ id, content });
28
+ mockedRowContent = content;
29
+ },
30
+ provenanceFromTrustContext: () => ({}),
31
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
32
+ }));
33
+
34
+ mock.module("../persistence/llm-request-log-store.js", () => ({
35
+ recordRequestLog: () => {},
36
+ backfillMessageIdOnLogs: () => {},
37
+ }));
38
+
39
+ // ── Imports (after mocks) ─────────────────────────────────────────────────────
40
+ import type { AnsweredQuestion } from "../api/events/question-answered.js";
41
+ import type {
42
+ EventHandlerDeps,
43
+ EventHandlerState,
44
+ } from "../daemon/conversation-agent-loop-handlers.js";
45
+ import {
46
+ createEventHandlerState,
47
+ handleToolResult,
48
+ } from "../daemon/conversation-agent-loop-handlers.js";
49
+
50
+ // ── Helpers ───────────────────────────────────────────────────────────────────
51
+
52
+ const emitted: Array<Record<string, unknown>> = [];
53
+
54
+ function makeDeps(): EventHandlerDeps {
55
+ return {
56
+ ctx: {
57
+ conversationId: "test-conv",
58
+ provider: { name: "anthropic" },
59
+ streamThinking: false,
60
+ emitActivityState: () => {},
61
+ markWorkspaceTopLevelDirty: () => {},
62
+ currentTurnSurfaces: [],
63
+ } as unknown as EventHandlerDeps["ctx"],
64
+ onEvent: (event: unknown) => {
65
+ emitted.push(event as Record<string, unknown>);
66
+ },
67
+ reqId: "test-req",
68
+ isFirstMessage: false,
69
+ shouldGenerateTitle: false,
70
+ rlog: new Proxy({} as Record<string, unknown>, {
71
+ get: () => () => {},
72
+ }) as unknown as EventHandlerDeps["rlog"],
73
+ turnChannelContext: {
74
+ userMessageChannel: "vellum",
75
+ assistantMessageChannel: "vellum",
76
+ } as unknown as EventHandlerDeps["turnChannelContext"],
77
+ turnInterfaceContext: {
78
+ userMessageInterface: "web",
79
+ assistantMessageInterface: "web",
80
+ } as unknown as EventHandlerDeps["turnInterfaceContext"],
81
+ applyCompaction: async () => {},
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Seed a turn whose assistant row holds `toolUseIds` tool_use blocks, all of
87
+ * them `ask_question` unless stated otherwise. Every id is registered as
88
+ * in-flight, so end-of-turn annotation only runs once each has a result.
89
+ */
90
+ function setupState(toolUseIds: string[]): EventHandlerState {
91
+ const state = createEventHandlerState();
92
+ state.lastAssistantMessageId = "msg-1";
93
+ for (const id of toolUseIds) {
94
+ state.toolUseIdToName.set(id, "ask_question");
95
+ state.toolCallTimestamps.set(id, { startedAt: Date.now() });
96
+ state.currentTurnToolUseIds.push(id);
97
+ }
98
+ mockedRowContent = JSON.stringify(
99
+ toolUseIds.map((id) => ({
100
+ type: "tool_use",
101
+ id,
102
+ name: "ask_question",
103
+ input: { questions: [{ question: "Which Alice?", options: [] }] },
104
+ })),
105
+ );
106
+ return state;
107
+ }
108
+
109
+ function persistedToolUse(toolUseId: string): Record<string, unknown> {
110
+ const parsed = JSON.parse(mockedRowContent) as Array<Record<string, unknown>>;
111
+ const block = parsed.find((b) => b.type === "tool_use" && b.id === toolUseId);
112
+ if (!block) {
113
+ throw new Error(`tool_use block ${toolUseId} not found`);
114
+ }
115
+ return block;
116
+ }
117
+
118
+ const ANSWERED: AnsweredQuestion = {
119
+ requestId: "req-1",
120
+ questions: [
121
+ {
122
+ id: "q1",
123
+ question: "Which Alice?",
124
+ options: [
125
+ { id: "alice_work", label: "Alice (work)" },
126
+ { id: "alice_personal", label: "Alice (personal)" },
127
+ ],
128
+ },
129
+ ],
130
+ responses: [{ questionId: "q1", decision: "option", optionId: "alice_work" }],
131
+ overall: "completed",
132
+ };
133
+
134
+ // ── Tests ─────────────────────────────────────────────────────────────────────
135
+
136
+ describe("answered ask_question persistence", () => {
137
+ beforeEach(() => {
138
+ updates.length = 0;
139
+ emitted.length = 0;
140
+ mockedRowContent = "";
141
+ });
142
+
143
+ test("stamps the answered record on the tool_use block and forwards it to clients", () => {
144
+ const state = setupState(["tu_q"]);
145
+
146
+ handleToolResult(state, makeDeps(), {
147
+ type: "tool_result",
148
+ toolUseId: "tu_q",
149
+ content: 'Question "Which Alice?" → Option: alice_work (Alice (work))',
150
+ isError: false,
151
+ answeredQuestion: ANSWERED,
152
+ });
153
+
154
+ expect(persistedToolUse("tu_q")._answeredQuestion).toEqual(ANSWERED);
155
+ const toolResult = emitted.find((e) => e.type === "tool_result");
156
+ expect(toolResult?.answeredQuestion).toEqual(ANSWERED);
157
+ });
158
+
159
+ test("stamps immediately, before the turn's other tools have finished", () => {
160
+ // A batch where `ask_question` settles first: the user can switch away
161
+ // (forcing a history refetch) while the sibling tool still runs, so the
162
+ // answer must already be durable rather than waiting for end-of-turn
163
+ // annotation.
164
+ const state = setupState(["tu_q", "tu_slow"]);
165
+
166
+ handleToolResult(state, makeDeps(), {
167
+ type: "tool_result",
168
+ toolUseId: "tu_q",
169
+ content: "answered",
170
+ isError: false,
171
+ answeredQuestion: ANSWERED,
172
+ });
173
+
174
+ expect(
175
+ state.toolCallTimestamps.get("tu_slow")?.completedAt,
176
+ ).toBeUndefined();
177
+ expect(persistedToolUse("tu_q")._answeredQuestion).toEqual(ANSWERED);
178
+ });
179
+
180
+ test("survives the end-of-turn annotation that re-stamps the row", () => {
181
+ const state = setupState(["tu_q"]);
182
+ const deps = makeDeps();
183
+
184
+ handleToolResult(state, deps, {
185
+ type: "tool_result",
186
+ toolUseId: "tu_q",
187
+ content: "answered",
188
+ isError: false,
189
+ answeredQuestion: ANSWERED,
190
+ });
191
+
192
+ // The result above completes the turn's only tool, so annotation has
193
+ // already run. It writes timing metadata alongside, and must not drop the
194
+ // answered record it did not author.
195
+ const block = persistedToolUse("tu_q");
196
+ expect(block._answeredQuestion).toEqual(ANSWERED);
197
+ expect(block._startedAt).toBeNumber();
198
+ });
199
+
200
+ test("re-stamping the same record does not rewrite the row", () => {
201
+ const state = setupState(["tu_q"]);
202
+ const deps = makeDeps();
203
+
204
+ handleToolResult(state, deps, {
205
+ type: "tool_result",
206
+ toolUseId: "tu_q",
207
+ content: "answered",
208
+ isError: false,
209
+ answeredQuestion: ANSWERED,
210
+ });
211
+ // Early stamp + end-of-turn annotation.
212
+ expect(updates).toHaveLength(2);
213
+
214
+ handleToolResult(state, deps, {
215
+ type: "tool_result",
216
+ toolUseId: "tu_q",
217
+ content: "answered",
218
+ isError: false,
219
+ answeredQuestion: ANSWERED,
220
+ });
221
+
222
+ // The second result re-runs the early stamp; the identical requestId makes
223
+ // it a no-op, so the row is not rewritten.
224
+ expect(updates).toHaveLength(2);
225
+ expect(persistedToolUse("tu_q")._answeredQuestion).toEqual(ANSWERED);
226
+ });
227
+
228
+ test("leaves the block unstamped when the result carries no answered record", () => {
229
+ const state = setupState(["tu_q"]);
230
+
231
+ handleToolResult(state, makeDeps(), {
232
+ type: "tool_result",
233
+ toolUseId: "tu_q",
234
+ content: "User did not respond within timeout",
235
+ isError: true,
236
+ });
237
+
238
+ expect(persistedToolUse("tu_q")._answeredQuestion).toBeUndefined();
239
+ });
240
+ });
@@ -781,10 +781,11 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
781
781
  expect(prevLast.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
782
782
  });
783
783
 
784
- test("mutableLatestUserMessage: during a tool-use loop placement is unchanged (block is fixed within the turn)", async () => {
785
- // Turn-start is not the last message (tool_result follows), so the
786
- // turn-start block is fixed for the rest of the turn the flag must not
787
- // move the anchor.
784
+ test("mutableLatestUserMessage: a volatile turn start keeps the 5m TTL through its tool-use loop", async () => {
785
+ // The turn start is fixed for the rest of the turn, so it stays markable;
786
+ // but it is volatile ACROSS turns, so the long TTL would buy a write that
787
+ // can never be read back. Upgrading it here would also mark the same block
788
+ // at a second TTL, billing two writes for one reusable prefix.
788
789
  const messages: Message[] = [
789
790
  userMsg("Turn 1"),
790
791
  assistantMsg("Response 1"),
@@ -804,14 +805,12 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
804
805
  }>;
805
806
  }>;
806
807
 
807
- // Turn-start (Turn 2, index 2) still gets the 1h anchor — within a tool-use
808
- // loop the block is fixed, so the volatile-latest flag must not move it.
809
808
  const turn2 = sent[2];
810
809
  const turn2Last = turn2.content[turn2.content.length - 1];
811
- expect(turn2Last.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
810
+ expect(turn2Last.cache_control).toEqual({ type: "ephemeral", ttl: "5m" });
812
811
  });
813
812
 
814
- test("mutableLatestUserMessage: first turn with no previous user message does not throw and applies no long-TTL anchor", async () => {
813
+ test("mutableLatestUserMessage: volatile first turn carries the 5m tail but no long-TTL anchor", async () => {
815
814
  await provider.sendMessage([userMsg("First ever turn (volatile)")], {
816
815
  config: { mutableLatestUserMessage: true },
817
816
  });
@@ -823,9 +822,28 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
823
822
  cache_control?: { type: string; ttl?: string };
824
823
  }>;
825
824
  }>;
826
- // Only user message is volatile and there is no prior stable one, so no
827
- // 1h anchor and no 5m tail bridge land on any user message — there is no
828
- // previous-turn anchor to bridge to. Graceful, no throw.
825
+ // The only user message is volatile, so it gets no 1h anchor: those bytes
826
+ // do not recur next turn. It does get the 5m tail; the message is fixed
827
+ // within its own turn, so the tool-loop calls that follow read the system
828
+ // prompt, tools, and opening message back instead of re-writing them.
829
+ const lastBlock = sent[0].content[sent[0].content.length - 1];
830
+ expect(lastBlock.cache_control).toEqual({ type: "ephemeral", ttl: "5m" });
831
+ });
832
+
833
+ test("mutableLatestUserMessage: volatile first turn with disableTurnStartCache places no message breakpoint", async () => {
834
+ // One-shot call sites opt out of paying for a turn-start write; the 5m
835
+ // bridge lands on that same block, so it honors the same opt-out.
836
+ await provider.sendMessage([userMsg("One-shot (volatile)")], {
837
+ config: { mutableLatestUserMessage: true, disableTurnStartCache: true },
838
+ });
839
+
840
+ const sent = lastStreamParams!.messages as Array<{
841
+ role: string;
842
+ content: Array<{
843
+ type: string;
844
+ cache_control?: { type: string; ttl?: string };
845
+ }>;
846
+ }>;
829
847
  const lastBlock = sent[0].content[sent[0].content.length - 1];
830
848
  expect(lastBlock.cache_control).toBeUndefined();
831
849
  });
@@ -1410,9 +1428,11 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1410
1428
  expect(sent[2].content[0].type).toBe("text");
1411
1429
  });
1412
1430
 
1413
- test("orphaned server_tool_use at end of messages gets synthetic result (no synthetic user append)", async () => {
1414
- // Orphaned server_tool_use at the end should get a synthetic
1415
- // web_search_tool_result but no synthetic user message.
1431
+ test("unanswered server_tool_use in the final assistant message is left intact for deferred execution", async () => {
1432
+ // When the final assistant message ends with an unanswered
1433
+ // server_tool_use, the API executes the search on this request (the
1434
+ // pause_turn / deferred-execution contract). The tail must go back
1435
+ // verbatim with no synthetic result, or the search is cancelled.
1416
1436
  const messages: Message[] = [
1417
1437
  userMsg("Search something"),
1418
1438
  {
@@ -1434,12 +1454,11 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1434
1454
  content: Array<{ type: string; tool_use_id?: string }>;
1435
1455
  }>;
1436
1456
 
1437
- // Original 2 messages, with synthetic result injected in assistant message
1457
+ // Original 2 messages, assistant tail verbatim: no synthetic result
1438
1458
  expect(sent).toHaveLength(2);
1439
1459
  expect(sent[1].role).toBe("assistant");
1460
+ expect(sent[1].content).toHaveLength(1);
1440
1461
  expect(sent[1].content[0].type).toBe("server_tool_use");
1441
- expect(sent[1].content[1].type).toBe("web_search_tool_result");
1442
- expect(sent[1].content[1].tool_use_id).toBe("srvtoolu_end");
1443
1462
  });
1444
1463
 
1445
1464
  test("server_tool_use with matching web_search_tool_result passes through unchanged", async () => {
@@ -1568,7 +1587,11 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1568
1587
  expect(webSearchResults[0].tool_use_id).toBe("srvtoolu_search");
1569
1588
  });
1570
1589
 
1571
- test("mixed tool_use and server_tool_use only client-side tool_use gets pairing, server tools pass through", async () => {
1590
+ test("mixed tool_use and deferred server_tool_use tail goes out verbatim", async () => {
1591
+ // The mixed parallel group is the deferred-execution trigger: the API
1592
+ // returns stop_reason tool_use without running the search, and runs it on
1593
+ // the next request when the client tool results come back with the
1594
+ // assistant message unchanged. No split, no synthetic result.
1572
1595
  const messages: Message[] = [
1573
1596
  userMsg("Do things"),
1574
1597
  {
@@ -1583,7 +1606,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1583
1606
  },
1584
1607
  ],
1585
1608
  },
1586
- // Only tu_a has a result server_tool_use doesn't need one in the user message
1609
+ // Only tu_a has a result; the deferred search has none yet
1587
1610
  toolResultMsg("tu_a", "result A"),
1588
1611
  ];
1589
1612
  await provider.sendMessage(messages);
@@ -1597,27 +1620,139 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1597
1620
  }>;
1598
1621
  }>;
1599
1622
 
1600
- // Assistant message should have tool_use in paired portion, server_tool_use in carryover
1601
- // ensureToolPairing splits: paired = [tool_use(tu_a)], carryover = [server_tool_use(srvtoolu_b)]
1602
- // Result: assistant(tool_use) → user(tool_result) → assistant(server_tool_use) → user(synthetic_continuation)
1623
+ // assistant(tool_use, server_tool_use) user(tool_result), verbatim
1624
+ expect(sent).toHaveLength(3);
1603
1625
  const assistantMsg = sent[1];
1604
1626
  expect(assistantMsg.role).toBe("assistant");
1605
- expect(assistantMsg.content[0].type).toBe("tool_use");
1627
+ expect(assistantMsg.content.map((b) => b.type)).toEqual([
1628
+ "tool_use",
1629
+ "server_tool_use",
1630
+ ]);
1606
1631
 
1607
1632
  const userAfterAssistant = sent[2];
1608
1633
  expect(userAfterAssistant.role).toBe("user");
1609
- // Only tool_result for tu_a — no synthetic web_search_tool_result
1610
1634
  expect(userAfterAssistant.content[0]).toMatchObject({
1611
1635
  type: "tool_result",
1612
1636
  tool_use_id: "tu_a",
1613
1637
  });
1614
1638
 
1615
- // server_tool_use preserved in a carryover assistant message with synthetic result
1616
- const carryoverAssistant = sent[3];
1617
- expect(carryoverAssistant.role).toBe("assistant");
1618
- expect(carryoverAssistant.content[0].type).toBe("server_tool_use");
1619
- expect(carryoverAssistant.content[1].type).toBe("web_search_tool_result");
1620
- expect(carryoverAssistant.content[1].tool_use_id).toBe("srvtoolu_b");
1639
+ // No synthetic web_search_tool_result anywhere; the API runs the search
1640
+ const allBlocks = sent.flatMap((m) => m.content);
1641
+ expect(
1642
+ allBlocks.filter((b) => b.type === "web_search_tool_result"),
1643
+ ).toHaveLength(0);
1644
+ });
1645
+
1646
+ test("deferred mixed heartbeat shape with text and multiple searches goes out verbatim", async () => {
1647
+ const messages: Message[] = [
1648
+ userMsg("Heartbeat: check the file and the news"),
1649
+ {
1650
+ role: "assistant",
1651
+ content: [
1652
+ { type: "text", text: "Checking both." },
1653
+ { type: "tool_use", id: "tu_read", name: "file_read", input: {} },
1654
+ {
1655
+ type: "server_tool_use",
1656
+ id: "srvtoolu_s1",
1657
+ name: "web_search",
1658
+ input: { query: "news one" },
1659
+ },
1660
+ {
1661
+ type: "server_tool_use",
1662
+ id: "srvtoolu_s2",
1663
+ name: "web_search",
1664
+ input: { query: "news two" },
1665
+ },
1666
+ ],
1667
+ },
1668
+ toolResultMsg("tu_read", "file contents"),
1669
+ ];
1670
+ await provider.sendMessage(messages);
1671
+
1672
+ const sent = lastStreamParams!.messages as Array<{
1673
+ role: string;
1674
+ content: Array<{ type: string; id?: string; tool_use_id?: string }>;
1675
+ }>;
1676
+
1677
+ expect(sent).toHaveLength(3);
1678
+ expect(sent[1].content.map((b) => b.type)).toEqual([
1679
+ "text",
1680
+ "tool_use",
1681
+ "server_tool_use",
1682
+ "server_tool_use",
1683
+ ]);
1684
+ const allBlocks = sent.flatMap((m) => m.content);
1685
+ expect(
1686
+ allBlocks.filter((b) => b.type === "web_search_tool_result"),
1687
+ ).toHaveLength(0);
1688
+ });
1689
+
1690
+ test("cross-message server tool pair from a completed deferred execution is preserved verbatim", async () => {
1691
+ // After a deferred search executes, history carries the split pair: the
1692
+ // server_tool_use in one assistant message and its web_search_tool_result
1693
+ // heading the next assistant message. Both sides must pass through
1694
+ // untouched: no synthetic result on the use, no text downgrade on the
1695
+ // result.
1696
+ const messages: Message[] = [
1697
+ userMsg("Check the file and the news"),
1698
+ {
1699
+ role: "assistant",
1700
+ content: [
1701
+ { type: "text", text: "On it." },
1702
+ { type: "tool_use", id: "tu_a", name: "file_read", input: {} },
1703
+ {
1704
+ type: "server_tool_use",
1705
+ id: "srvtoolu_deferred",
1706
+ name: "web_search",
1707
+ input: { query: "news" },
1708
+ },
1709
+ ],
1710
+ },
1711
+ toolResultMsg("tu_a", "file contents"),
1712
+ {
1713
+ role: "assistant",
1714
+ content: [
1715
+ {
1716
+ type: "web_search_tool_result",
1717
+ tool_use_id: "srvtoolu_deferred",
1718
+ content: [
1719
+ {
1720
+ type: "web_search_result",
1721
+ url: "https://example.com",
1722
+ title: "Example",
1723
+ encrypted_content: "enc_deferred",
1724
+ },
1725
+ ],
1726
+ },
1727
+ { type: "text", text: "Here is the news." },
1728
+ ],
1729
+ },
1730
+ userMsg("thanks, anything else?"),
1731
+ ];
1732
+ await provider.sendMessage(messages);
1733
+
1734
+ const sent = lastStreamParams!.messages as Array<{
1735
+ role: string;
1736
+ content: Array<{ type: string; id?: string; tool_use_id?: string }>;
1737
+ }>;
1738
+
1739
+ expect(sent).toHaveLength(5);
1740
+ expect(sent[1].content.map((b) => b.type)).toEqual([
1741
+ "text",
1742
+ "tool_use",
1743
+ "server_tool_use",
1744
+ ]);
1745
+ expect(sent[3].content.map((b) => b.type)).toEqual([
1746
+ "web_search_tool_result",
1747
+ "text",
1748
+ ]);
1749
+ // Exactly the original result block, still a result block
1750
+ const allBlocks = sent.flatMap((m) => m.content);
1751
+ const results = allBlocks.filter(
1752
+ (b) => b.type === "web_search_tool_result",
1753
+ );
1754
+ expect(results).toHaveLength(1);
1755
+ expect(results[0].tool_use_id).toBe("srvtoolu_deferred");
1621
1756
  });
1622
1757
 
1623
1758
  test("orphaned server_tool_use from interrupted stream gets repaired in multi-turn conversation", async () => {
@@ -19,6 +19,8 @@
19
19
  */
20
20
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
21
21
 
22
+ import { asConversation } from "./helpers/mock-conversation.js";
23
+
22
24
  // ---------------------------------------------------------------------------
23
25
  // Module mocks (must be installed before importing the units under test)
24
26
  // ---------------------------------------------------------------------------
@@ -102,12 +104,11 @@ const {
102
104
  _setActiveAppControlSession,
103
105
  } = await import("../daemon/host-app-control-proxy.js");
104
106
  const { ROUTES } = await import("../runtime/routes/host-app-control-routes.js");
105
- const { surfaceProxyResolver } =
107
+ const { createSurfaceMutex, surfaceProxyResolver } =
106
108
  await import("../daemon/conversation-surfaces.js");
107
109
  const { setConversation, clearConversations } =
108
110
  await import("../daemon/conversation-registry.js");
109
- type SurfaceConversationContext =
110
- import("../daemon/conversation-surfaces.js").SurfaceConversationContext;
111
+ type Conversation = import("../daemon/conversation.js").Conversation;
111
112
 
112
113
  const handleHostAppControlResult = ROUTES.find(
113
114
  (r) => r.endpoint === "host-app-control-result",
@@ -123,15 +124,15 @@ const TINY_PNG_B64 = "iVBORw0KGgoAAAA";
123
124
  // ---------------------------------------------------------------------------
124
125
 
125
126
  /**
126
- * Build a SurfaceConversationContext with a real proxy attached. Only the
127
+ * Build a Conversation with a real proxy attached. Only the
127
128
  * fields the app-control branch reads are populated — see
128
129
  * `cu-unified-flow.test.ts` for the analogous shape.
129
130
  */
130
131
  function buildContext(
131
132
  proxy: InstanceType<typeof HostAppControlProxy>,
132
133
  conversationId = "test-conv",
133
- ): SurfaceConversationContext {
134
- return {
134
+ ): Conversation {
135
+ return asConversation({
135
136
  conversationId,
136
137
  trustContext: {
137
138
  sourceChannel: "vellum",
@@ -151,8 +152,8 @@ function buildContext(
151
152
  enqueueMessage: () => ({ queued: false, requestId: "r1" }),
152
153
  getQueueDepth: () => 0,
153
154
  processMessage: async () => "",
154
- withSurface: async (_id, fn) => fn(),
155
- };
155
+ withSurface: createSurfaceMutex(),
156
+ });
156
157
  }
157
158
 
158
159
  /**
@@ -11,6 +11,7 @@ import {
11
11
  getReplayWindow,
12
12
  } from "../runtime/assistant-stream-state.js";
13
13
  import * as pendingInteractions from "../runtime/pending-interactions.js";
14
+ import { registerHubClient } from "./helpers/hub-clients.js";
14
15
 
15
16
  function makeEvent(
16
17
  overrides: Partial<AssistantEventEnvelope> = {},
@@ -435,6 +436,53 @@ describe("AssistantEventHub — actorPrincipalId on ClientEntry", () => {
435
436
  });
436
437
  });
437
438
 
439
+ // ── ClientEntry desktop presence ─────────────────────────────────────────────
440
+
441
+ describe("AssistantEventHub client presence", () => {
442
+ test("records presence on a matching client", () => {
443
+ const hub = new AssistantEventHub();
444
+ registerHubClient({ hub, clientId: "mac-1" });
445
+
446
+ expect(hub.setClientPresence("mac-1", "active")).toBe(true);
447
+
448
+ const presence = hub.getClientById("mac-1")!.presence!;
449
+ expect(presence.state).toBe("active");
450
+ });
451
+
452
+ test("reportedAt advances on every report", async () => {
453
+ const hub = new AssistantEventHub();
454
+ registerHubClient({ hub, clientId: "mac-1" });
455
+
456
+ hub.setClientPresence("mac-1", "idle");
457
+ const first = hub.getClientById("mac-1")!.presence!;
458
+
459
+ await Bun.sleep(5);
460
+ hub.setClientPresence("mac-1", "idle");
461
+ const second = hub.getClientById("mac-1")!.presence!;
462
+
463
+ expect(second.reportedAt.getTime()).toBeGreaterThan(
464
+ first.reportedAt.getTime(),
465
+ );
466
+ });
467
+
468
+ test("returns false for an unknown clientId", () => {
469
+ const hub = new AssistantEventHub();
470
+ registerHubClient({ hub, clientId: "mac-1" });
471
+
472
+ expect(hub.setClientPresence("does-not-exist", "active")).toBe(false);
473
+ expect(hub.getClientById("mac-1")?.presence).toBeUndefined();
474
+ });
475
+
476
+ test("a disposed client no longer accepts presence", () => {
477
+ const hub = new AssistantEventHub();
478
+ registerHubClient({ hub, clientId: "mac-1" });
479
+
480
+ expect(hub.disposeClient("mac-1")).toBe(1);
481
+ expect(hub.setClientPresence("mac-1", "active")).toBe(false);
482
+ expect(hub.getClientById("mac-1")).toBeUndefined();
483
+ });
484
+ });
485
+
438
486
  // ── capabilityForMessageType — host-prefix routing ───────────────────────────
439
487
 
440
488
  describe("capabilityForMessageType — host-prefix routing", () => {