@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
@@ -79,12 +79,33 @@ const ERROR_RECENCY_WINDOW_MS = 15 * 60_000;
79
79
  // ── Types ─────────────────────────────────────────────────────────────
80
80
 
81
81
  export type TelegramWebhookHealthStatus =
82
- /** Webhook registered, no recent delivery errors. */
82
+ /**
83
+ * Telegram holds the URL the reconciler last registered, and reports no
84
+ * recent delivery error. This is the only status that may be presented as
85
+ * confirmed delivery.
86
+ */
83
87
  | "healthy"
84
88
  /** Telegram holds no webhook URL at all — the channel is definitively dark. */
85
89
  | "not_registered"
90
+ /**
91
+ * Telegram holds a webhook URL, but not the one the reconciler registered.
92
+ * A stale tunnel address or another deployment's callback delivers to
93
+ * somewhere this assistant is not listening, which reads identically to a
94
+ * healthy channel until someone sends a message. This is the case
95
+ * `last_error_date` cannot see on a quiet channel (see the module header's
96
+ * known limitation).
97
+ */
98
+ | "url_mismatch"
86
99
  /** Telegram reported a delivery error inside the recency window. */
87
100
  | "delivery_failing"
101
+ /**
102
+ * A webhook is registered and not erroring, but the reconciler has not
103
+ * recorded which URL it registered, so ownership cannot be established.
104
+ * Expected transiently between the credential save and reconciliation, and
105
+ * persistently for deployments whose last registration predates recording.
106
+ * Distinct from `healthy` because "no fault found" is not "verified".
107
+ */
108
+ | "unverified"
88
109
  /** Preconditions unmet (no bot token / no webhook routing) — nothing to check. */
89
110
  | "skipped"
90
111
  /** Telegram was unreachable or answered unusably — health is unknown. */
@@ -97,6 +118,10 @@ export interface TelegramWebhookHealthResult {
97
118
  lastErrorMessage?: string;
98
119
  lastErrorDate?: number;
99
120
  pendingUpdateCount?: number;
121
+ /** The URL Telegram currently holds, when one is registered. */
122
+ registeredUrl?: string;
123
+ /** The URL the reconciler recorded, when it has recorded one. */
124
+ expectedUrl?: string;
100
125
  }
101
126
 
102
127
  /**
@@ -133,6 +158,28 @@ function telegramApiBaseUrl(): string {
133
158
  }
134
159
  }
135
160
 
161
+ /**
162
+ * The webhook URL the gateway reconciler recorded on its last successful
163
+ * `setWebhook`, or undefined when it has never recorded one.
164
+ *
165
+ * This is deliberately a stored fact rather than a re-derivation. The gateway
166
+ * and the assistant already maintain mirrored copies of the routing tier order
167
+ * (`resolveExpectedTelegramWebhookUrl` and `hasWebhookRoutingConfigured`, which
168
+ * cross-reference each other in comments after LUM-2899), and a third copy here
169
+ * would be a third thing to keep in agreement. It also lets the managed path be
170
+ * checked at all: resolving a platform callback URL means POSTing a route
171
+ * registration, which a read-only health check must not do.
172
+ */
173
+ function recordedWebhookUrl(): string | undefined {
174
+ try {
175
+ const recorded = getConfig().telegram.registeredWebhookUrl;
176
+ const trimmed = recorded?.trim();
177
+ return trimmed ? trimmed : undefined;
178
+ } catch {
179
+ return undefined;
180
+ }
181
+ }
182
+
136
183
  /** Name the channel concretely when the bot username is known. */
137
184
  function channelLabel(): string {
138
185
  const username = getTelegramBotUsername();
@@ -246,6 +293,27 @@ export async function checkTelegramWebhookHealth(): Promise<TelegramWebhookHealt
246
293
  };
247
294
  }
248
295
 
296
+ const registeredUrl = info.url.trim();
297
+ const expectedUrl = recordedWebhookUrl();
298
+
299
+ // Ownership is checked before delivery errors on purpose. When Telegram is
300
+ // pointed somewhere we never registered, any error it reports describes
301
+ // delivery to that other destination, so reporting the error would name a
302
+ // symptom while the mismatch is the cause, and the two have different fixes
303
+ // (re-run reconciliation vs. investigate a reachable ingress).
304
+ if (expectedUrl && registeredUrl !== expectedUrl) {
305
+ return {
306
+ status: "url_mismatch",
307
+ detail:
308
+ `${label} is registered with Telegram at a different address than this assistant ` +
309
+ `last registered, so its messages are being delivered somewhere this assistant is not ` +
310
+ `listening. ${fixPath(usesManagedCallbacks)}`,
311
+ registeredUrl,
312
+ expectedUrl,
313
+ ...(pendingUpdateCount !== undefined ? { pendingUpdateCount } : {}),
314
+ };
315
+ }
316
+
249
317
  const lastErrorDate = info.last_error_date;
