@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
@@ -42,7 +42,7 @@ import {
42
42
  enforceSameActorOrErrorResult,
43
43
  pickSameUserAutoResolve,
44
44
  } from "../runtime/auth/same-actor.js";
45
- import type { AuthContext } from "../runtime/auth/types.js";
45
+ import { resolveCapabilities } from "../runtime/capabilities.js";
46
46
  import type {
47
47
  InteractiveUiRequest,
48
48
  InteractiveUiResult,
@@ -57,10 +57,9 @@ import { resolveAppId } from "../tools/apps/resolve-app-id.js";
57
57
  import type { ToolExecutionResult } from "../tools/types.js";
58
58
  import { getLogger } from "../util/logger.js";
59
59
  import { isPlainObject } from "../util/object.js";
60
+ import type { Conversation } from "./conversation.js";
60
61
  import { buildConversationErrorMessage } from "./conversation-error.js";
61
62
  import { launchConversation } from "./conversation-launch.js";
62
- import type { EnqueueMessageOptions } from "./conversation-messaging.js";
63
- import type { ProcessMessageOptions } from "./conversation-process.js";
64
63
  import {
65
64
  buildSurfaceShowPair,
66
65
  type CurrentTurnSurface,
@@ -68,7 +67,6 @@ import {
68
67
  type SurfaceShowPair,
69
68
  type SurfaceStateEntry,
70
69
  } from "./conversation-surface-state.js";
71
- import type { HostAppControlProxy } from "./host-app-control-proxy.js";
72
70
  import type { HostCuProxy } from "./host-cu-proxy.js";
73
71
  import type {
74
72
  AnySurfaceData,
@@ -86,6 +84,7 @@ import type {
86
84
  UiSurfaceShow,
87
85
  } from "./message-protocol.js";
88
86
  import { INTERACTIVE_SURFACE_TYPES } from "./message-protocol.js";
87
+ import { isRowVisibleToUntrustedActor } from "./message-provenance.js";
89
88
  export {
90
89
  buildSurfaceShowPair,
91
90
  type CurrentTurnSurface,
@@ -97,7 +96,6 @@ export {
97
96
  } from "./conversation-surface-state.js";
98
97
  import type { HostAppControlInput } from "./message-types/host-app-control.js";
99
98
  import type { UserMessageAttachment } from "./message-types/shared.js";
100
- import type { TrustContext } from "./trust-context-types.js";
101
99
 
102
100
  /**
103
101
  * Prefix of the synthetic user-message text this module writes when a surface
@@ -175,11 +173,70 @@ const pendingSurfacePersists = new Map<
175
173
  }
176
174
  >();
177
175
 
176
+ /** A located `ui_surface` block plus everything needed to write it back. */
177
+ type PersistedSurfaceHit = {
178
+ rowId: string;
179
+ blocks: unknown[];
180
+ block: Record<string, unknown>;
181
+ blockIndex: number;
182
+ };
183
+
184
+ /**
185
+ * Locate the persisted `ui_surface` content block for `surfaceId`.
186
+ *
187
+ * The single owner of how a persisted surface block is resolved *in this
188
+ * module*: newest message row first, first matching block within that row,
189
+ * stopping at the first hit. Every persisted read and write here goes through
190
+ * it so a change to storage resolution, ordering, or matching can never make
191
+ * them target different blocks. `findPersistedSurfaceState`
192
+ * (`runtime/routes/surface-conversation-resolver.ts`) is the other persisted
193
+ * `ui_surface` reader and deliberately does not: it runs its own SQL scan,
194
+ * bounded by the compaction boundary, for the read-only content route.
195
+ *
196
+ * `filterByProvenance` applies {@link isRowVisibleToUntrustedActor} per row,
197
+ * the identical predicate `loadFromDb` applies when building `messages`. It
198
+ * covers the persisted type/data read ({@link findPersistedSurfaceInfo})
199
+ * only. Writes scan unfiltered, so a completion write can mutate a row the
200
+ * requester cannot see and push requester-controlled summary text into it.
201
+ * The live-state path (`ctx.surfaceState` / `ctx.pendingSurfaceActions`) is
202
+ * unfiltered too: it is scoped to the trust the conversation was loaded
203
+ * under, not the requester's.
204
+ *
205
+ * The scan is deliberately unbounded by compaction (see
206
+ * {@link findPersistedSurfaceInfo}), and it throws rather than swallowing DB
207
+ * errors so each caller keeps its own logging.
208
+ */
209
+ function findPersistedSurfaceBlock(
210
+ conversationId: string,
211
+ surfaceId: string,
212
+ opts: { filterByProvenance: boolean },
213
+ ): PersistedSurfaceHit | undefined {
214
+ const { filterByProvenance } = opts;
215
+ const rows = getMessages(conversationId);
216
+ for (let r = rows.length - 1; r >= 0; r--) {
217
+ if (filterByProvenance && !isRowVisibleToUntrustedActor(rows[r].metadata)) {
218
+ continue;
219
+ }
220
+ const blocks: unknown[] = rows[r].content;
221
+ const blockIndex = blocks.findIndex((pb) => {
222
+ const rb = pb as Record<string, unknown>;
223
+ return rb.type === "ui_surface" && rb.surfaceId === surfaceId;
224
+ });
225
+ if (blockIndex !== -1) {
226
+ return {
227
+ rowId: rows[r].id,
228
+ blocks,
229
+ block: blocks[blockIndex] as Record<string, unknown>,
230
+ blockIndex,
231
+ };
232
+ }
233
+ }
234
+ return undefined;
235
+ }
236
+
178
237
  /**
179
- * Persist the latest `data` for a `ui_surface` content block by
180
- * scanning the conversation's messages for one containing the given
181
- * `surfaceId` and patching its `data` field. Mirrors the scan-and-patch
182
- * pattern in `markSurfaceCompleted`.
238
+ * Persist the latest `data` for a `ui_surface` content block by locating it
239
+ * with {@link findPersistedSurfaceBlock} and patching its `data` field.
183
240
  *
184
241
  * Safe to call before the assistant message has been persisted (mid-stream):
185
242
  * the scan simply finds nothing and bails. The next update after
@@ -191,23 +248,14 @@ function persistSurfaceData(
191
248
  data: SurfaceData,
192
249
  ): void {
193
250
  try {
194
- const rows = getMessages(conversationId);
195
- for (let r = rows.length - 1; r >= 0; r--) {
196
- const parsed: unknown[] = rows[r].content;
197
- let found = false;
198
- for (const pb of parsed) {
199
- const rb = pb as Record<string, unknown>;
200
- if (rb.type === "ui_surface" && rb.surfaceId === surfaceId) {
201
- rb.data = data;
202
- found = true;
203
- break;
204
- }
205
- }
206
- if (found) {
207
- updateMessageContent(rows[r].id, JSON.stringify(parsed));
208
- return;
209
- }
251
+ const hit = findPersistedSurfaceBlock(conversationId, surfaceId, {
252
+ filterByProvenance: false,
253
+ });
254
+ if (!hit) {
255
+ return;
210
256
  }
257
+ hit.block.data = data;
258
+ updateMessageContent(hit.rowId, JSON.stringify(hit.blocks));
211
259
  } catch (err) {
212
260
  log.debug(
213
261
  { err, surfaceId, conversationId },
@@ -306,22 +354,72 @@ export function flushPendingSurfaceDataPersists(conversationId?: string): void {
306
354
 
307
355
  /**
308
356
  * Mark a `ui_surface` content block as completed in the database so that
309
- * history reconstruction preserves the completion state. Also updates
357
+ * history reconstruction preserves the completion state. Also updates
310
358
  * in-memory messages when available.
359
+ *
360
+ * Never throws: a persistence hiccup must not fail the surface action that
361
+ * triggered it. Returns whether the completion is safe to announce to
362
+ * clients, so a caller about to broadcast `ui_surface_complete` can withhold
363
+ * it. A write that threw is not safe: it leaves a persisted block that reverts
364
+ * to pending on the next history reseed. Finding no block to write IS safe: a
365
+ * standalone surface owns none, so nothing can revert.
366
+ *
367
+ * A surface still in `ctx.currentTurnSurfaces` owns no persisted block either,
368
+ * but one is coming: the turn-finalization appenders build it from that
369
+ * snapshot. The completion is stamped onto the snapshot so the appended block
370
+ * carries it, instead of landing as a fresh pending card the next reseed
371
+ * reactivates.
311
372
  */
312
373
  export function markSurfaceCompleted(
313
- ctx: { conversationId: string; messages?: Array<{ content: unknown }> },
374
+ ctx: {
375
+ conversationId: string;
376
+ messages?: Array<{ content: unknown }>;
377
+ currentTurnSurfaces?: CurrentTurnSurface[];
378
+ },
314
379
  surfaceId: string,
315
380
  summary: string,
316
- ): void {
381
+ ): boolean {
317
382
  // Force-flush any pending debounced data persist so the completion
318
383
  // patch lands on top of the latest data instead of racing with it.
319
384
  flushSurfaceDataPersist(surfaceId);
320
385
 
386
+ try {
387
+ const hit = findPersistedSurfaceBlock(ctx.conversationId, surfaceId, {
388
+ filterByProvenance: false,
389
+ });
390
+ if (hit) {
391
+ hit.block.completed = true;
392
+ hit.block.completionSummary = summary;
393
+ updateMessageContent(hit.rowId, JSON.stringify(hit.blocks));
394
+ } else {
395
+ // Mutated in place so a later `ui_update` that respreads the entry
396
+ // carries the completion forward.
397
+ const pendingSnapshot = ctx.currentTurnSurfaces?.find(
398
+ (s) => s.surfaceId === surfaceId,
399
+ );
400
+ if (pendingSnapshot) {
401
+ pendingSnapshot.completed = true;
402
+ pendingSnapshot.completionSummary = summary;
403
+ }
404
+ }
405
+ } catch (err) {
406
+ // Error, not warn: a silent failure here presents as the user's answer being discarded.
407
+ log.error(
408
+ { err, conversationId: ctx.conversationId, surfaceId },
409
+ "Failed to persist surface completion to DB",
410
+ );
411
+ // In-memory messages stay untouched so this process's history cannot
412
+ // claim an answered card the database still holds as pending.
413
+ return false;
414
+ }
415
+
321
416
  // Update in-memory messages when available so subsequent reads within
322
- // this session see the change without waiting for DB.
417
+ // this session see the change without waiting for DB. Newest match only,
418
+ // matching the persisted patch above: marking every copy of a duplicated
419
+ // surfaceId would make memory and history disagree, and a reseed would
420
+ // revert the extras.
323
421
  if (ctx.messages) {
324
- for (let i = ctx.messages.length - 1; i >= 0; i--) {
422
+ outer: for (let i = ctx.messages.length - 1; i >= 0; i--) {
325
423
  const msg = ctx.messages[i];
326
424
  if (!Array.isArray(msg.content)) {
327
425
  continue;
@@ -331,48 +429,94 @@ export function markSurfaceCompleted(
331
429
  if (b.type === "ui_surface" && b.surfaceId === surfaceId) {
332
430
  b.completed = true;
333
431
  b.completionSummary = summary;
334
- break;
432
+ break outer;
335
433
  }
336
434
  }
337
435
  }
338
436
  }
339
437
 
340
- // Persist to DB.
438
+ return true;
439
+ }
440
+
441
+ /** What a persisted `ui_surface` block can still tell us once it is cold. */
442
+ type PersistedSurfaceInfo = {
443
+ /** The block's `surfaceType`, absent when it carries no string type. */
444
+ surfaceType: string | undefined;
445
+ /** The block's `data`, absent when it carries no plain-object data. */
446
+ data: Record<string, unknown> | undefined;
447
+ };
448
+
449
+ /**
450
+ * Read a `ui_surface` block's `surfaceType` and `data` out of persisted
451
+ * history. Both travel together because every caller that has lost the live
452
+ * entry needs both: the type to decide completion, the data for the labels a
453
+ * completion summary quotes.
454
+ *
455
+ * `requesterCanAccessMemory` is the REQUESTER's memory capability, not the
456
+ * trust class whatever conversation happens to be loaded under. The live path
457
+ * this falls back from hides guardian-provenance rows from an untrusted actor
458
+ * (`loadFromDb` → `restoreSurfaceStateFromHistory`), so the live entry is
459
+ * missing precisely when the row was filtered out. The scan therefore applies
460
+ * the same per-row predicate rather than handing back what the filter dropped.
461
+ *
462
+ * This must NOT be routed through `findPersistedSurfaceState`
463
+ * (`runtime/routes/surface-conversation-resolver.ts`): that helper is bounded
464
+ * by `liveHistoryStartRow` / `contextCompactedMessageCount` and by design will
465
+ * not see a surface behind the compaction boundary, which is exactly the case
466
+ * this lookup exists to serve. {@link findPersistedSurfaceBlock}, which also
467
+ * backs the write paths, is unbounded, so reads and writes stay consistent.
468
+ *
469
+ * Hits are deliberately not memoized into `conversation.surfaceState`; see
470
+ * `runtime/routes/surface-content-routes.ts` for why that shared map must not
471
+ * absorb scan results.
472
+ *
473
+ * Exported for its unit test only, and not a supported read API. In-repo callers
474
+ * go through `handleSurfaceAction`.
475
+ */
476
+ export function findPersistedSurfaceInfo(
477
+ conversationId: string,
478
+ surfaceId: string,
479
+ opts: { requesterCanAccessMemory: boolean },
480
+ ): PersistedSurfaceInfo | undefined {
341
481
  try {
342
- const rows = getMessages(ctx.conversationId);
343
- for (let r = rows.length - 1; r >= 0; r--) {
344
- const parsed: unknown[] = rows[r].content;
345
- let found = false;
346
- for (const pb of parsed) {
347
- const rb = pb as Record<string, unknown>;
348
- if (rb.type === "ui_surface" && rb.surfaceId === surfaceId) {
349
- rb.completed = true;
350
- rb.completionSummary = summary;
351
- found = true;
352
- break;
353
- }
354
- }
355
- if (found) {
356
- updateMessageContent(rows[r].id, JSON.stringify(parsed));
357
- return;
358
- }
482
+ const hit = findPersistedSurfaceBlock(conversationId, surfaceId, {
483
+ filterByProvenance: !opts.requesterCanAccessMemory,
484
+ });
485
+ if (!hit) {
486
+ return undefined;
359
487
  }
488
+ const data = hit.block.data;
489
+ return {
490
+ surfaceType:
491
+ typeof hit.block.surfaceType === "string"
492
+ ? hit.block.surfaceType
493
+ : undefined,
494
+ data: isPlainObject(data) ? data : undefined,
495
+ };
360
496
  } catch (err) {
361
- log.warn({ err, surfaceId }, "Failed to persist surface completion to DB");
497
+ log.warn(
498
+ { err, conversationId, surfaceId },
499
+ "Failed to read persisted surface info from DB",
500
+ );
362
501
  }
502
+ return undefined;
363
503
  }
364
504
 
365
505
  /**
366
506
  * Complete a `ui_surface` card and notify live clients, addressed only by
367
507
  * conversation + surface id.
368
508
  *
369
- * Unlike {@link completeSurfaceFromAction}, this needs no live `Conversation`
509
+ * Unlike {@link maybeCompleteSurfaceAfterAction}, this needs no live `Conversation`
370
510
  * instance, so it can run from flows that don't own one — projecting a
371
511
  * terminal guardian-request status onto its in-app approval card when the
372
512
  * request was resolved on another surface (or by the expiry sweep). Persists
373
- * the completion (reload-safe) and broadcasts `ui_surface_complete` so every
374
- * connected client of this guardian converges. No-ops when the surface block
375
- * isn't found in the conversation.
513
+ * the completion (reload-safe) first, then broadcasts `ui_surface_complete` so
514
+ * every connected client of this guardian converges.
515
+ *
516
+ * The broadcast is deliberately NOT gated on the persist, unlike the
517
+ * user-action completion paths: the underlying request is already resolved, so
518
+ * withholding the announcement on a transient write failure strands a live,
519
+ * clickable approval card for a decision that has already been made.
376
520
  */
377
521
  export function completeSurfaceAndNotify(
378
522
  conversationId: string,
@@ -419,18 +563,12 @@ export function removeSurfaceBlock(
419
563
  }
420
564
 
421
565
  try {
422
- const rows = getMessages(ctx.conversationId);
423
- for (let r = rows.length - 1; r >= 0; r--) {
424
- const parsed: unknown[] = rows[r].content;
425
- const idx = parsed.findIndex((pb) => {
426
- const rb = pb as Record<string, unknown>;
427
- return rb.type === "ui_surface" && rb.surfaceId === surfaceId;
428
- });
429
- if (idx !== -1) {
430
- parsed.splice(idx, 1);
431
- updateMessageContent(rows[r].id, JSON.stringify(parsed));
432
- return;
433
- }
566
+ const hit = findPersistedSurfaceBlock(ctx.conversationId, surfaceId, {
567
+ filterByProvenance: false,
568
+ });
569
+ if (hit) {
570
+ hit.blocks.splice(hit.blockIndex, 1);
571
+ updateMessageContent(hit.rowId, JSON.stringify(hit.blocks));
434
572
  }
435
573
  } catch (err) {
436
574
  log.warn({ err, surfaceId }, "Failed to remove dismissed surface from DB");
@@ -798,7 +936,7 @@ function isTaskProgressCardData(data: SurfaceData | Record<string, unknown>) {
798
936
  }
799
937
 
800
938
  function isSlackTaskProgressUiException(
801
- ctx: SurfaceConversationContext,
939
+ ctx: Conversation,
802
940
  toolName: string,
803
941
  input: Record<string, unknown>,
804
942
  ): boolean {
@@ -834,89 +972,6 @@ function isSlackTaskProgressUiException(
834
972
  return false;
835
973
  }
836
974
 
837
- /**
838
- * Subset of Conversation state that surface helpers need access to.
839
- * The Conversation class implements this interface so its instances can be
840
- * passed directly to the extracted functions.
841
- */
842
-
843
- export interface SurfaceConversationContext {
844
- readonly conversationId: string;
845
- /** Assistant id (if known) — used when publishing launch-triggered events. */
846
- readonly assistantId?: string;
847
- /** Inherited to spawned conversations in the `launch_conversation` action path. */
848
- readonly trustContext?: TrustContext;
849
- /** Verified requester auth context for the active turn. */
850
- readonly authContext?: AuthContext;
851
- /** Per-turn auth snapshot, preferred for tool dispatch authorization. */
852
- readonly currentTurnAuthContext?: AuthContext;
853
- /** JWT-verified requester principal for the active turn. */
854
- readonly currentTurnSourceActorPrincipalId?: string;
855
- readonly channelCapabilities?: {
856
- channel: string;
857
- supportsDynamicUi: boolean;
858
- };
859
- sendToClient(msg: AssistantEvent): void;
860
- pendingSurfaceActions: Map<string, { surfaceType: SurfaceType }>;
861
- lastSurfaceAction: Map<
862
- string,
863
- { actionId: string; data?: Record<string, unknown> }
864
- >;
865
- surfaceState: Map<string, SurfaceStateEntry>;
866
- surfaceUndoStacks: Map<string, string[]>;
867
- accumulatedSurfaceState: Map<string, Record<string, unknown>>;
868
- /** Request IDs that originated from surface action button clicks (not regular user messages). */
869
- surfaceActionRequestIds: Set<string>;
870
- /**
871
- * Pending standalone UI requests keyed by surfaceId.
872
- * These are daemon-driven surfaces (not LLM tool invocations) that block
873
- * the caller until the user submits, cancels, or the timeout elapses.
874
- * Optional: only present on conversations that support standalone surfaces.
875
- */
876
- pendingStandaloneSurfaces?: Map<
877
- string,
878
- {
879
- resolve: (result: InteractiveUiResult) => void;
880
- timer: ReturnType<typeof setTimeout>;
881
- surfaceType: SurfaceType;
882
- }
883
- >;
884
- /**
885
- * Short-lived tombstone set of recently-completed standalone surface IDs.
886
- * Prevents late client actions (arriving after timeout/resolution) from
887
- * falling through to the history-restored path and triggering an
888
- * unintended LLM turn. Entries are auto-removed after a TTL.
889
- */
890
- recentlyCompletedStandaloneSurfaces?: Map<
891
- string,
892
- ReturnType<typeof setTimeout>
893
- >;
894
- currentTurnSurfaces: CurrentTurnSurface[];
895
- /** Optional proxy for delegating computer-use actions to a connected desktop client. */
896
- hostCuProxy?: HostCuProxy;
897
- /** Optional proxy for delegating per-app app-control actions to a connected desktop client. */
898
- hostAppControlProxy?: HostAppControlProxy;
899
- /**
900
- * Setter that lets the resolver detach the conversation's app-control proxy
901
- * after `app_control_stop`. Disposes the existing proxy when transitioning
902
- * to undefined so subsequent tool calls cleanly fail with "unavailable"
903
- * rather than dispatching to a torn-down proxy.
904
- */
905
- setHostAppControlProxy?(proxy: HostAppControlProxy | undefined): void;
906
- /** True when no interactive client is connected (headless / channel-only). */
907
- readonly hasNoClient?: boolean;
908
- isProcessing(): boolean;
909
- enqueueMessage(options: EnqueueMessageOptions): {
910
- queued: boolean;
911
- requestId: string;
912
- rejected?: boolean;
913
- };
914
- getQueueDepth(): number;
915
- processMessage(options: ProcessMessageOptions): Promise<string>;
916
- /** Serialize operations on a given surface to prevent read-modify-write races. */
917
- withSurface<T>(surfaceId: string, fn: () => T | Promise<T>): Promise<T>;
918
- }
919
-
920
975
  export type SurfaceMutex = {
921
976
  <T>(surfaceId: string, fn: () => T | Promise<T>): Promise<T>;
922
977
  /** Number of surfaces with an active chain — exposed for tests. */
@@ -977,7 +1032,7 @@ const STANDALONE_TOMBSTONE_TTL_MS = 30_000; // 30 seconds
977
1032
  * support dynamic UI.
978
1033
  */
979
1034
  export function canShowInteractiveUi(
980
- ctx: Pick<SurfaceConversationContext, "hasNoClient" | "channelCapabilities">,
1035
+ ctx: Pick<Conversation, "hasNoClient" | "channelCapabilities">,
981
1036
  ): boolean {
982
1037
  if (ctx.hasNoClient) {
983
1038
  return false;
@@ -998,7 +1053,7 @@ export function canShowInteractiveUi(
998
1053
  * so that `handleSurfaceAction` can intercept the callback.
999
1054
  */
1000
1055
  export function showStandaloneSurface(
1001
- ctx: SurfaceConversationContext,
1056
+ ctx: Conversation,
1002
1057
  request: InteractiveUiRequest,
1003
1058
  surfaceId: string,
1004
1059
  ): Promise<InteractiveUiResult> {
@@ -1168,7 +1223,7 @@ function buildStandaloneSurfaceData(
1168
1223
  */
1169
1224
  export function cleanupStandaloneSurface(
1170
1225
  ctx: Pick<
1171
- SurfaceConversationContext,
1226
+ Conversation,
1172
1227
  | "pendingStandaloneSurfaces"
1173
1228
  | "recentlyCompletedStandaloneSurfaces"
1174
1229
  | "surfaceState"
@@ -1233,7 +1288,7 @@ const OPEN_PANEL_ACK_TIMEOUT_MS = 10_000;
1233
1288
  * removes it on every outcome.
1234
1289
  */
1235
1290
  export function openChannelSetupPanel(
1236
- ctx: SurfaceConversationContext,
1291
+ ctx: Conversation,
1237
1292
  surfaceId: string,
1238
1293
  data: Record<string, unknown>,
1239
1294
  options?: { signal?: AbortSignal; timeoutMs?: number },
@@ -1318,7 +1373,7 @@ export function openChannelSetupPanel(
1318
1373
  * Auto-saves the document content to the app store.
1319
1374
  */
1320
1375
  function handleDocumentContentChanged(
1321
- ctx: SurfaceConversationContext,
1376
+ ctx: Conversation,
1322
1377
  surfaceId: string,
1323
1378
  data?: Record<string, unknown>,
1324
1379
  ): void {
@@ -1394,7 +1449,7 @@ function handleDocumentContentChanged(
1394
1449
  * Accumulates state via shallow merge without triggering an LLM turn.
1395
1450
  */
1396
1451
  function handleStateUpdate(
1397
- ctx: SurfaceConversationContext,
1452
+ ctx: Conversation,
1398
1453
  surfaceId: string,
1399
1454
  data?: Record<string, unknown>,
1400
1455
  ): void {
@@ -1438,10 +1493,7 @@ function pushUndoState(
1438
1493
  }
1439
1494
  }
1440
1495
 
1441
- export function handleSurfaceUndo(
1442
- ctx: SurfaceConversationContext,
1443
- surfaceId: string,
1444
- ): void {
1496
+ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
1445
1497
  const stack = ctx.surfaceUndoStacks.get(surfaceId);
1446
1498
  if (!stack || stack.length === 0) {
1447
1499
  ctx.sendToClient({
@@ -1666,7 +1718,7 @@ function getRequestedSurfaceCompletionSummary(
1666
1718
  * recorded when the user commits the surface).
1667
1719
  */
1668
1720
  function recordActivationMoment(
1669
- ctx: SurfaceConversationContext,
1721
+ ctx: Conversation,
1670
1722
  moment: ActivationMomentParam,
1671
1723
  ): void {
1672
1724
  try {
@@ -1700,10 +1752,7 @@ function recordActivationMoment(
1700
1752
  * submitted / selected-and-committed), NOT from intermediate non-terminal
1701
1753
  * events (`selection_changed` / `content_changed` / `state_update`).
1702
1754
  */
1703
- function maybeEmitActivationMoment(
1704
- ctx: SurfaceConversationContext,
1705
- surfaceId: string,
1706
- ): void {
1755
+ function maybeEmitActivationMoment(ctx: Conversation, surfaceId: string): void {
1707
1756
  const stored = ctx.surfaceState.get(surfaceId);
1708
1757
  const moment = stored?.activationMoment;
1709
1758
  if (!moment) {
@@ -1715,18 +1764,30 @@ function maybeEmitActivationMoment(
1715
1764
  recordActivationMoment(ctx, moment);
1716
1765
  }
1717
1766
 
1718
- function completeSurfaceFromAction(
1719
- ctx: SurfaceConversationContext,
1767
+ /**
1768
+ * Record a surface action the message queue refused.
1769
+ *
1770
+ * The route answers `{ ok: true }` for a rejected enqueue and the client
1771
+ * optimistically completes the card on that, so the user sees an answered card
1772
+ * that the next history reseed reverts. Nothing else on this path logs, so
1773
+ * without this the only user-visible cause of a reverted card leaves no trace.
1774
+ */
1775
+ function logSurfaceActionRejected(
1776
+ ctx: Conversation,
1720
1777
  surfaceId: string,
1721
- summary: string,
1778
+ actionId: string,
1779
+ surfaceType: string | undefined,
1722
1780
  ): void {
1723
- broadcastMessage({
1724
- type: "ui_surface_complete",
1725
- conversationId: ctx.conversationId,
1726
- surfaceId,
1727
- summary,
1728
- });
1729
- markSurfaceCompleted(ctx, surfaceId, summary);
1781
+ log.warn(
1782
+ {
1783
+ conversationId: ctx.conversationId,
1784
+ surfaceId,
1785
+ actionId,
1786
+ surfaceType,
1787
+ queueDepth: ctx.getQueueDepth(),
1788
+ },
1789
+ "Surface action rejected by the message queue",
1790
+ );
1730
1791
  }
1731
1792
 
1732
1793
  // One-shot interactive surfaces auto-complete once their action message is
@@ -1740,8 +1801,78 @@ const ONE_SHOT_SURFACE_TYPES = [
1740
1801
  "task_preferences",
1741
1802
  ];
1742
1803
 
1804
+ /**
1805
+ * The completion rule for an accepted surface action: complete when the client
1806
+ * asked for it explicitly (`_completeSurface`), or when the surface is a
1807
+ * one-shot type. No-op otherwise. Owns the whole rule, including reading the
1808
+ * `_completeSurface` request out of the raw action data.
1809
+ *
1810
+ * Called from both `handleSurfaceAction` branches, one holding a pending entry
1811
+ * and one with none, always after `enqueueMessage` accepted the turn so a
1812
+ * rejected enqueue leaves the surface answerable. Persists first and broadcasts
1813
+ * `ui_surface_complete` only once that write is known not to have failed: a
1814
+ * client must not render a card the next history reseed reverts.
1815
+ *
1816
+ * "No pending entry" is broader than "restored from history", and with
1817
+ * `pendingSurfaceActions` empty the two cannot be told apart: a surface shown
1818
+ * with `await_action: false` never registered a pending entry, and a daemon
1819
+ * restart drops the entry of one that did. So a one-shot surface that never
1820
+ * awaited an action does complete on its first action.
1821
+ *
1822
+ * `liveSurfaceType` / `liveSurfaceData` come from in-memory state. When the
1823
+ * type is missing the persisted block supplies both, read at most once per
1824
+ * action and only once the decision actually needs it.
1825
+ */
1826
+ function maybeCompleteSurfaceAfterAction(
1827
+ ctx: Conversation,
1828
+ surfaceId: string,
1829
+ actionId: string,
1830
+ actionData: Record<string, unknown> | undefined,
1831
+ opts: {
1832
+ liveSurfaceType: string | undefined;
1833
+ liveSurfaceData: Record<string, unknown> | undefined;
1834
+ requesterCanAccessMemory: boolean;
1835
+ submittedData?: Record<string, unknown>;
1836
+ },
1837
+ ): void {
1838
+ const requestedSummary = getRequestedSurfaceCompletionSummary(actionData);
1839
+
1840
+ let surfaceType = opts.liveSurfaceType;
1841
+ let surfaceData = opts.liveSurfaceData;
1842
+ // Only the type-driven one-shot rule needs history: an explicit request
1843
+ // carries its own summary and decides on its own. Type and labels come from
1844
+ // the same read so the decision and the summary can never disagree.
1845
+ if (surfaceType === undefined && !requestedSummary) {
1846
+ const persisted = findPersistedSurfaceInfo(ctx.conversationId, surfaceId, {
1847
+ requesterCanAccessMemory: opts.requesterCanAccessMemory,
1848
+ });
1849
+ surfaceType = persisted?.surfaceType;
1850
+ surfaceData = persisted?.data ?? surfaceData;
1851
+ }
1852
+
1853
+ const isOneShot =
1854
+ surfaceType !== undefined && ONE_SHOT_SURFACE_TYPES.includes(surfaceType);
1855
+ if (!requestedSummary && !isOneShot) {
1856
+ return;
1857
+ }
1858
+
1859
+ const summary =
1860
+ requestedSummary ??
1861
+ buildCompletionSummary(surfaceType, actionId, actionData, surfaceData);
1862
+ if (!markSurfaceCompleted(ctx, surfaceId, summary)) {
1863
+ return;
1864
+ }
1865
+ broadcastMessage({
1866
+ type: "ui_surface_complete",
1867
+ conversationId: ctx.conversationId,
1868
+ surfaceId,
1869
+ summary,
1870
+ ...(opts.submittedData ? { submittedData: opts.submittedData } : {}),
1871
+ });
1872
+ }
1873
+
1743
1874
  export async function handleSurfaceAction(
1744
- ctx: SurfaceConversationContext,
1875
+ ctx: Conversation,
1745
1876
  surfaceId: string,
1746
1877
  actionId: string,
1747
1878
  data?: Record<string, unknown>,
@@ -1802,6 +1933,9 @@ export async function handleSurfaceAction(
1802
1933
  return { accepted: true, conversationId: ctx.conversationId };
1803
1934
  }
1804
1935
 
1936
+ // Broadcast unconditionally: `showStandaloneSurface` renders straight to
1937
+ // the client without appending a `ui_surface` block, so there is no
1938
+ // persisted state a failed completion write could contradict.
1805
1939
  broadcastMessage({
1806
1940
  type: "ui_surface_complete",
1807
1941
  conversationId: ctx.conversationId,
@@ -1911,6 +2045,16 @@ export async function handleSurfaceAction(
1911
2045
  return { accepted: true, conversationId };
1912
2046
  }
1913
2047
 
2048
+ // Trust of the actor committing THIS action. `POST /v1/surface-actions`
2049
+ // stamps the verified requester's trust onto the conversation before
2050
+ // dispatching here, and `loadFromDb` derives the live history filter from
2051
+ // the same value, so the completion path's persisted read stays scoped to
2052
+ // exactly what this actor's live view would have held. Unresolvable trust
2053
+ // fails closed to the untrusted filter.
2054
+ const requesterCanAccessMemory = resolveCapabilities(
2055
+ ctx.trustContext?.trustClass,
2056
+ ).canAccessMemory;
2057
+
1914
2058
  const pending = ctx.pendingSurfaceActions.get(surfaceId);
1915
2059
 
1916
2060
  // When surfaces are restored from history (e.g. onboarding cards), there is
@@ -2019,8 +2163,12 @@ export async function handleSurfaceAction(
2019
2163
 
2020
2164
  log.info(
2021
2165
  {
2166
+ conversationId: ctx.conversationId,
2022
2167
  surfaceId,
2023
2168
  actionId,
2169
+ // False means the surface type and the labels its completion summary
2170
+ // quotes can only come from persisted history.
2171
+ hasLiveState: stored !== undefined,
2024
2172
  contentLength: content.length,
2025
2173
  contentPreview: content.slice(0, 200),
2026
2174
  attachmentCount: attachments.length,
@@ -2057,6 +2205,7 @@ export async function handleSurfaceAction(
2057
2205
 
2058
2206
  if (result.rejected) {
2059
2207
  ctx.surfaceActionRequestIds.delete(requestId);
2208
+ logSurfaceActionRejected(ctx, surfaceId, actionId, stored?.surfaceType);
2060
2209
  return;
2061
2210
  }
2062
2211
 
@@ -2066,11 +2215,12 @@ export async function handleSurfaceAction(
2066
2215
  // (and the one-shot tag stays intact for the user's retry).
2067
2216
  maybeEmitActivationMoment(ctx, surfaceId);
2068
2217
 
2069
- const requestedCompletionSummary =
2070
- getRequestedSurfaceCompletionSummary(mergedData);
2071
- if (requestedCompletionSummary) {
2072
- completeSurfaceFromAction(ctx, surfaceId, requestedCompletionSummary);
2073
- }
2218
+ maybeCompleteSurfaceAfterAction(ctx, surfaceId, actionId, mergedData, {
2219
+ liveSurfaceType: stored?.surfaceType,
2220
+ liveSurfaceData: stored?.data as Record<string, unknown> | undefined,
2221
+ requesterCanAccessMemory,
2222
+ submittedData: actionDataForText,
2223
+ });
2074
2224
 
2075
2225
  // One-shot: clear accumulated state now that the message has been accepted.
2076
2226
  // Deferred until after rejection check so state is preserved for retry on rejection.
@@ -2302,6 +2452,7 @@ export async function handleSurfaceAction(
2302
2452
  });
2303
2453
  if (result.rejected) {
2304
2454
  ctx.surfaceActionRequestIds.delete(requestId);
2455
+ logSurfaceActionRejected(ctx, surfaceId, actionId, pending.surfaceType);
2305
2456
  return;
2306
2457
  }
2307
2458
 
@@ -2311,26 +2462,12 @@ export async function handleSurfaceAction(
2311
2462
  // one-shot tag stays intact for the user's retry).
2312
2463
  maybeEmitActivationMoment(ctx, surfaceId);
2313
2464
 
2314
- const requestedCompletionSummary =
2315
- getRequestedSurfaceCompletionSummary(mergedData);
2316
-
2317
- // One-shot interactive surfaces — auto-complete now that the message has
2318
- // been accepted. Deferred until after rejection check so the surface stays
2319
- // active and retryable if the queue was full.
2320
- if (
2321
- requestedCompletionSummary ||
2322
- ONE_SHOT_SURFACE_TYPES.includes(pending.surfaceType)
2323
- ) {
2324
- const completionSummary = requestedCompletionSummary ?? summary;
2325
- broadcastMessage({
2326
- type: "ui_surface_complete",
2327
- conversationId: ctx.conversationId,
2328
- surfaceId,
2329
- summary: completionSummary,
2330
- submittedData: mergedDataForText,
2331
- });
2332
- markSurfaceCompleted(ctx, surfaceId, completionSummary);
2333
- }
2465
+ maybeCompleteSurfaceAfterAction(ctx, surfaceId, actionId, mergedData, {
2466
+ liveSurfaceType: pending.surfaceType,
2467
+ liveSurfaceData: surfaceData,
2468
+ requesterCanAccessMemory,
2469
+ submittedData: mergedDataForText,
2470
+ });
2334
2471
 
2335
2472
  // One-shot: clear accumulated state now that the message has been accepted.
2336
2473
  // Deferred until after rejection check so state is preserved for retry on rejection.
@@ -2401,7 +2538,7 @@ export async function handleSurfaceAction(
2401
2538
  * After an app_refresh, refresh any active surface that displays the updated app.
2402
2539
  */
2403
2540
  export function refreshSurfacesForApp(
2404
- ctx: SurfaceConversationContext,
2541
+ ctx: Conversation,
2405
2542
  appId: string,
2406
2543
  opts?: { fileChange?: boolean; status?: string },
2407
2544
  ): boolean {
@@ -2707,12 +2844,56 @@ export function buildAppOpenPreview(
2707
2844
  };
2708
2845
  }
2709
2846
 
2847
+ /**
2848
+ * Explain why a `computer_use_*` call cannot be dispatched, after
2849
+ * {@link ensureHostCuProxy} has already failed to attach one.
2850
+ *
2851
+ * "No desktop client connected" is only one of the reasons, and stating it
2852
+ * unconditionally contradicts `assistant clients list` whenever a desktop is
2853
+ * plainly connected. Only counts are reported — never client ids or actor
2854
+ * principals — since this string reaches the model and the transcript.
2855
+ */
2856
+ function describeComputerUseUnavailable(ctx: Conversation): string {
2857
+ const capable = assistantEventHub.listClientsByCapability("host_cu");
2858
+ if (capable.length === 0) {
2859
+ return "Computer use is not available — no desktop client connected. Open the Vellum desktop app on the machine you want to control, then retry.";
2860
+ }
2861
+ return `Computer use is not available for this conversation — ${capable.length} desktop client(s) advertise host_cu, but none of them can be driven from this conversation's interface (${ctx.transportInterface ?? "unknown"}) as its current user.`;
2862
+ }
2863
+
2864
+ /**
2865
+ * Return the conversation's CU proxy, attaching one first when the
2866
+ * conversation has none.
2867
+ *
2868
+ * Host-proxy attachment is decided at turn boundaries — message create and
2869
+ * queue drain — and a `computer_use_*` call can arrive well after that
2870
+ * decision was made. A conversation whose gate failed at turn start (the
2871
+ * desktop had not connected yet, or the actor principal was not yet
2872
+ * resolvable) therefore stayed permanently without a CU proxy for the rest
2873
+ * of the turn, and every computer-use call reported "no desktop client
2874
+ * connected" while `assistant clients list` showed the desktop connected and
2875
+ * usable.
2876
+ *
2877
+ * Re-running the same gate here — `Conversation.ensureHostProxiesForTurn`,
2878
+ * the very function both turn-boundary paths call — makes the decision track
2879
+ * the live state instead of a stale snapshot. It grants nothing on its own:
2880
+ * the gate is unchanged, and every dispatch below still binds to the calling
2881
+ * actor through the same-actor checks.
2882
+ */
2883
+ function ensureHostCuProxy(ctx: Conversation): HostCuProxy | undefined {
2884
+ if (ctx.hostCuProxy) {
2885
+ return ctx.hostCuProxy;
2886
+ }
2887
+ ctx.ensureHostProxiesForTurn?.(ctx.transportInterface);
2888
+ return ctx.hostCuProxy;
2889
+ }
2890
+
2710
2891
  /**
2711
2892
  * Resolve a proxy tool call that targets a UI surface.
2712
2893
  * Handles ui_show, ui_update, ui_dismiss, computer_use_* proxy tools, and app_open.
2713
2894
  */
2714
2895
  export async function surfaceProxyResolver(
2715
- ctx: SurfaceConversationContext,
2896
+ ctx: Conversation,
2716
2897
  toolName: string,
2717
2898
  input: Record<string, unknown>,
2718
2899
  signal?: AbortSignal,
@@ -2720,9 +2901,10 @@ export async function surfaceProxyResolver(
2720
2901
  ): Promise<ToolExecutionResult> {
2721
2902
  // Route CU proxy tools (all computer_use_* action tools)
2722
2903
  if (toolName.startsWith("computer_use_")) {
2723
- if (!ctx.hostCuProxy || !ctx.hostCuProxy.isAvailable()) {
2904
+ const hostCuProxy = ensureHostCuProxy(ctx);
2905
+ if (!hostCuProxy || !hostCuProxy.isAvailable()) {
2724
2906
  return {
2725
- content: "Computer use is not available — no desktop client connected.",
2907
+ content: describeComputerUseUnavailable(ctx),
2726
2908
  isError: true,
2727
2909
  };
2728
2910
  }
@@ -2738,7 +2920,7 @@ export async function surfaceProxyResolver(
2738
2920
  : typeof input.answer === "string"
2739
2921
  ? input.answer
2740
2922
  : "Task complete";
2741
- ctx.hostCuProxy.reset();
2923
+ hostCuProxy.reset();
2742
2924
  return { content: summary, isError: false };
2743
2925
  }
2744
2926
 
@@ -2811,12 +2993,12 @@ export async function surfaceProxyResolver(
2811
2993
  }
2812
2994
  }
2813
2995
 
2814
- ctx.hostCuProxy.recordAction(toolName, input, reasoning);
2815
- return ctx.hostCuProxy.request(
2996
+ hostCuProxy.recordAction(toolName, input, reasoning);
2997
+ return hostCuProxy.request(
2816
2998
  toolName,
2817
2999
  input,
2818
3000
  ctx.conversationId,
2819
- ctx.hostCuProxy.stepCount,
3001
+ hostCuProxy.stepCount,
2820
3002
  reasoning,
2821
3003
  signal,
2822
3004
  targetClientId,
@@ -3394,6 +3576,18 @@ export async function surfaceProxyResolver(
3394
3576
  lastAction.data,
3395
3577
  stored?.data,
3396
3578
  );
3579
+ // A `ui_show` surface owns a persisted block, so the completion has to
3580
+ // land before the client is told the card is answered.
3581
+ if (!markSurfaceCompleted(ctx, surfaceId, summary)) {
3582
+ // Tearing the live entries down here would strand the card that the
3583
+ // next reseed restores, so leave them for a retry and tell the model
3584
+ // the dismissal did not land.
3585
+ return {
3586
+ content:
3587
+ "Could not dismiss the surface: persisting its completion failed. The card is still showing, so try ui_dismiss again.",
3588
+ isError: true,
3589
+ };
3590
+ }
3397
3591
  ctx.sendToClient({
3398
3592
  type: "ui_surface_complete",
3399
3593
  conversationId: ctx.conversationId,
@@ -3401,7 +3595,6 @@ export async function surfaceProxyResolver(
3401
3595
  summary,
3402
3596
  submittedData: lastAction.data,
3403
3597
  });
3404
- markSurfaceCompleted(ctx, surfaceId, summary);
3405
3598
  } else {
3406
3599
  ctx.sendToClient({
3407
3600
  type: "ui_surface_dismiss",