@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
@@ -318,6 +318,8 @@ const deleteMessageByIdMock = mock(() => ({
318
318
  deletedSummaryIds: [],
319
319
  }));
320
320
  const reserveMessageMock = mock(async () => ({ id: "msg-reserve" }));
321
+ /** Persisted rows the loop reads back. Empty unless a test seeds one. */
322
+ let mockStoredMessages: unknown[] = [];
321
323
  const updateMessageContentMock = mock(() => {});
322
324
  const finalizeMessageContentMock = mock(() => {});
323
325
  const addMessageMock = mock(() => ({ id: "mock-msg-id" }));
@@ -328,7 +330,7 @@ mock.module("../persistence/conversation-crud.js", () => ({
328
330
  updateConversationUsage: () => {},
329
331
  updateMessageMetadata: updateMessageMetadataMock,
330
332
  setConversationHistoryStrippedAt: setConversationHistoryStrippedAtMock,
331
- getMessages: () => [],
333
+ getMessages: () => mockStoredMessages,
332
334
  getConversation: () => mockConversationRow,
333
335
  provenanceFromTrustContext: () => ({
334
336
  source: "user",
@@ -673,6 +675,7 @@ import {
673
675
  applyCompactionResult,
674
676
  runAgentLoopImpl,
675
677
  } from "../daemon/conversation-agent-loop.js";
678
+ import { settleTurnTail } from "../daemon/turn-tail-chain.js";
676
679
  import {
677
680
  createMockProvider,
678
681
  type ScriptedResponse,
@@ -780,6 +783,7 @@ function makeCtx(
780
783
  channelCapabilities: undefined,
781
784
  commandIntent: undefined,
782
785
  trustContext: undefined,
786
+ toolsDisabledDepth: 0,
783
787
 
784
788
  allowedToolNames: undefined,
785
789
  preactivatedSkillIds: undefined,
@@ -862,6 +866,24 @@ function makeCtx(
862
866
  return ctx;
863
867
  }
864
868
 
869
+ /**
870
+ * What `classifyConversationError` returns for a daily-credit-limit 402 (see
871
+ * `dailyLimitClassification` in conversation-error.ts). Its `userMessage` is
872
+ * banner voice; the loop swaps in {@link DAILY_LIMIT_ASSISTANT_REPLY} for the
873
+ * transcript row.
874
+ */
875
+ const DAILY_LIMIT_CLASSIFICATION = {
876
+ code: "PROVIDER_BILLING",
877
+ userMessage:
878
+ "You've hit your daily credit limit. Raise the limit in Billing settings to keep going today.",
879
+ retryable: false,
880
+ errorCategory: "daily_limit_reached",
881
+ };
882
+
883
+ /** Mirrors `DAILY_LIMIT_REACHED_ASSISTANT_REPLY` in conversation-agent-loop.ts. */
884
+ const DAILY_LIMIT_ASSISTANT_REPLY =
885
+ "I had to stop because you hit your daily credit limit. Raise the limit in Settings → Billing and we can pick up where we left off, or I can continue once it resets.";
886
+
865
887
  type CompactionResult = Parameters<typeof applyCompactionResult>[1];
866
888
 
867
889
  function makeCompactionResult(
@@ -931,7 +953,9 @@ beforeEach(() => {
931
953
  getSlackCompactionWatermarkForPrefixMock.mockClear();
932
954
  deleteMessageByIdMock.mockClear();
933
955
  reserveMessageMock.mockClear();
956
+ mockStoredMessages = [];
934
957
  updateMessageContentMock.mockClear();
958
+ updateMessageContentMock.mockImplementation(() => {});
935
959
  finalizeMessageContentMock.mockClear();
936
960
  rmSync(inflightDir(), { recursive: true, force: true });
937
961
  addMessageMock.mockClear();
@@ -1183,6 +1207,92 @@ describe("session-agent-loop", () => {
1183
1207
  runAgentLoopImpl(ctx, "hello", "msg-1", () => {}),
1184
1208
  ).rejects.toThrow("runAgentLoop called without prior persistUserMessage");
1185
1209
  });
1210
+
1211
+ test("defers workspace Git work when tools are disabled", async () => {
1212
+ const ensureInitialized = mock(async () => {});
1213
+ const commitTurnChanges = mock(async () => {});
1214
+ const ctx = makeCtx({
1215
+ toolsDisabledDepth: 1,
1216
+ getWorkspaceGitService: () => ({ ensureInitialized }),
1217
+ commitTurnChanges,
1218
+ });
1219
+
1220
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
1221
+
1222
+ expect(ensureInitialized).not.toHaveBeenCalled();
1223
+ expect(commitTurnChanges).not.toHaveBeenCalled();
1224
+
1225
+ await new Promise<void>((resolve) => setImmediate(resolve));
1226
+
1227
+ expect(commitTurnChanges).toHaveBeenCalledTimes(1);
1228
+ });
1229
+
1230
+ test("leaves a deferred commit to an immediate follow-up turn", async () => {
1231
+ const commitTurnChanges = mock(async () => {});
1232
+ const ctx = makeCtx({
1233
+ toolsDisabledDepth: 1,
1234
+ commitTurnChanges,
1235
+ });
1236
+
1237
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
1238
+ ctx.setProcessing(true);
1239
+ await new Promise<void>((resolve) => setImmediate(resolve));
1240
+
1241
+ expect(commitTurnChanges).not.toHaveBeenCalled();
1242
+ ctx.setProcessing(false);
1243
+ });
1244
+
1245
+ test("initializes workspace Git before hooks when tools can run", async () => {
1246
+ let initialized = false;
1247
+ const ensureInitialized = mock(async () => {
1248
+ initialized = true;
1249
+ });
1250
+ const commitTurnChanges = mock(async () => {});
1251
+ const initializedDuringHook: boolean[] = [];
1252
+ registerPlugin({
1253
+ manifest: {
1254
+ name: "test-workspace-git-ready-before-hook",
1255
+ version: "1.0.0",
1256
+ },
1257
+ hooks: {
1258
+ "user-prompt-submit": async () => {
1259
+ initializedDuringHook.push(initialized);
1260
+ },
1261
+ },
1262
+ });
1263
+ const ctx = makeCtx({
1264
+ getWorkspaceGitService: () => ({ ensureInitialized }),
1265
+ commitTurnChanges,
1266
+ });
1267
+
1268
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
1269
+
1270
+ expect(ensureInitialized).toHaveBeenCalledTimes(1);
1271
+ expect(commitTurnChanges).toHaveBeenCalledTimes(1);
1272
+ expect(initializedDuringHook).toEqual([true]);
1273
+ });
1274
+
1275
+ test("continues a tool-capable turn when workspace Git initialization fails", async () => {
1276
+ const ensureInitialized = mock(async () => {
1277
+ throw new Error("simulated Git initialization failure");
1278
+ });
1279
+ const events: AssistantEvent[] = [];
1280
+ const ctx = makeCtx({
1281
+ getWorkspaceGitService: () => ({ ensureInitialized }),
1282
+ });
1283
+
1284
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (event) =>
1285
+ events.push(event),
1286
+ );
1287
+
1288
+ expect(ensureInitialized).toHaveBeenCalledTimes(1);
1289
+ expect(events.some((event) => event.type === "message_complete")).toBe(
1290
+ true,
1291
+ );
1292
+ expect(events.some((event) => event.type === "conversation_error")).toBe(
1293
+ false,
1294
+ );
1295
+ });
1186
1296
  });
1187
1297
 
1188
1298
  describe("disk pressure injection context", () => {
@@ -1971,6 +2081,76 @@ describe("session-agent-loop", () => {
1971
2081
  expect(ctx.pendingSurfaceActions.has("stale-form-1")).toBe(false);
1972
2082
  });
1973
2083
 
2084
+ test("withholds the dismissal event and keeps the surface pending when its persisted write fails", async () => {
2085
+ const events: AssistantEvent[] = [];
2086
+
2087
+ const ctx = makeCtx();
2088
+ ctx.pendingSurfaceActions.set("stale-table-2", { surfaceType: "table" });
2089
+ mockStoredMessages = [
2090
+ {
2091
+ id: "msg-with-stale-surface",
2092
+ conversationId: "conv-1",
2093
+ role: "assistant",
2094
+ content: [{ type: "ui_surface", surfaceId: "stale-table-2" }],
2095
+ createdAt: 0,
2096
+ metadata: null,
2097
+ },
2098
+ ];
2099
+ updateMessageContentMock.mockImplementationOnce(() => {
2100
+ throw new Error("database is locked");
2101
+ });
2102
+
2103
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg), {
2104
+ isUserMessage: true,
2105
+ });
2106
+
2107
+ // Announcing a dismissal the DB still holds as pending leaves the client
2108
+ // showing a card the next history reseed reverts.
2109
+ expect(
2110
+ events.filter((e) => e.type === "ui_surface_complete"),
2111
+ ).toHaveLength(0);
2112
+ // The card stays live on the client, so the daemon must keep treating it
2113
+ // as pending; the sweep retries on the next user message.
2114
+ expect(ctx.pendingSurfaceActions.has("stale-table-2")).toBe(true);
2115
+ });
2116
+
2117
+ test("dismisses the remaining surfaces when one write fails", async () => {
2118
+ const events: AssistantEvent[] = [];
2119
+
2120
+ const ctx = makeCtx();
2121
+ ctx.pendingSurfaceActions.set("stale-table-3", { surfaceType: "table" });
2122
+ ctx.pendingSurfaceActions.set("stale-form-3", { surfaceType: "form" });
2123
+ mockStoredMessages = [
2124
+ {
2125
+ id: "msg-with-stale-surfaces",
2126
+ conversationId: "conv-1",
2127
+ role: "assistant",
2128
+ content: [
2129
+ { type: "ui_surface", surfaceId: "stale-table-3" },
2130
+ { type: "ui_surface", surfaceId: "stale-form-3" },
2131
+ ],
2132
+ createdAt: 0,
2133
+ metadata: null,
2134
+ },
2135
+ ];
2136
+ updateMessageContentMock.mockImplementationOnce(() => {
2137
+ throw new Error("database is locked");
2138
+ });
2139
+
2140
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg), {
2141
+ isUserMessage: true,
2142
+ });
2143
+
2144
+ // Retaining the failed entry must not stall the sweep: deleting the
2145
+ // current key while iterating the map leaves later keys reachable.
2146
+ const completed = events
2147
+ .filter((e) => e.type === "ui_surface_complete")
2148
+ .map((e) => (e as { surfaceId: string }).surfaceId);
2149
+ expect(completed).toEqual(["stale-form-3"]);
2150
+ expect(ctx.pendingSurfaceActions.has("stale-table-3")).toBe(true);
2151
+ expect(ctx.pendingSurfaceActions.has("stale-form-3")).toBe(false);
2152
+ });
2153
+
1974
2154
  test("does not auto-complete surfaces when request is a surface action", async () => {
1975
2155
  const events: AssistantEvent[] = [];
1976
2156
 
@@ -2277,6 +2457,134 @@ describe("session-agent-loop", () => {
2277
2457
  });
2278
2458
  });
2279
2459
 
2460
+ test("persists assistant-voice daily-limit copy on a first-call rejection", async () => {
2461
+ mockConversationErrorClassification = DAILY_LIMIT_CLASSIFICATION;
2462
+
2463
+ const ctx = makeCtx({
2464
+ loopProvider: {
2465
+ name: "mock-provider",
2466
+ async sendMessage() {
2467
+ throw new Error("Payment Required");
2468
+ },
2469
+ } as unknown as Provider,
2470
+ });
2471
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
2472
+
2473
+ expect(addMessageMock).toHaveBeenCalledTimes(1);
2474
+ const addCall = addMessageMock.mock.calls[0] as unknown as [
2475
+ string,
2476
+ string,
2477
+ string,
2478
+ { metadata?: Record<string, unknown> },
2479
+ ];
2480
+ expect(addCall[1]).toBe("assistant");
2481
+ // Like the credits-exhausted row, the daily-limit row re-enters LLM
2482
+ // history and renders as assistant speech, so it carries first-person
2483
+ // copy rather than the banner-voice classification text.
2484
+ const persistedBlocks = JSON.parse(addCall[2]) as Array<{
2485
+ type: string;
2486
+ text: string;
2487
+ }>;
2488
+ expect(persistedBlocks[0]?.text).toBe(DAILY_LIMIT_ASSISTANT_REPLY);
2489
+ expect(addCall[3]?.metadata).toMatchObject({
2490
+ messageKind: "provider_error",
2491
+ providerErrorCode: "PROVIDER_BILLING",
2492
+ providerErrorCategory: "daily_limit_reached",
2493
+ });
2494
+ });
2495
+
2496
+ test("persists the synthetic error row when a daily-limit error kills the run mid-turn", async () => {
2497
+ // Mid-turn shape: the run already carries assistant `tool_use` messages
2498
+ // from the completed tool round-trip, and only the failing follow-up
2499
+ // call lacks a reply. The synthetic error row must persist for this
2500
+ // shape too, so a reload explains why the assistant stopped instead of
2501
+ // ending on a bare tool call.
2502
+ reserveMessageMock
2503
+ .mockImplementationOnce(async () => ({ id: "msg-reserve-1" }))
2504
+ .mockImplementationOnce(async () => ({ id: "msg-reserve-2" }))
2505
+ .mockImplementationOnce(async () => ({ id: "msg-reserve-3" }));
2506
+ mockConversationErrorClassification = DAILY_LIMIT_CLASSIFICATION;
2507
+
2508
+ // GIVEN a run whose first call asks for a tool, the tool succeeds, and
2509
+ // the follow-up call is rejected with a daily-limit 402.
2510
+ const ctx = makeCtx({
2511
+ providerResponses: [
2512
+ toolUseResponse("tu-1", "file_read", {}),
2513
+ new Error("Payment Required"),
2514
+ ],
2515
+ loopTools: [
2516
+ {
2517
+ name: "file_read",
2518
+ description: "Read a file",
2519
+ input_schema: { type: "object", properties: {} },
2520
+ },
2521
+ ],
2522
+ toolExecutor: async () => ({ content: "file content", isError: false }),
2523
+ } as unknown as Partial<Conversation>);
2524
+
2525
+ await runAgentLoopImpl(ctx, "hello", "msg-user-daily", () => {});
2526
+
2527
+ // (a) The synthetic provider-error row is persisted with the
2528
+ // assistant-voice daily-limit copy and the classification metadata.
2529
+ expect(addMessageMock).toHaveBeenCalledTimes(1);
2530
+ const addCall = addMessageMock.mock.calls[0] as unknown as [
2531
+ string,
2532
+ string,
2533
+ string,
2534
+ { metadata?: Record<string, unknown> },
2535
+ ];
2536
+ expect(addCall[1]).toBe("assistant");
2537
+ const persistedBlocks = JSON.parse(addCall[2]) as Array<{
2538
+ type: string;
2539
+ text: string;
2540
+ }>;
2541
+ expect(persistedBlocks[0]?.text).toBe(DAILY_LIMIT_ASSISTANT_REPLY);
2542
+ expect(addCall[3]?.metadata).toMatchObject({
2543
+ messageKind: "provider_error",
2544
+ providerErrorCode: "PROVIDER_BILLING",
2545
+ providerErrorCategory: "daily_limit_reached",
2546
+ });
2547
+
2548
+ // (b) The empty row the failing call reserved at `llm_call_started` is
2549
+ // dropped, so the transcript carries the error row instead of a stranded
2550
+ // blank bubble. The failing call reserves last (after the first call's
2551
+ // assistant row and the grouped tool-result row).
2552
+ expect(deleteMessageByIdMock).toHaveBeenCalledTimes(1);
2553
+ const deleteCall = deleteMessageByIdMock.mock.calls[0] as unknown as [
2554
+ string,
2555
+ ];
2556
+ expect(deleteCall[0]).toBe("msg-reserve-3");
2557
+
2558
+ // (c) The turn is stamped failed with the classified code.
2559
+ const outcomeStamps = (
2560
+ updateMessageMetadataMock.mock.calls as unknown as Array<
2561
+ [string, Record<string, unknown>]
2562
+ >
2563
+ ).filter((call) => call[1]?.turnOutcome !== undefined);
2564
+ expect(outcomeStamps).toHaveLength(1);
2565
+ expect(outcomeStamps[0]?.[0]).toBe("msg-user-daily");
2566
+ expect(outcomeStamps[0]?.[1]).toMatchObject({
2567
+ turnOutcome: "failed",
2568
+ turnFailureCode: "PROVIDER_BILLING",
2569
+ });
2570
+
2571
+ // (d) History ends tool_use -> tool_result -> error text, so the row
2572
+ // never lands between a tool_use and its tool_result on replay.
2573
+ const tail = ctx.messages.slice(-3) as Array<{
2574
+ role: string;
2575
+ content: Array<{ type: string; text?: string }>;
2576
+ }>;
2577
+ expect(tail[0]?.role).toBe("assistant");
2578
+ expect(tail[0]?.content[0]?.type).toBe("tool_use");
2579
+ expect(tail[1]?.role).toBe("user");
2580
+ expect(tail[1]?.content[0]?.type).toBe("tool_result");
2581
+ expect(tail[2]?.role).toBe("assistant");
2582
+ expect(tail[2]?.content[0]).toEqual({
2583
+ type: "text",
2584
+ text: DAILY_LIMIT_ASSISTANT_REPLY,
2585
+ });
2586
+ });
2587
+
2280
2588
  test("does not persist managed credential refresh failures as assistant text", async () => {
2281
2589
  mockConversationErrorClassification = {
2282
2590
  code: "MANAGED_KEY_INVALID",
@@ -2435,6 +2743,128 @@ describe("session-agent-loop", () => {
2435
2743
  });
2436
2744
  });
2437
2745
 
2746
+ describe("detached deferred turn tail", () => {
2747
+ const reservedAssistantRow = {
2748
+ id: "msg-reserve",
2749
+ conversationId: "test-conv",
2750
+ createdAt: 1_700_000_000_001,
2751
+ role: "assistant" as const,
2752
+ content: "[]",
2753
+ metadata: null,
2754
+ };
2755
+
2756
+ test("frees the conversation before the deferred tail finishes, and serializes tails", async () => {
2757
+ mockMessageById = reservedAssistantRow;
2758
+ // A tail effect that never settles on its own stands in for the
2759
+ // production case this guards: memory indexing that takes tens of
2760
+ // seconds against a slow embedding provider.
2761
+ let releaseFirstIndex: (() => void) | undefined;
2762
+ const firstIndexHangs = new Promise<void>((resolve) => {
2763
+ releaseFirstIndex = resolve;
2764
+ });
2765
+ indexMessageNowMock.mockImplementationOnce(async () => {
2766
+ await firstIndexHangs;
2767
+ return { indexedSegments: 0, enqueuedJobs: 0 };
2768
+ });
2769
+
2770
+ const drainReasons: string[] = [];
2771
+ const ctx = makeCtx({
2772
+ providerResponses: [textResponse("first"), textResponse("second")],
2773
+ drainQueue: async (reason?: string) => {
2774
+ drainReasons.push(reason ?? "loop_complete");
2775
+ },
2776
+ });
2777
+
2778
+ await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
2779
+
2780
+ // The tail is still parked inside the indexer…
2781
+ expect(indexMessageNowMock).toHaveBeenCalledTimes(1);
2782
+ // …yet the conversation is already released and the queue already
2783
+ // kicked, so a send arriving now runs instead of being queued.
2784
+ expect(ctx.isProcessing()).toBe(false);
2785
+ expect(drainReasons).toEqual(["loop_complete"]);
2786
+
2787
+ // A follow-up turn runs to completion against the still-hung tail.
2788
+ ctx.abortController = new AbortController();
2789
+ ctx.setProcessing(true);
2790
+ await runAgentLoopImpl(ctx, "again", "msg-2", () => {});
2791
+ expect(ctx.isProcessing()).toBe(false);
2792
+ expect(drainReasons).toEqual(["loop_complete", "loop_complete"]);
2793
+
2794
+ // The second turn's tail is chained behind the first, so it has not run
2795
+ // its own indexing yet.
2796
+ expect(indexMessageNowMock).toHaveBeenCalledTimes(1);
2797
+
2798
+ releaseFirstIndex?.();
2799
+ await settleTurnTail(ctx.conversationId);
2800
+ expect(indexMessageNowMock).toHaveBeenCalledTimes(2);
2801
+ });
2802
+
2803
+ test("a throwing tail effect neither latches the lock nor skips the drain", async () => {
2804
+ mockMessageById = reservedAssistantRow;
2805
+ indexMessageNowMock.mockImplementationOnce(async () => {
2806
+ throw new Error("indexer exploded");
2807
+ });
2808
+
2809
+ const drainReasons: string[] = [];
2810
+ const ctx = makeCtx({
2811
+ providerResponses: [textResponse("first")],
2812
+ drainQueue: async (reason?: string) => {
2813
+ drainReasons.push(reason ?? "loop_complete");
2814
+ },
2815
+ });
2816
+
2817
+ await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
2818
+ // The chain settles rather than rejecting, so the throw can neither
2819
+ // surface as an unhandled rejection nor poison a later turn's tail.
2820
+ await settleTurnTail(ctx.conversationId);
2821
+
2822
+ expect(ctx.isProcessing()).toBe(false);
2823
+ expect(drainReasons).toEqual(["loop_complete"]);
2824
+ // The tail continued past the failure: attention projection still ran.
2825
+ expect(projectAssistantMessageMock).toHaveBeenCalledTimes(1);
2826
+ });
2827
+
2828
+ test("the queue drain waits for the turn-boundary commit", async () => {
2829
+ // `commitTurnChanges` attributes the working tree's changes to the turn
2830
+ // that just finished, so a queued turn must not start writing files
2831
+ // while it runs. The lock release above frees direct sends immediately;
2832
+ // the drain is what has to wait.
2833
+ let commitReached: (() => void) | undefined;
2834
+ const commitStarted = new Promise<void>((resolve) => {
2835
+ commitReached = resolve;
2836
+ });
2837
+ let releaseCommit: (() => void) | undefined;
2838
+ const commitHangs = new Promise<void>((resolve) => {
2839
+ releaseCommit = resolve;
2840
+ });
2841
+
2842
+ const drainReasons: string[] = [];
2843
+ const ctx = makeCtx({
2844
+ providerResponses: [textResponse("first")],
2845
+ commitTurnChanges: async () => {
2846
+ commitReached?.();
2847
+ await commitHangs;
2848
+ },
2849
+ drainQueue: async (reason?: string) => {
2850
+ drainReasons.push(reason ?? "loop_complete");
2851
+ },
2852
+ });
2853
+
2854
+ const loop = runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
2855
+ await commitStarted;
2856
+
2857
+ // Released for direct sends…
2858
+ expect(ctx.isProcessing()).toBe(false);
2859
+ // …but the queue is untouched until the commit settles.
2860
+ expect(drainReasons).toEqual([]);
2861
+
2862
+ releaseCommit?.();
2863
+ await loop;
2864
+ expect(drainReasons).toEqual(["loop_complete"]);
2865
+ });
2866
+ });
2867
+
2438
2868
  describe("B3 pre-allocation: indexing + cleanup", () => {
2439
2869
  test("handleMessageComplete indexes and projects the finalized assistant row", async () => {
2440
2870
  // The pre-B3 path inserted assistant rows via `addMessage`, which ran
@@ -2461,6 +2891,9 @@ describe("session-agent-loop", () => {
2461
2891
  providerResponses: [textResponse("indexed reply")],
2462
2892
  });
2463
2893
  await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
2894
+ // The finalize effects run on the detached tail, so wait for the chain
2895
+ // rather than on the loop's own resolution.
2896
+ await settleTurnTail(ctx.conversationId);
2464
2897
 
2465
2898
  // Indexer fired with the reserved row's id + the finalized content.
2466
2899
  expect(indexMessageNowMock).toHaveBeenCalledTimes(1);
@@ -2537,8 +2970,9 @@ describe("session-agent-loop", () => {
2537
2970
  providerResponses: [textResponse("indexed reply")],
2538
2971
  });
2539
2972
  await runAgentLoopImpl(ctx, "hi", "msg-1", (msg) => events.push(msg));
2973
+ await settleTurnTail(ctx.conversationId);
2540
2974
 
2541
- // The deferred indexer runs exactly once, within the turn
2975
+ // The deferred indexer runs exactly once…
2542
2976
  expect(indexMessageNowMock).toHaveBeenCalledTimes(1);
2543
2977
  // …and only after the terminal SSE that re-enables the composer.
2544
2978
  expect(messageCompleteSeenWhenIndexed).toBe(true);
@@ -2561,6 +2995,7 @@ describe("session-agent-loop", () => {
2561
2995
  // GIVEN a real loop that answers with a single finalized assistant turn
2562
2996
  const ctx = makeCtx({ providerResponses: [textResponse("quiet")] });
2563
2997
  await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
2998
+ await settleTurnTail(ctx.conversationId);
2564
2999
 
2565
3000
  expect(projectAssistantMessageMock).toHaveBeenCalledTimes(1);
2566
3001
  // The mock will still receive a `:messages` invalidation from the
@@ -2950,6 +3385,7 @@ describe("session-agent-loop", () => {
2950
3385
 
2951
3386
  // WHEN the orchestrator runs the turn to completion
2952
3387
  await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
3388
+ await settleTurnTail(ctx.conversationId);
2953
3389
 
2954
3390
  expect(snapshot).toBeDefined();
2955
3391
  // Indexer + projector were both ZERO during the mid-turn partial
@@ -128,7 +128,11 @@ describe("addMessage + syncMessageToDisk → disk view", () => {
128
128
  skipIndexing: true,
129
129
  });
130
130
 
131
- const att = uploadAttachment("screenshot.png", "image/png", "iVBORw0K");
131
+ const att = await uploadAttachment(
132
+ "screenshot.png",
133
+ "image/png",
134
+ "iVBORw0K",
135
+ );
132
136
  linkAttachmentToMessage(msg.id, att.id, 0);
133
137
 
134
138
  syncMessageToDisk(conv.id, msg.id, conv.createdAt);
@@ -434,7 +434,7 @@ describe("syncMessageToDisk", () => {
434
434
  });
435
435
 
436
436
  // Upload an attachment and link to the message
437
- const att = uploadAttachment("photo.png", "image/png", "iVBORw0K");
437
+ const att = await uploadAttachment("photo.png", "image/png", "iVBORw0K");
438
438
  linkAttachmentToMessage(msg.id, att.id, 0);
439
439
 
440
440
  syncMessageToDisk(conv.id, msg.id, conv.createdAt);
@@ -498,7 +498,7 @@ describe("syncMessageToDisk", () => {
498
498
  skipIndexing: true,
499
499
  });
500
500
 
501
- const att = uploadAttachment("legacy.png", "image/png", "iVBORw0K");
501
+ const att = await uploadAttachment("legacy.png", "image/png", "iVBORw0K");
502
502
  linkAttachmentToMessage(msg.id, att.id, 0);
503
503
 
504
504
  syncMessageToDisk(conv.id, msg.id, createdAt);
@@ -525,7 +525,7 @@ describe("syncMessageToDisk", () => {
525
525
  const msg = await addMessage(conv.id, "user", "Disk repair", {
526
526
  skipIndexing: true,
527
527
  });
528
- const att = uploadAttachment("repair.png", "image/png", "iVBORw0K");
528
+ const att = await uploadAttachment("repair.png", "image/png", "iVBORw0K");
529
529
  rawRun(
530
530
  "test:linkAttachment",
531
531
  `INSERT INTO message_attachments (id, message_id, attachment_id, position, created_at)
@@ -603,7 +603,7 @@ describe("rebuildConversationDiskViewFromDbState", () => {
603
603
  { skipIndexing: true },
604
604
  );
605
605
 
606
- const att = uploadAttachment("result.txt", "text/plain", "ok");
606
+ const att = await uploadAttachment("result.txt", "text/plain", "ok");
607
607
  linkAttachmentToMessage(assistantPart2.id, att.id, 0);
608
608
 
609
609
  // Simulate stale disk view generated before consolidation.
@@ -1556,7 +1556,7 @@ describe("ConnectionResolutionError classification", () => {
1556
1556
  expect(result.profileName).toBe("custom-fast");
1557
1557
  });
1558
1558
 
1559
- it("classifies missing_connection with an add-a-key fix and no sentinel name", () => {
1559
+ it("classifies missing_connection offering inline recovery and no sentinel name", () => {
1560
1560
  const err = new ConnectionResolutionError(
1561
1561
  "<llm.default>",
1562
1562
  "missing_connection",
@@ -1566,7 +1566,10 @@ describe("ConnectionResolutionError classification", () => {
1566
1566
  expect(result.userMessage).toContain(
1567
1567
  "No provider connection is configured",
1568
1568
  );
1569
- expect(result.userMessage).toContain("Add an API key or log in");
1569
+ // The user is locked out of chat here, so the copy must offer recovery in
1570
+ // the conversation itself, not only a settings detour.
1571
+ expect(result.userMessage).toContain("Ask me to set one up right here");
1572
+ expect(result.userMessage).toContain("Settings → Models & Services");
1570
1573
  expect(result.userMessage).not.toContain("<llm.default>");
1571
1574
  expect(result.connectionName).toBeUndefined();
1572
1575
  });
@@ -1610,7 +1613,7 @@ describe("ConnectionResolutionError classification", () => {
1610
1613
  expect(result.userMessage).toContain('profile "custom-fast"');
1611
1614
  });
1612
1615
 
1613
- it("classifies platform_unauthenticated with a log-in fix", () => {
1616
+ it("classifies platform_unauthenticated with a log-in fix on a non-platform install", () => {
1614
1617
  const err = new ConnectionResolutionError(
1615
1618
  "vellum",
1616
1619
  "platform_unauthenticated",
@@ -1621,6 +1624,22 @@ describe("ConnectionResolutionError classification", () => {
1621
1624
  expect(result.userMessage).toContain("Log in");
1622
1625
  });
1623
1626
 
1627
+ it("classifies platform_unauthenticated as a re-provision hint on a platform-managed assistant", () => {
1628
+ const err = new ConnectionResolutionError(
1629
+ "vellum",
1630
+ "platform_unauthenticated",
1631
+ "unavailable",
1632
+ );
1633
+ process.env.IS_PLATFORM = "true";
1634
+ try {
1635
+ const result = classifyConversationError(err, errCtx);
1636
+ expect(result.userMessage).toContain("re-provisioned");
1637
+ expect(result.userMessage).not.toContain("Log in");
1638
+ } finally {
1639
+ delete process.env.IS_PLATFORM;
1640
+ }
1641
+ });
1642
+
1624
1643
  it("is a structured VellumError (ConfigError) for logging/monitoring", () => {
1625
1644
  const err = new ConnectionResolutionError("x", "not_found", "m");
1626
1645
  expect(err).toBeInstanceOf(VellumError);
@@ -702,7 +702,11 @@ describe("forkConversation", () => {
702
702
  "Attached the updated mock.",
703
703
  { skipIndexing: true },
704
704
  );
705
- const uploaded = uploadAttachment("wireframe.png", "image/png", "iVBORw0K");
705
+ const uploaded = await uploadAttachment(
706
+ "wireframe.png",
707
+ "image/png",
708
+ "iVBORw0K",
709
+ );
706
710
  linkAttachmentToMessage(sourceAssistant.id, uploaded.id, 0);
707
711
 
708
712
  const sourceAttachments = getAttachmentsForMessage(sourceAssistant.id);