250
318
  const errorIsRecent =
251
319
  lastErrorDate !== undefined &&
@@ -270,9 +338,28 @@ export async function checkTelegramWebhookHealth(): Promise<TelegramWebhookHealt
270
338
  };
271
339
  }
272
340
 
341
+ if (!expectedUrl) {
342
+ // A webhook is registered and quiet, but nothing recorded which URL we
343
+ // registered, so this cannot be distinguished from a stranger's webhook
344
+ // that happens not to be erroring. Reporting "healthy" here is the
345
+ // overclaim this status exists to prevent.
346
+ return {
347
+ status: "unverified",
348
+ detail:
349
+ `${label} has a webhook registered with Telegram and reports no recent delivery errors, ` +
350
+ `but this assistant has no record of registering it, so it cannot confirm the messages ` +
351
+ `reach here. Re-running setup records the registration.`,
352
+ registeredUrl,
353
+ ...(lastErrorDate !== undefined ? { lastErrorDate } : {}),
354
+ ...(pendingUpdateCount !== undefined ? { pendingUpdateCount } : {}),
355
+ };
356
+ }
357
+
273
358
  return {
274
359
  status: "healthy",
275
- detail: `${label} webhook is registered and reporting no recent delivery errors`,
360
+ detail: `${label} is registered at the address this assistant last set, and Telegram reports no recent delivery errors`,
361
+ registeredUrl,
362
+ expectedUrl,
276
363
  ...(lastErrorDate !== undefined ? { lastErrorDate } : {}),
277
364
  ...(pendingUpdateCount !== undefined ? { pendingUpdateCount } : {}),
278
365
  };
@@ -385,7 +472,18 @@ export async function runTelegramWebhookHealthCheck(): Promise<TelegramWebhookHe
385
472
  // Neither evidence of health nor of failure — leave the latch as-is so an
386
473
  // unreachable Telegram (or a channel that isn't set up) can't either raise a
387
474
  // false alarm or silently clear a real one.
388
- if (result.status === "skipped" || result.status === "unknown") {
475
+ //
476
+ // `unverified` belongs here rather than with the failures below: a webhook
477
+ // that is registered and quiet, on a deployment that never recorded its
478
+ // registration, is unproven, not broken. Alerting on it would page the
479
+ // guardian about every install whose last reconciliation predates URL
480
+ // recording. Note the asymmetry with `url_mismatch`, which falls through to
481
+ // the alert: there we know the address is wrong, which is a fault.
482
+ if (
483
+ result.status === "skipped" ||
484
+ result.status === "unknown" ||
485
+ result.status === "unverified"
486
+ ) {
389
487
  log.debug({ status: result.status }, result.detail);
390
488
  return result;
391
489
  }
@@ -43,6 +43,8 @@ const llmUsage: LlmUsageTelemetryEvent = {
43
43
  // Zero is legitimate (wire bound is min(0)): a subagent can be spawned
44
44
  // before the parent's first real user turn.
45
45
  parent_turn_index: 0,
46
+ subagent_role: "advisor",
47
+ subagent_spawn_mode: "advisor_consult",
46
48
  provider: "anthropic",
47
49
  model: "claude-fable-5",
48
50
  input_tokens: 1200,
@@ -157,10 +157,15 @@ export function activationStepIndex(stepName: ActivationStepName): number {
157
157
  * `funnel_version` so the id stays stable across a version bump — otherwise
158
158
  * queued/offline v1 rows would be keyed with the new binary's version and
159
159
  * stop collapsing with already-ingested v1 rows from the same session.
160
+ *
161
+ * `stepName` is a plain string rather than an `ActivationStepName`: every
162
+ * funnel riding the onboarding substrate shares this keying (the live-voice
163
+ * session funnel is the other one today), and the dedup property depends only
164
+ * on the triple being stable per row, not on which funnel produced it.
160
165
  */
161
166
  export function buildActivationDaemonEventId(
162
167
  sessionId: string,
163
- stepName: ActivationStepName,
168
+ stepName: string,
164
169
  funnelVersion: string = ACTIVATION_FUNNEL_VERSION,
165
170
  ): string {
166
171
  return `${funnelVersion}:${sessionId}:${stepName}`;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Live-voice session telemetry vocabulary: the single source of truth for the
3
+ * step names, funnel version, and end-reason values the live-voice session
4
+ * emitter uses.
5
+ *
6
+ * Rides the existing onboarding telemetry substrate (`type: "onboarding"`),
7
+ * exactly as `activation-funnel.ts` does: the backend stores `step_name` and
8
+ * `funnel_version` as open strings, so this funnel needs no platform serializer
9
+ * and no wire-contract change.
10
+ *
11
+ * **Duration is not a field.** The onboarding event shape carries no numeric
12
+ * slot for it, so the two events below are keyed by the live-voice `sessionId`
13
+ * in `session_id` and the warehouse subtracts their `recorded_at` stamps. That
14
+ * yields exact percentiles rather than the bucketed-string dimension the tips
15
+ * and tour funnels pack into `screen`.
16
+ *
17
+ * **Turn count is not a field either.** It comes from the `turn` telemetry
18
+ * events voice turns already produce, which carry this session's id in their
19
+ * `client` bag (see `voice-session-bridge.ts`), so a turn count is a count of
20
+ * those rows rather than a number restated here.
21
+ *
22
+ * A session that starts and never ends is **censored, not infinite**: a daemon
23
+ * crash or kill emits no end event, so downstream duration math must drop
24
+ * unmatched `started` rows instead of treating them as open-ended sessions.
25
+ */
26
+
27
+ import type { LiveVoiceSessionCloseReason } from "../live-voice/live-voice-session-manager.js";
28
+ import type { LiveVoiceProtocolErrorCode } from "../live-voice/protocol.js";
29
+
30
+ /** Funnel version stamped on every live-voice session event. */
31
+ export const LIVE_VOICE_FUNNEL_VERSION = "live_voice_v1_2026_08";
32
+
33
+ /**
34
+ * Live-voice session funnel steps. `stepName` is the wire value; `stepIndex`
35
+ * is the ordinal position.
36
+ *
37
+ * `sessionStarted` fires for every *attempted* session, before the credential
38
+ * preflight that can reject it. A session that fails to connect is exactly the
39
+ * one worth counting, and gating the start event on `ready` would hide it from
40
+ * the failure rate entirely.
41
+ */
42
+ export const LIVE_VOICE_STEPS = {
43
+ sessionStarted: { stepName: "live_voice_session_started", stepIndex: 0 },
44
+ sessionEnded: { stepName: "live_voice_session_ended", stepIndex: 1 },
45
+ } as const;
46
+
47
+ export type LiveVoiceStepName =
48
+ (typeof LIVE_VOICE_STEPS)[keyof typeof LIVE_VOICE_STEPS]["stepName"];
49
+
50
+ /**
51
+ * How a session ended, stamped as `outcome`.
52
+ *
53
+ * `failed` means the session died on an error: a credential preflight
54
+ * rejection before `ready`, or an utterance-arm failure after it. Everything
55
+ * else, including a dropped socket, is `completed`: the session ran and then
56
+ * stopped, and the close reason on `screen` says how.
57
+ */
58
+ export type LiveVoiceSessionOutcome = "completed" | "failed";
59
+
60
+ /**
61
+ * The `screen` dimension for an ended session: how it closed, plus the
62
+ * protocol error code when a failure is what closed it.
63
+ *
64
+ * Both halves are the daemon's own vocabulary verbatim
65
+ * (`LiveVoiceSessionCloseReason`, `LiveVoiceProtocolErrorCode`) rather than a
66
+ * mapping, so a rename on either side surfaces here as a compile error instead
67
+ * of a silently-empty dashboard facet. The longest pair this can produce is
68
+ * well inside the wire field's 64-char bound.
69
+ */
70
+ export function liveVoiceEndScreen(
71
+ reason: LiveVoiceSessionCloseReason,
72
+ failureCode?: LiveVoiceProtocolErrorCode | null,
73
+ ): string {
74
+ return failureCode ? `ended_${reason}:${failureCode}` : `ended_${reason}`;
75
+ }
@@ -281,6 +281,12 @@ const usageSource = simpleSource(
281
281
  turn_index: e.turnIndex,
282
282
  parent_conversation_id: e.parentConversationId,
283
283
  parent_turn_index: e.parentTurnIndex,
284
+ // Delegated-work decomposition. Every subagent variety shares
285
+ // `llm_call_site = "subagentSpawn"`; these two orthogonal dimensions are
286
+ // what make advisor consults, forks, and regular spawns separable. Null
287
+ // for the vast majority of calls, which are not delegated at all.
288
+ subagent_role: e.subagentRole,
289
+ subagent_spawn_mode: e.subagentSpawnMode,
284
290
  provider: e.provider,
285
291
  model: e.model,
286
292
  input_tokens: e.inputTokens,
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "sourceRepo": "vellum-ai/vellum-assistant-platform",
3
- "sourceSha": "3bb9cb22eb289bd55d4a732e579acd93f20e3114"
3
+ "sourceSha": "16dd2b8e758355014768a09f974ae509dcf5dc0d"
4
4
  }
@@ -95,6 +95,8 @@ export const llmUsageTelemetryEventSchema = z.object({
95
95
  .nullable()
96
96
  .optional(),
97
97
  parent_turn_index: z.number().int().min(0).nullable().optional(),
98
+ subagent_role: z.string().trim().min(1).max(64).nullable().optional(),
99
+ subagent_spawn_mode: z.string().trim().min(1).max(64).nullable().optional(),
98
100
  });
99
101
  export type LlmUsageTelemetryEvent = z.infer<
100
102
  typeof llmUsageTelemetryEventSchema
@@ -114,6 +114,26 @@ export interface LlmUsageTelemetryEvent extends TelemetryEventBase {
114
114
  * parent conversation.
115
115
  */
116
116
  parent_turn_index: number | null;
117
+ /**
118
+ * Role the subagent that owns this event's conversation was spawned with
119
+ * (`researcher`, `builder`, `advisor`; a spawn that named no role records
120
+ * the default, `builder`). An open string set: older rows carry role names
121
+ * nothing emits any more.
122
+ * The advisor is a ROLE, not an `LLMCallSiteEnum` value, so this is the only
123
+ * thing that tells an advisor consult apart from a regular subagent, since both
124
+ * emit under `llm_call_site = "subagentSpawn"`. Null when the LLM call did
125
+ * not run inside a subagent conversation.
126
+ */
127
+ subagent_role: string | null;
128
+ /**
129
+ * How that subagent was spawned, one of the modes described on
130
+ * `SubagentSpawnMode` in `subagent/types.ts`, carried here as an open string
131
+ * set. Orthogonal to `subagent_role`: the role selects the child's
132
+ * capabilities, the spawn mode selects its context inheritance and lifecycle
133
+ * (a fork's inherited transcript dominates its input tokens regardless of
134
+ * role). Null under the same condition as `subagent_role`.
135
+ */
136
+ subagent_spawn_mode: string | null;
117
137
  provider: string;
118
138
  model: string;
119
139
  input_tokens: number;
@@ -164,10 +184,9 @@ export interface TurnTelemetryClientInfo {
164
184
  browser_version?: string;
165
185
  /**
166
186
  * OS surface reported by the client at message time
167
- * ("web" | "ios" | "macos" | "android"). The web, iOS, and macOS apps all
168
- * run the same web renderer and report `interface_id: "web"` (the
169
- * transport surface, which host-proxy capability gating keys off), so
170
- * this field is the only per-platform attribution in turn telemetry.
187
+ * ("web" | "ios" | "macos" | "windows" | "android"). Browser, mobile, and
188
+ * desktop apps all run the same renderer and report `interface_id: "web"`.
189
+ * This field is the only per-platform attribution in turn telemetry.
171
190
  */
172
191
  os?: string;
173
192
  /**
@@ -260,6 +260,8 @@ type UnreportedUsageEventFixture = UsageEvent & {
260
260
  turnIndex: number | null;
261
261
  parentConversationId: string | null;
262
262
  parentTurnIndex: number | null;
263
+ subagentRole: string | null;
264
+ subagentSpawnMode: string | null;
263
265
  };
264
266
 
265
267
  function makeUsageEvent(
@@ -290,6 +292,8 @@ function makeUsageEvent(
290
292
  turnIndex: 1,
291
293
  parentConversationId: null,
292
294
  parentTurnIndex: null,
295
+ subagentRole: null,
296
+ subagentSpawnMode: null,
293
297
  llmCallCount: 1,
294
298
  ...overrides,
295
299
  };
@@ -48,7 +48,7 @@ const CASES: {
48
48
  fork: "deliberate `=== true` coercion owns malformed values",
49
49
  send_result_to_user:
50
50
  "deliberate `=== true` / `!== false` coercions own malformed values",
51
- role: "SubagentManager.spawn's 'Invalid subagent role' error (test-asserted) owns non-enum values",
51
+ role: "resolveSubagentRole is total: every string lands on a type, so there is nothing to reject",
52
52
  },
53
53
  },
54
54
  {
@@ -1,6 +1,7 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import type {
4
+ QuestionPromptOutcome,
4
5
  QuestionPromptParams,
5
6
  QuestionPromptResult,
6
7
  } from "../../permissions/question-prompter.js";
@@ -25,16 +26,22 @@ function setNextResult(result: QuestionPromptResult): void {
25
26
 
26
27
  mock.module("../../permissions/question-prompter.js", () => ({
27
28
  QuestionPrompter: class {
28
- async prompt(params: QuestionPromptParams): Promise<QuestionPromptResult> {
29
+ async prompt(params: QuestionPromptParams): Promise<QuestionPromptOutcome> {
29
30
  calls.push(params);
30
- return nextResult;
31
+ // Mirror the real prompter: it mints the request id and assigns the
32
+ // per-question `q1..qN` ids, then returns them alongside the resolution.
33
+ return {
34
+ requestId: "req-stub",
35
+ questions: params.questions.map((q, i) => ({ id: `q${i + 1}`, ...q })),
36
+ ...nextResult,
37
+ };
31
38
  }
32
39
  },
33
40
  }));
34
41
 
35
42
  // Import after the mock so the tool's `import { QuestionPrompter }` binds
36
43
  // to the stub class above.
37
- const { askQuestionTool, formatQuestionsAsTextFallback } =
44
+ const { askQuestionTool, formatQuestionsAsTextFallback, toAnsweredQuestion } =
38
45
  await import("./ask-question-tool.js");
39
46
 
40
47
  type PromptParams = QuestionPromptParams;
@@ -710,3 +717,111 @@ describe("askQuestionTool definition (batched schema)", () => {
710
717
  expect(schema.properties.options).toBeUndefined();
711
718
  });
712
719
  });
720
+
721
+ describe("answered-question record", () => {
722
+ test("carries the option the user chose, keyed to the questions as asked", async () => {
723
+ setNextResult({
724
+ entries: [{ questionId: "q1", decision: "option", optionId: "b" }],
725
+ overall: "completed",
726
+ });
727
+
728
+ const result = await askQuestionTool.execute(validInput, makeContext());
729
+
730
+ expect(result.answeredQuestion).toEqual({
731
+ requestId: "req-stub",
732
+ questions: [{ id: "q1", ...singleQ }],
733
+ responses: [{ questionId: "q1", decision: "option", optionId: "b" }],
734
+ overall: "completed",
735
+ });
736
+ });
737
+
738
+ test("carries a free-text answer", async () => {
739
+ setNextResult({
740
+ entries: [{ questionId: "q1", decision: "free_text", text: "Cherry" }],
741
+ overall: "completed",
742
+ });
743
+
744
+ const result = await askQuestionTool.execute(validInput, makeContext());
745
+
746
+ expect(result.answeredQuestion?.responses).toEqual([
747
+ { questionId: "q1", decision: "free_text", text: "Cherry" },
748
+ ]);
749
+ });
750
+
751
+ test("records a closed card as every question skipped", async () => {
752
+ setNextResult({
753
+ entries: [
754
+ { questionId: "q1", decision: "skipped" },
755
+ { questionId: "q2", decision: "skipped" },
756
+ ],
757
+ overall: "closed",
758
+ });
759
+
760
+ const result = await askQuestionTool.execute(
761
+ { questions: [singleQ, singleQ] },
762
+ makeContext(),
763
+ );
764
+
765
+ expect(result.answeredQuestion?.overall).toBe("closed");
766
+ expect(result.answeredQuestion?.responses).toEqual([
767
+ { questionId: "q1", decision: "skipped" },
768
+ { questionId: "q2", decision: "skipped" },
769
+ ]);
770
+ });
771
+
772
+ test("keeps per-question skips inside an otherwise completed batch", async () => {
773
+ setNextResult({
774
+ entries: [
775
+ { questionId: "q1", decision: "option", optionId: "a" },
776
+ { questionId: "q2", decision: "skipped" },
777
+ ],
778
+ overall: "completed",
779
+ });
780
+
781
+ const result = await askQuestionTool.execute(
782
+ { questions: [singleQ, singleQ] },
783
+ makeContext(),
784
+ );
785
+
786
+ expect(result.answeredQuestion?.responses).toEqual([
787
+ { questionId: "q1", decision: "option", optionId: "a" },
788
+ { questionId: "q2", decision: "skipped" },
789
+ ]);
790
+ });
791
+
792
+ test("records nothing for a prompt that timed out or was aborted", async () => {
793
+ for (const overall of ["timed_out", "aborted"] as const) {
794
+ setNextResult({
795
+ entries: [{ questionId: "q1", decision: overall }],
796
+ overall,
797
+ });
798
+
799
+ const result = await askQuestionTool.execute(validInput, makeContext());
800
+
801
+ expect(result.isError).toBe(true);
802
+ expect(result.answeredQuestion).toBeUndefined();
803
+ }
804
+ });
805
+
806
+ test("records nothing when the channel degrades to the text fallback", async () => {
807
+ const result = await askQuestionTool.execute(
808
+ validInput,
809
+ makeContext({ supportsDynamicUi: false }),
810
+ );
811
+
812
+ // No card was ever shown, so there is no answered state to keep.
813
+ expect(result.answeredQuestion).toBeUndefined();
814
+ expect(calls).toHaveLength(0);
815
+ });
816
+
817
+ test("toAnsweredQuestion is undefined for non-user outcomes", () => {
818
+ expect(
819
+ toAnsweredQuestion({
820
+ requestId: "req-1",
821
+ questions: [],
822
+ entries: [],
823
+ overall: "timed_out",
824
+ }),
825
+ ).toBeUndefined();
826
+ });
827
+ });
@@ -1,6 +1,13 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { QuestionPrompter } from "../../permissions/question-prompter.js";
3
+ import type {
4
+ AnsweredQuestion,
5
+ AnsweredQuestionResponse,
6
+ } from "../../api/events/question-answered.js";
7
+ import {
8
+ QuestionPrompter,
9
+ type QuestionPromptOutcome,
10
+ } from "../../permissions/question-prompter.js";
4
11
  import { RiskLevel } from "../../permissions/types.js";
5
12
  import {
6
13
  invalidToolInputResult,
@@ -161,6 +168,47 @@ export function formatQuestionsAsTextFallback(
161
168
  return [header, "", blocks.join("\n\n")].join("\n");
162
169
  }
163
170
 
171
+ /**
172
+ * Project a settled prompt into the durable answered-question record the
173
+ * daemon persists on the tool call. Returns undefined for outcomes that carry
174
+ * no user decision (timeout, abort), which have nothing to show in history.
175
+ */
176
+ export function toAnsweredQuestion(
177
+ outcome: QuestionPromptOutcome,
178
+ ): AnsweredQuestion | undefined {
179
+ if (outcome.overall !== "completed" && outcome.overall !== "closed") {
180
+ return undefined;
181
+ }
182
+ const responses: AnsweredQuestionResponse[] = [];
183
+ for (const entry of outcome.entries) {
184
+ if (entry.decision === "option") {
185
+ responses.push({
186
+ questionId: entry.questionId,
187
+ decision: "option",
188
+ optionId: entry.optionId,
189
+ });
190
+ } else if (entry.decision === "free_text") {
191
+ responses.push({
192
+ questionId: entry.questionId,
193
+ decision: "free_text",
194
+ text: entry.text,
195
+ });
196
+ } else {
197
+ // A `completed` batch can still carry per-question skips, and a `closed`
198
+ // card reports every question as skipped. `timed_out` / `aborted` cannot
199
+ // reach here: they only ever appear on a batch-wide outcome of the same
200
+ // name, which is filtered above.
201
+ responses.push({ questionId: entry.questionId, decision: "skipped" });
202
+ }
203
+ }
204
+ return {
205
+ requestId: outcome.requestId,
206
+ questions: outcome.questions,
207
+ responses,
208
+ overall: outcome.overall,
209
+ };
210
+ }
211
+
164
212
  // ── Tool ────────────────────────────────────────────────────────────
165
213
 
166
214
  /**
@@ -254,15 +302,21 @@ export const askQuestionTool = {
254
302
  return `${prefix} Skipped`;
255
303
  });
256
304
 
305
+ // The user's decision belongs in the transcript, not just in the string
306
+ // handed to the model: the daemon persists this on the tool call so the
307
+ // answered card survives a conversation switch, reload, or history reopen.
308
+ const answeredQuestion = toAnsweredQuestion(result);
309
+
257
310
  switch (result.overall) {
258
311
  case "completed":
259
- return { content: lines.join("\n"), isError: false };
312
+ return { content: lines.join("\n"), isError: false, answeredQuestion };
260
313
  case "closed": {
261
314
  const summary =
262
315
  "User closed the question card without answering. All questions skipped.";
263
316
  return {
264
317
  content: [summary, ...lines].join("\n"),
265
318
  isError: false,
319
+ answeredQuestion,
266
320
  };
267
321
  }
268
322
  case "timed_out":
@@ -1410,7 +1410,7 @@ export async function executeBrowserScreenshot(
1410
1410
  // capture of a tall or high-DPI page can exceed Anthropic's 5 MB
1411
1411
  // per-image cap, which would otherwise persist into the conversation
1412
1412
  // history inside this tool_result and reject every subsequent turn.
1413
- const { data: base64Data, mediaType } = optimizeImageForTransport(
1413
+ const { data: base64Data, mediaType } = await optimizeImageForTransport(
1414
1414
  rawBase64,
1415
1415
  "image/jpeg",
1416
1416
  );
@@ -48,11 +48,14 @@ export async function executeCallStart(
48
48
 
49
49
  const requestedPhone = normalizePhoneNumber(parsed.phone_number);
50
50
  if (requestedPhone) {
51
- const activeVoiceVerification = await findActiveSession("phone");
52
- const verificationDestination =
53
- activeVoiceVerification?.destinationAddress ??
54
- activeVoiceVerification?.expectedPhoneE164;
55
- if (verificationDestination === requestedPhone) {
51
+ // Scoped to the number being called. Voice sessions bind
52
+ // `expectedExternalUserId` to the E.164 number, and several can be live
53
+ // at once, so reading the latest and comparing would miss a verification
54
+ // for this number whenever another one started more recently.
55
+ const activeVoiceVerification = await findActiveSession("phone", {
56
+ expectedExternalUserId: requestedPhone,
57
+ });
58
+ if (activeVoiceVerification) {
56
59
  return {
57
60
  content: [
58
61
  "Error: A guardian voice verification call is already active for this number.",
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import {
4
- batchSetDisplayOrders,
4
+ batchSetConversationPlacement,
5
5
  getConversation,
6
6
  getDisplayMetaForConversations,
7
7
  } from "../../persistence/conversation-crud.js";
@@ -73,15 +73,7 @@ export async function executeConversationMoveToGroup(
73
73
  };
74
74
  }
75
75
 
76
- // Preserve the conversation's manual sort slot across the move; only the
77
- // group assignment changes.
78
- batchSetDisplayOrders([
79
- {
80
- id: conversationId,
81
- displayOrder: meta?.displayOrder ?? null,
82
- groupId: group.id,
83
- },
84
- ]);
76
+ batchSetConversationPlacement([{ id: conversationId, groupId: group.id }]);
85
77
  publishConversationListAndMetadataChanged("reordered", [conversationId]);
86
78
 
87
79
  const notes: string[] = [];
@@ -93,9 +85,15 @@ export async function executeConversationMoveToGroup(
93
85
  `Moving into ${group.name} demotes it out of the Recents listing.`,
94
86
  );
95
87
  }
96
- if (conversation.conversationType !== "standard") {
88
+ // Only Pinned and custom groups surface a hidden conversation. Recents is a
89
+ // removal target, and the Scheduled/Background groups are demotions, so
90
+ // claiming any of them made the conversation visible would misreport the
91
+ // outcome to the model and the user.
92
+ const surfacesConversation =
93
+ group.id === "system:pinned" || !group.id.startsWith("system:");
94
+ if (conversation.conversationType !== "standard" && surfacesConversation) {
97
95
  notes.push(
98
- `Note: this is a ${conversation.conversationType} conversation, which stays hidden from the sidebar regardless of group.`,
96
+ `Note: this is a ${conversation.conversationType} conversation, so filing it here also surfaces it into the sidebar.`,
99
97
  );
100
98
  }
101
99