@vellumai/assistant 0.11.2 → 0.11.3-staging.1

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 (709) 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-queue.test.ts +265 -1
  72. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  73. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  74. package/src/__tests__/conversation-store.test.ts +26 -10
  75. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  82. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  83. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  84. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  85. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  86. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  87. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  88. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  89. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  90. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  91. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  92. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  93. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  94. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  95. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  96. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  97. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  98. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  99. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  100. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  101. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  102. package/src/__tests__/disk-usage.test.ts +35 -20
  103. package/src/__tests__/document-sync-tags.test.ts +379 -0
  104. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  105. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  106. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  107. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  108. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  109. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  110. package/src/__tests__/file-list-tool.test.ts +12 -10
  111. package/src/__tests__/file-ops-service.test.ts +151 -44
  112. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  113. package/src/__tests__/file-read-tool.test.ts +21 -9
  114. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  115. package/src/__tests__/filesystem-tools.test.ts +82 -66
  116. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  117. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  118. package/src/__tests__/helpers/hub-clients.ts +42 -0
  119. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  120. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  121. package/src/__tests__/history-repair.test.ts +112 -12
  122. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  123. package/src/__tests__/host-bash-routes.test.ts +67 -0
  124. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  125. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  126. package/src/__tests__/host-file-proxy.test.ts +3 -7
  127. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  128. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  129. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  130. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  131. package/src/__tests__/identity-routes.test.ts +13 -13
  132. package/src/__tests__/image-conversion.test.ts +29 -26
  133. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  134. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  135. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  136. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  137. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  138. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  139. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  140. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  141. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  142. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  143. package/src/__tests__/list-messages-queued.test.ts +49 -6
  144. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  145. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  146. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  147. package/src/__tests__/llm-usage-store.test.ts +55 -0
  148. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  149. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  150. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  151. package/src/__tests__/migration-export-http.test.ts +1 -1
  152. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  153. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  154. package/src/__tests__/migration-validate-http.test.ts +1 -1
  155. package/src/__tests__/mtime-cache.test.ts +65 -3
  156. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  157. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  158. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  159. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  160. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  161. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  162. package/src/__tests__/path-policy.test.ts +164 -1
  163. package/src/__tests__/persist-media-references.test.ts +2 -2
  164. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  165. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  166. package/src/__tests__/persona-resolver.test.ts +154 -0
  167. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  168. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  169. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  170. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  171. package/src/__tests__/profiler-routes.test.ts +13 -13
  172. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  173. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  174. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  175. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  176. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  177. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  178. package/src/__tests__/run-due-schedules.test.ts +174 -0
  179. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  180. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  181. package/src/__tests__/schedule-routes.test.ts +730 -8
  182. package/src/__tests__/schedule-store.test.ts +540 -0
  183. package/src/__tests__/schedule-tools.test.ts +103 -0
  184. package/src/__tests__/scheduler-wake.test.ts +5 -0
  185. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  186. package/src/__tests__/secure-keys.test.ts +91 -0
  187. package/src/__tests__/server-history-render.test.ts +97 -7
  188. package/src/__tests__/size-guard.test.ts +8 -8
  189. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  190. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  191. package/src/__tests__/skills-install-staging.test.ts +15 -6
  192. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  193. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  194. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  195. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  196. package/src/__tests__/starter-task-flow.test.ts +5 -4
  197. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  198. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  199. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  200. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  201. package/src/__tests__/subagent-disposal.test.ts +10 -0
  202. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  203. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  204. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  205. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  206. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  207. package/src/__tests__/subagent-persistence.test.ts +141 -0
  208. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  209. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  210. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  211. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  212. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  213. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  214. package/src/__tests__/subagent-tools.test.ts +1901 -108
  215. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  216. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  217. package/src/__tests__/system-prompt.test.ts +82 -7
  218. package/src/__tests__/tool-result-spool.test.ts +74 -18
  219. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  220. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  221. package/src/__tests__/tools-audio-read.test.ts +10 -10
  222. package/src/__tests__/tools-get-route.test.ts +85 -12
  223. package/src/__tests__/tools-list-cli.test.ts +171 -0
  224. package/src/__tests__/top-level-renderer.test.ts +54 -1
  225. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  226. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  227. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  228. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  229. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  230. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  231. package/src/__tests__/usage-routes.test.ts +62 -0
  232. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  233. package/src/__tests__/visible-app-context.test.ts +20 -4
  234. package/src/__tests__/web-search-history.test.ts +96 -0
  235. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  236. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  237. package/src/acp/failure-error.ts +3 -5
  238. package/src/agent/attachments.ts +26 -24
  239. package/src/agent/history-repair/history-repair.ts +27 -26
  240. package/src/agent/image-optimize.ts +6 -6
  241. package/src/agent/loop.ts +40 -22
  242. package/src/agent/message-types.ts +3 -3
  243. package/src/api/constants/document-tools.ts +55 -0
  244. package/src/api/events/question-answered.ts +53 -0
  245. package/src/api/events/tool-result.ts +7 -0
  246. package/src/api/index.ts +13 -0
  247. package/src/api/responses/conversation-message.ts +11 -0
  248. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  249. package/src/api/surfaces.ts +36 -8
  250. package/src/approvals/guardian-channel-delivery.ts +83 -0
  251. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  252. package/src/approvals/guardian-request-resolvers.ts +216 -132
  253. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  254. package/src/calls/media-stream-server.ts +9 -1
  255. package/src/calls/voice-session-bridge.ts +38 -0
  256. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  257. package/src/channels/config.ts +7 -2
  258. package/src/channels/gateway-verification-sessions.ts +7 -2
  259. package/src/channels/types.ts +7 -1
  260. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  261. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  262. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  263. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  264. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  265. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  266. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  267. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  268. package/src/cli/commands/conversations.ts +7 -3
  269. package/src/cli/commands/credentials.help.ts +7 -0
  270. package/src/cli/commands/inference-profiles.ts +9 -3
  271. package/src/cli/commands/inference.help.ts +16 -2
  272. package/src/cli/commands/mcp.help.ts +4 -2
  273. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  274. package/src/cli/commands/notifications.ts +3 -6
  275. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  276. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  277. package/src/cli/commands/oauth/connect.test.ts +26 -1
  278. package/src/cli/commands/oauth/connect.ts +4 -1
  279. package/src/cli/commands/oauth/index.help.ts +3 -1
  280. package/src/cli/commands/oauth/providers.ts +3 -0
  281. package/src/cli/commands/plugins.help.ts +25 -2
  282. package/src/cli/commands/plugins.ts +289 -10
  283. package/src/cli/commands/schedules.help.ts +14 -12
  284. package/src/cli/commands/schedules.ts +19 -5
  285. package/src/cli/commands/tools.help.ts +4 -2
  286. package/src/cli/commands/tools.ts +38 -0
  287. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  288. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  289. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  290. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  291. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  292. package/src/cli/lib/install-from-github.ts +61 -0
  293. package/src/cli/lib/install-from-platform.ts +13 -2
  294. package/src/cli/lib/plugin-surfaces.ts +124 -4
  295. package/src/cli/lib/upgrade-plugin.ts +18 -0
  296. package/src/cli.ts +1 -1
  297. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  298. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  299. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  300. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  301. package/src/config/bundled-skills/AGENTS.md +1 -1
  302. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  303. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  304. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  305. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  306. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  307. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  308. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  309. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  310. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  311. package/src/config/default-profile-catalog.ts +6 -2
  312. package/src/config/feature-flag-registry.json +25 -17
  313. package/src/config/llm-resolver.ts +45 -15
  314. package/src/config/loader.ts +70 -1
  315. package/src/config/profile-tool-support.ts +110 -0
  316. package/src/config/sanitize-for-transfer.ts +11 -0
  317. package/src/config/schema.ts +4 -0
  318. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  319. package/src/config/schemas/channels.ts +13 -0
  320. package/src/config/schemas/memory-retrospective.ts +25 -0
  321. package/src/config/schemas/plugin-updates.ts +55 -0
  322. package/src/config/schemas/services.ts +8 -4
  323. package/src/config/schemas/stt.ts +17 -5
  324. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  325. package/src/contacts/guardian-delivery-reader.ts +29 -2
  326. package/src/context/compactor.ts +4 -4
  327. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  328. package/src/context/strip-injections.ts +42 -0
  329. package/src/context/tool-result-spool.ts +32 -18
  330. package/src/credential-execution/ces-runtime.ts +3 -0
  331. package/src/credential-execution/client.ts +22 -1
  332. package/src/credential-execution/process-manager.test.ts +99 -0
  333. package/src/credential-execution/process-manager.ts +62 -16
  334. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  335. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  336. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  337. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  338. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  339. package/src/daemon/channel-ui-capability.ts +4 -4
  340. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  341. package/src/daemon/conversation-agent-loop.ts +296 -145
  342. package/src/daemon/conversation-attachments.ts +1 -1
  343. package/src/daemon/conversation-error.ts +8 -1
  344. package/src/daemon/conversation-initial-prompt.ts +16 -38
  345. package/src/daemon/conversation-lifecycle.ts +154 -32
  346. package/src/daemon/conversation-messaging.ts +85 -42
  347. package/src/daemon/conversation-process.ts +76 -28
  348. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  349. package/src/daemon/conversation-store.ts +52 -3
  350. package/src/daemon/conversation-surface-state.ts +9 -0
  351. package/src/daemon/conversation-surfaces.ts +403 -210
  352. package/src/daemon/conversation-tool-setup.ts +56 -7
  353. package/src/daemon/conversation-turn-finalize.ts +134 -89
  354. package/src/daemon/conversation.ts +105 -27
  355. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  356. package/src/daemon/disk-pressure-guard.ts +3 -3
  357. package/src/daemon/doordash-steps.ts +5 -5
  358. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  359. package/src/daemon/handlers/config-channels.ts +48 -5
  360. package/src/daemon/handlers/conversation-history.ts +1 -1
  361. package/src/daemon/handlers/conversations.ts +62 -17
  362. package/src/daemon/handlers/shared.ts +12 -0
  363. package/src/daemon/handlers/skills.ts +11 -1
  364. package/src/daemon/identity-helpers.ts +38 -10
  365. package/src/daemon/install-assistant-command.ts +322 -0
  366. package/src/daemon/lifecycle.ts +14 -3
  367. package/src/daemon/message-types/conversations.ts +6 -7
  368. package/src/daemon/message-types/sync.ts +1 -0
  369. package/src/daemon/persist-media-references.ts +23 -14
  370. package/src/daemon/process-message.ts +6 -6
  371. package/src/daemon/tool-setup-types.ts +19 -122
  372. package/src/daemon/tool-side-effects.ts +17 -94
  373. package/src/daemon/trust-context.ts +14 -6
  374. package/src/daemon/turn-tail-chain.ts +73 -0
  375. package/src/daemon/web-search-history.ts +47 -33
  376. package/src/heartbeat/heartbeat-service.ts +6 -6
  377. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  378. package/src/ipc/assistant-server.ts +17 -10
  379. package/src/ipc/cli-client.ts +10 -1
  380. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  381. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  382. package/src/ipc/socket-cleanup.ts +9 -7
  383. package/src/ipc/socket-path.ts +4 -96
  384. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  385. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  386. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  387. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  388. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  389. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  390. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  391. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  392. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  393. package/src/live-voice/live-voice-archive.ts +13 -13
  394. package/src/live-voice/live-voice-connection.ts +7 -1
  395. package/src/live-voice/live-voice-photo.ts +169 -0
  396. package/src/live-voice/live-voice-session.ts +108 -4
  397. package/src/live-voice/protocol.ts +89 -1
  398. package/src/mcp/client.ts +10 -0
  399. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  400. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  401. package/src/messaging/providers/callback-routing.ts +1 -0
  402. package/src/messaging/providers/discord/api.ts +312 -0
  403. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  404. package/src/messaging/providers/discord/render.test.ts +204 -0
  405. package/src/messaging/providers/discord/render.ts +195 -0
  406. package/src/messaging/providers/discord/send.test.ts +179 -0
  407. package/src/messaging/providers/discord/send.ts +212 -0
  408. package/src/messaging/providers/discord/transport.ts +81 -0
  409. package/src/messaging/providers/index.ts +3 -1
  410. package/src/messaging/providers/retry-policy.test.ts +247 -0
  411. package/src/messaging/providers/retry-policy.ts +206 -0
  412. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  413. package/src/messaging/providers/slack/mention-source.ts +287 -0
  414. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  415. package/src/messaging/providers/whatsapp/api.ts +47 -136
  416. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  417. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  418. package/src/monitoring/db-integrity-sample.ts +6 -1
  419. package/src/monitoring/plugin-auto-update.ts +359 -0
  420. package/src/monitoring/resource-sampler.ts +21 -5
  421. package/src/monitoring/worker.ts +13 -0
  422. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  423. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  424. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  425. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  426. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  427. package/src/notifications/adapters/platform.ts +31 -2
  428. package/src/notifications/assistant-reply-producer.ts +79 -32
  429. package/src/notifications/broadcaster.ts +30 -10
  430. package/src/notifications/copy-composer.ts +69 -0
  431. package/src/notifications/emit-signal.ts +82 -15
  432. package/src/notifications/events-store.ts +10 -2
  433. package/src/notifications/home-feed-side-effect.ts +6 -3
  434. package/src/notifications/notification-utils.ts +173 -5
  435. package/src/notifications/signal.ts +12 -0
  436. package/src/oauth/byo-connection.ts +6 -1
  437. package/src/oauth/credential-token-resolver.ts +3 -10
  438. package/src/oauth/manual-token-connection.ts +29 -64
  439. package/src/oauth/manual-token-providers.ts +62 -0
  440. package/src/oauth/provider-serializer.ts +13 -0
  441. package/src/oauth/seed-providers.ts +56 -8
  442. package/src/onboarding/onboarding-events-store.ts +91 -1
  443. package/src/permissions/question-prompter.test.ts +4 -0
  444. package/src/permissions/question-prompter.ts +21 -4
  445. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  446. package/src/persistence/attachments-store.ts +15 -15
  447. package/src/persistence/conversation-attention-store.ts +30 -10
  448. package/src/persistence/conversation-bootstrap.ts +17 -0
  449. package/src/persistence/conversation-crud.ts +363 -97
  450. package/src/persistence/conversation-group-migration.ts +1 -1
  451. package/src/persistence/conversation-key-store.ts +38 -10
  452. package/src/persistence/conversation-lineage.ts +226 -0
  453. package/src/persistence/conversation-plugin-facade.ts +28 -3
  454. package/src/persistence/conversation-queries.ts +207 -33
  455. package/src/persistence/conversation-types.ts +107 -1
  456. package/src/persistence/jobs-store.ts +32 -8
  457. package/src/persistence/llm-usage-store.ts +22 -0
  458. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  459. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  460. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  461. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  462. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  463. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  464. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  465. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  466. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  467. package/src/persistence/schema/conversations.ts +32 -0
  468. package/src/persistence/schema/infrastructure.ts +3 -0
  469. package/src/persistence/steps.ts +30 -0
  470. package/src/persistence/subagent-store.ts +148 -6
  471. package/src/platform/managed-speech.test.ts +20 -0
  472. package/src/platform/managed-speech.ts +9 -0
  473. package/src/plugin-api/index.ts +8 -0
  474. package/src/plugin-api/webhook-url.ts +151 -0
  475. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  476. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  477. package/src/plugins/collect-source-versions.ts +4 -27
  478. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  479. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  480. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  481. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  482. package/src/plugins/defaults/index.ts +1 -1
  483. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  484. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  485. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  486. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  487. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  488. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  489. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  490. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  491. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  492. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  493. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  494. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  495. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  496. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  497. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  498. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  499. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  500. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  501. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  502. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  503. package/src/plugins/external-plugin-loader.ts +17 -8
  504. package/src/plugins/installed-plugin-dirs.ts +101 -0
  505. package/src/plugins/mtime-cache.ts +34 -22
  506. package/src/plugins/types.ts +69 -8
  507. package/src/prompts/persona-resolver.ts +6 -2
  508. package/src/prompts/system-prompt.ts +72 -17
  509. package/src/providers/__tests__/inference.test.ts +40 -0
  510. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  511. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  512. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  513. package/src/providers/anthropic/client.ts +112 -95
  514. package/src/providers/connection-resolution.ts +35 -1
  515. package/src/providers/gemini/client.ts +5 -5
  516. package/src/providers/inference/adapter-factory.ts +16 -10
  517. package/src/providers/inference/auth.ts +16 -0
  518. package/src/providers/inference/connection-availability.ts +164 -12
  519. package/src/providers/inference/connections.ts +25 -1
  520. package/src/providers/media-resolve.ts +21 -11
  521. package/src/providers/model-catalog.ts +15 -0
  522. package/src/providers/openai/chat-completions-provider.ts +4 -4
  523. package/src/providers/openai/responses-provider.ts +12 -25
  524. package/src/providers/registry.ts +9 -4
  525. package/src/providers/retry.ts +25 -0
  526. package/src/providers/server-tool-pairing.ts +134 -0
  527. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  528. package/src/providers/speech-to-text/deepgram.ts +1 -0
  529. package/src/providers/speech-to-text/resolve.ts +80 -10
  530. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  531. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  532. package/src/providers/types.ts +26 -6
  533. package/src/providers/vellum-model-routing.ts +6 -7
  534. package/src/runtime/AGENTS.md +2 -0
  535. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  536. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  537. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  538. package/src/runtime/agent-wake.ts +63 -24
  539. package/src/runtime/assistant-event-hub.ts +87 -12
  540. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  541. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  542. package/src/runtime/auth/same-actor.ts +56 -8
  543. package/src/runtime/auth/scopes.ts +12 -0
  544. package/src/runtime/auth/types.ts +2 -0
  545. package/src/runtime/background-job-runner.ts +5 -27
  546. package/src/runtime/channel-readiness-service.ts +59 -3
  547. package/src/runtime/channel-readiness-types.ts +18 -0
  548. package/src/runtime/desktop-presence.ts +69 -0
  549. package/src/runtime/http-server.ts +12 -0
  550. package/src/runtime/local-actor-identity.ts +11 -9
  551. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  552. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  553. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  554. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  555. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  556. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  557. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  558. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  559. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  560. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  561. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  562. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  563. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  564. package/src/runtime/routes/archive-utils.ts +16 -12
  565. package/src/runtime/routes/attachment-routes.ts +13 -6
  566. package/src/runtime/routes/canned-reply-release.ts +54 -0
  567. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  568. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  569. package/src/runtime/routes/client-routes.ts +114 -2
  570. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  571. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  572. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  573. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  574. package/src/runtime/routes/conversation-routes.ts +350 -293
  575. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  576. package/src/runtime/routes/debug-routes.ts +4 -1
  577. package/src/runtime/routes/default-provider-routes.ts +7 -11
  578. package/src/runtime/routes/documents-routes.ts +44 -1
  579. package/src/runtime/routes/events-routes.ts +28 -1
  580. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  581. package/src/runtime/routes/host-bash-routes.ts +2 -0
  582. package/src/runtime/routes/host-browser-routes.ts +2 -0
  583. package/src/runtime/routes/host-cu-routes.ts +2 -0
  584. package/src/runtime/routes/host-file-routes.ts +2 -0
  585. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  586. package/src/runtime/routes/identity-routes.ts +6 -6
  587. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  588. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  589. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  590. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  591. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  592. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  593. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  594. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  595. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  596. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  597. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  598. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  599. package/src/runtime/routes/log-export-routes.ts +1 -1
  600. package/src/runtime/routes/notification-routes.ts +0 -14
  601. package/src/runtime/routes/oauth-providers.ts +1 -0
  602. package/src/runtime/routes/plugins-routes.ts +106 -1
  603. package/src/runtime/routes/profiler-routes.ts +4 -2
  604. package/src/runtime/routes/question-routes.ts +28 -10
  605. package/src/runtime/routes/retrospective-routes.ts +9 -3
  606. package/src/runtime/routes/schedule-routes.ts +210 -12
  607. package/src/runtime/routes/secret-routes.ts +40 -23
  608. package/src/runtime/routes/settings-routes.ts +41 -13
  609. package/src/runtime/routes/skills-routes.ts +79 -0
  610. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  611. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  612. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  613. package/src/runtime/services/conversation-serializer.ts +24 -9
  614. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  615. package/src/runtime/sync/resource-sync-events.ts +79 -1
  616. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  617. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  618. package/src/runtime/verification-outbound-actions.ts +250 -222
  619. package/src/runtime/verification-templates.ts +41 -30
  620. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  621. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  622. package/src/schedule/inference-profile.ts +64 -3
  623. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  624. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  625. package/src/schedule/plugin-schedules-gate.ts +23 -0
  626. package/src/schedule/schedule-store.ts +520 -26
  627. package/src/schedule/scheduler.ts +38 -0
  628. package/src/security/credential-backend.ts +51 -4
  629. package/src/security/encrypted-store.ts +64 -42
  630. package/src/security/secure-keys.ts +55 -7
  631. package/src/skills/catalog-install.ts +20 -7
  632. package/src/skills/inline-command-runner.ts +2 -7
  633. package/src/skills/skill-history.test.ts +269 -0
  634. package/src/skills/skill-history.ts +250 -0
  635. package/src/skills/skillssh-registry.ts +1 -1
  636. package/src/stt/daemon-batch-transcriber.ts +8 -6
  637. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  638. package/src/subagent/consult-prompt.ts +3 -1
  639. package/src/subagent/consult-transcript.ts +1 -1
  640. package/src/subagent/manager.ts +295 -42
  641. package/src/subagent/progress-events.ts +35 -0
  642. package/src/subagent/role-resolution.ts +88 -0
  643. package/src/subagent/types.ts +302 -67
  644. package/src/subagent/validate-allowlists.ts +3 -4
  645. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  646. package/src/telegram/webhook-health.ts +101 -3
  647. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  648. package/src/telemetry/activation-funnel.ts +6 -1
  649. package/src/telemetry/live-voice-funnel.ts +75 -0
  650. package/src/telemetry/telemetry-event-sources.ts +6 -0
  651. package/src/telemetry/telemetry-wire-source.json +1 -1
  652. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  653. package/src/telemetry/types.ts +23 -4
  654. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  655. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  656. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  657. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  658. package/src/tools/browser/browser-execution.ts +1 -1
  659. package/src/tools/calls/call-start.ts +8 -5
  660. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  661. package/src/tools/document/document-tool.ts +11 -4
  662. package/src/tools/executor.ts +7 -6
  663. package/src/tools/filesystem/edit.ts +1 -1
  664. package/src/tools/filesystem/list.ts +1 -1
  665. package/src/tools/filesystem/read.ts +7 -13
  666. package/src/tools/filesystem/search.ts +23 -21
  667. package/src/tools/filesystem/write.ts +4 -1
  668. package/src/tools/host-filesystem/edit.ts +7 -2
  669. package/src/tools/host-filesystem/read.ts +21 -8
  670. package/src/tools/host-filesystem/write.ts +10 -2
  671. package/src/tools/host-terminal/host-shell.ts +2 -2
  672. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  673. package/src/tools/permission-checker.ts +8 -3
  674. package/src/tools/schedule/delete.ts +13 -1
  675. package/src/tools/schedule/list.ts +14 -3
  676. package/src/tools/schedule/update.ts +56 -33
  677. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  678. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  679. package/src/tools/shared/filesystem/image-read.ts +12 -12
  680. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  681. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  682. package/src/tools/shared/input-misuse.ts +72 -0
  683. package/src/tools/skills/scaffold-managed.ts +82 -0
  684. package/src/tools/skills/skill-tool-factory.ts +14 -1
  685. package/src/tools/subagent/consult-deadline.ts +12 -7
  686. package/src/tools/subagent/message.ts +3 -1
  687. package/src/tools/subagent/read.ts +66 -4
  688. package/src/tools/subagent/spawn.ts +500 -78
  689. package/src/tools/types.ts +22 -3
  690. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  691. package/src/tools/ui-surface/visual-validation.ts +2 -0
  692. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  693. package/src/usage/subagent-attribution.ts +108 -0
  694. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  695. package/src/util/abort-reasons.ts +16 -0
  696. package/src/util/ansi.ts +44 -0
  697. package/src/util/disk-usage.ts +17 -13
  698. package/src/util/image-conversion.ts +20 -13
  699. package/src/util/schedule-source-key.ts +23 -0
  700. package/src/util/worker-process.ts +7 -1
  701. package/src/workspace/git-service.ts +33 -9
  702. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  703. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  704. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  705. package/src/workspace/migrations/registry.ts +4 -0
  706. package/src/workspace/top-level-renderer.ts +15 -2
  707. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  708. package/src/daemon/install-symlink.ts +0 -207
  709. package/src/notifications/deferred-emit.ts +0 -147
@@ -116,7 +116,10 @@ import {
116
116
  } from "./conversation-runtime-assembly.js";
117
117
  import type { CurrentTurnSurface } from "./conversation-surfaces.js";
118
118
  import { markSurfaceCompleted } from "./conversation-surfaces.js";
119
- import { runDeferredTurnTail } from "./conversation-turn-finalize.js";
119
+ import {
120
+ runDeferredTurnTail,
121
+ settleTurnContent,
122
+ } from "./conversation-turn-finalize.js";
120
123
  import { recordUsage } from "./conversation-usage.js";
121
124
  import { resolveTurnTimezoneContext } from "./date-context.js";
122
125
  import { getDiskPressureStatus } from "./disk-pressure-guard.js";
@@ -133,6 +136,7 @@ import {
133
136
  MEMORY_CONTEXT_PHASE_KEY,
134
137
  TurnLatencyTracker,
135
138
  } from "./turn-latency-tracker.js";
139
+ import { chainTurnTail } from "./turn-tail-chain.js";
136
140
 
137
141
  const log = getLogger("conversation-agent-loop");
138
142
 
@@ -152,6 +156,43 @@ const DISK_PRESSURE_ERROR_CATEGORY = "disk_pressure";
152
156
  const OUT_OF_CREDITS_ASSISTANT_REPLY =
153
157
  "I couldn't reply because you ran out of credits. Add credits in Settings → Billing and we can pick up where we left off.";
154
158
 
159
+ /**
160
+ * Assistant-voice wording persisted as the synthetic assistant row when a turn
161
+ * terminates because the daily credit limit is reached. The shared
162
+ * classification (`dailyLimitClassification` in conversation-error.ts) keeps
163
+ * its `userMessage` in banner voice because it also feeds the live
164
+ * `conversation_error` event the composer renders; here the row re-enters LLM
165
+ * history and is displayed as assistant speech, so first-person copy is
166
+ * accurate. Says "stop" rather than "reply" because the row can land mid-turn,
167
+ * after a tool round-trip the user already saw. Phrased about the past failure,
168
+ * not the current limit, because the row stays in the transcript (and renders
169
+ * as a plain bubble) after the limit resets.
170
+ */
171
+ const DAILY_LIMIT_REACHED_ASSISTANT_REPLY =
172
+ "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.";
173
+
174
+ /**
175
+ * The assistant-voice text a managed-billing failure persists in place of the
176
+ * classification's own `userMessage`, or `null` when the classification copy is
177
+ * already right for a transcript row.
178
+ */
179
+ function managedBillingAssistantReply(
180
+ providerErrorCode: string | null,
181
+ providerErrorCategory: string | null,
182
+ ): string | null {
183
+ if (providerErrorCode !== "PROVIDER_BILLING") {
184
+ return null;
185
+ }
186
+ switch (providerErrorCategory) {
187
+ case "credits_exhausted":
188
+ return OUT_OF_CREDITS_ASSISTANT_REPLY;
189
+ case "daily_limit_reached":
190
+ return DAILY_LIMIT_REACHED_ASSISTANT_REPLY;
191
+ default:
192
+ return null;
193
+ }
194
+ }
195
+
155
196
  /** Title-cased friendly labels for tool names, used in confirmation chips. */
156
197
  const TOOL_FRIENDLY_LABEL: Record<string, string> = {
157
198
  bash: "Run Command",
@@ -560,6 +601,11 @@ export async function runAgentLoopImpl(
560
601
  // applies to later tool executions and nested subagents in the same turn.
561
602
  ctx.currentTurnOverrideProfile = turnOverrideProfile;
562
603
 
604
+ // Mirrored onto the live conversation for `createToolExecutor` to read into
605
+ // `ToolContext.cronRunId`, so a tool that delegates LLM work (subagent spawn
606
+ // or message) stamps the delegated usage with this firing.
607
+ ctx.currentTurnCronRunId = turnCronRunId;
608
+
563
609
  // Capture the turn channel context *before* any awaits so a second
564
610
  // message from a different channel can't overwrite it mid-flight.
565
611
  // When context is unavailable (e.g. regenerate after daemon restart),
@@ -637,6 +683,7 @@ export async function runAgentLoopImpl(
637
683
  );
638
684
  ctx.diskPressureCleanupModeActive =
639
685
  diskPressureDecision.action === "allow-cleanup-mode";
686
+ const toolsDisabledForTurn = ctx.toolsDisabledDepth > 0;
640
687
 
641
688
  ctx.lastAssistantAttachments = [];
642
689
  ctx.lastAttachmentWarnings = [];
@@ -659,14 +706,106 @@ export async function runAgentLoopImpl(
659
706
  | { outcome: "failed" | "cancelled"; failureCode?: string }
660
707
  | undefined;
661
708
  // True once a replied terminal SSE (message_complete / generation_handoff)
662
- // has been emitted. Guards the catch block: an error thrown afterwards
663
- // (deferred turn-tail bookkeeping) must not relabel a visibly-replied turn.
709
+ // has been emitted. Guards the catch block: an error thrown by the
710
+ // content-settle steps that run after it must not relabel a visibly-replied
711
+ // turn.
664
712
  let turnReplied = false;
665
713
  // Narrower than `turnReplied`: true only for a `message_complete` branch that
666
714
  // produced a genuine reply. A handed-off generation continues the run, and a
667
715
  // provider-error turn's only assistant row is the synthetic error text, so
668
716
  // the deferred tail must not treat either as a final reply.
669
717
  let turnCompleted = false;
718
+ // True once `releaseTurn` has run. The happy path releases as soon as the
719
+ // turn's content is settled; the `finally` calls it again as the backstop for
720
+ // the cancel/error paths that never reached the early release.
721
+ let turnReleased = false;
722
+
723
+ /**
724
+ * Free the conversation for its next turn.
725
+ *
726
+ * Ordering is load-bearing and identical on both call paths: stamp the
727
+ * turn's abnormal outcome first, because the telemetry reporter's
728
+ * settled-turn barrier only releases this turn once processing stops; null
729
+ * `abortController` before clearing the flag so a concurrent abort takes the
730
+ * force-clear branch rather than signalling a dead controller; unregister the
731
+ * in-flight turn only after processing stops, since daemon-side callers fall
732
+ * back to the live seq counter once that registration is gone (safe because
733
+ * `settlePendingPartialFlush` has already flushed the turn's content).
734
+ * Idempotent, so the `finally` can call it unconditionally.
735
+ *
736
+ * The release frees direct sends only. Queued messages are drained from the
737
+ * loop's `finally`, after the turn-boundary commit, so a dequeued turn's
738
+ * first file writes cannot be swept into the finished turn's commit.
739
+ */
740
+ const releaseTurn = (): void => {
741
+ if (turnReleased) {
742
+ return;
743
+ }
744
+ turnReleased = true;
745
+ if (abnormalOutcome) {
746
+ try {
747
+ stampTurnOutcome(userMessageId, abnormalOutcome.outcome, {
748
+ failureCode: abnormalOutcome.failureCode,
749
+ });
750
+ } catch (err) {
751
+ rlog.warn(
752
+ { err },
753
+ "Failed to stamp turn outcome (non-fatal); releasing the turn anyway",
754
+ );
755
+ }
756
+ }
757
+ ctx.abortController = null;
758
+ ctx.setProcessing(false);
759
+ unregisterInflightTurn(ctx.conversationId, state);
760
+
761
+ // Tear down the remaining per-turn state in the same synchronous step as
762
+ // the release, so a turn that starts against the freed conversation cannot
763
+ // inherit stale turn scope (task-run permissions, allowed tools, override
764
+ // profile, command intent) or observe a half-cleared conversation.
765
+ ctx.onConfirmationOutcome = undefined;
766
+ ctx.surfaceActionRequestIds.delete(ctx.currentRequestId ?? "");
767
+ ctx.approvedViaPromptThisTurn = false;
768
+ ctx.currentRequestId = undefined;
769
+ ctx.currentActiveSurfaceId = undefined;
770
+ ctx.allowedToolNames = undefined;
771
+ ctx.diskPressureCleanupModeActive = false;
772
+ ctx.preactivatedSkillIds = undefined;
773
+ ctx.currentTurnOverrideProfile = undefined;
774
+ ctx.currentTurnCronRunId = undefined;
775
+ ctx.currentTurnModelProfileNoticeKey = undefined;
776
+ // Turn-scoped interactivity. Clear it so paths that bypass this loop
777
+ // (e.g. opportunity wakes calling `agentLoop.run` directly) don't inherit
778
+ // a stale value and instead fall back to live client state in the tool
779
+ // context.
780
+ ctx.currentTurnIsNonInteractive = undefined;
781
+ // Turn-scoped request origin. Clear so a later turn on a reused
782
+ // conversation cannot inherit a stale origin-scoped permission grant.
783
+ ctx.currentTurnRequestOrigin = undefined;
784
+ // Channel command intents (e.g. Telegram /start) are single-turn
785
+ // metadata. Clear at turn end so they never leak into subsequent
786
+ // unrelated messages.
787
+ ctx.commandIntent = undefined;
788
+ // The app on screen is view state owned by the message that reported it,
789
+ // not durable conversation state: nothing tells the daemon when the user
790
+ // closes an app. Clear both copies at turn end so a later turn that brings
791
+ // no client transport (a scheduled wake, a background follow-up) cannot
792
+ // claim the user is looking at an app they may have closed. Every inbound
793
+ // send re-applies it from transport before its own turn, including the
794
+ // queue drain, so live turns are unaffected.
795
+ ctx.visibleAppId = undefined;
796
+ ctx.currentTurnVisibleAppId = undefined;
797
+ // taskRunId scopes ephemeral task-run permissions to a single turn. Clear
798
+ // before the queue drain so queued/drained turns on a reused conversation
799
+ // cannot inherit stale in-task-run scope from the turn that just finished.
800
+ ctx.taskRunId = undefined;
801
+
802
+ // Close the turn's profiling window here rather than after the queue
803
+ // drain: the profiler is per-conversation and the next turn's
804
+ // `startToolProfilingRequest` resets it, so a summary emitted after a
805
+ // drained turn started would report that turn's window under this turn's
806
+ // request id.
807
+ emitToolProfilingSummary(ctx.conversationId, reqId);
808
+ };
670
809
 
671
810
  const publishLoopMessagesChanged = (): void => {
672
811
  if (
@@ -717,14 +856,20 @@ export async function runAgentLoopImpl(
717
856
  return;
718
857
  }
719
858
 
720
- // Ensure workspace git repo is initialized before any tools run.
721
- try {
722
- const getWorkspaceGitServiceFn =
723
- ctx.getWorkspaceGitService ?? getWorkspaceGitService;
724
- const gitService = getWorkspaceGitServiceFn(ctx.workingDir);
725
- await gitService.ensureInitialized();
726
- } catch (err) {
727
- rlog.warn({ err }, "Failed to initialize workspace git repo (non-fatal)");
859
+ // Workspace Git readiness is required only when tools can run. Tool-less
860
+ // callers use the same depth gate consumed by tool resolution.
861
+ if (!toolsDisabledForTurn) {
862
+ try {
863
+ const getWorkspaceGitServiceFn =
864
+ ctx.getWorkspaceGitService ?? getWorkspaceGitService;
865
+ const gitService = getWorkspaceGitServiceFn(ctx.workingDir);
866
+ await gitService.ensureInitialized();
867
+ } catch (err) {
868
+ rlog.warn(
869
+ { err },
870
+ "Failed to initialize workspace git repo (non-fatal)",
871
+ );
872
+ }
728
873
  }
729
874
 
730
875
  // Auto-complete stale interactive surfaces from previous turns.
@@ -737,13 +882,20 @@ export async function runAgentLoopImpl(
737
882
  if (entry.surfaceType === "dynamic_page") {
738
883
  continue;
739
884
  }
885
+ // Persist before announcing: a client told the card was dismissed
886
+ // while the write failed would watch the next reseed revert it.
887
+ // An unannounced dismissal keeps its pending entry so client and
888
+ // daemon agree the card is still live and the one-interactive-surface
889
+ // gate keeps holding; the next user message sweeps it again.
890
+ if (!markSurfaceCompleted(ctx, surfaceId, "Dismissed")) {
891
+ continue;
892
+ }
740
893
  onEvent({
741
894
  type: "ui_surface_complete",
742
895
  conversationId: ctx.conversationId,
743
896
  surfaceId,
744
897
  summary: "Dismissed",
745
898
  });
746
- markSurfaceCompleted(ctx, surfaceId, "Dismissed");
747
899
  ctx.pendingSurfaceActions.delete(surfaceId);
748
900
  }
749
901
  }
@@ -1341,9 +1493,16 @@ export async function runAgentLoopImpl(
1341
1493
  return { ...msg, content: cleanedBlocks };
1342
1494
  });
1343
1495
 
1344
- const hasAssistantResponse = newMessages.some(
1345
- (msg) => msg.role === "assistant",
1346
- );
1496
+ // "The run delivered a final assistant reply", not "the run produced any
1497
+ // assistant-role message". A run that called tools already carries
1498
+ // assistant `tool_use` messages, and those are not a delivered reply: a
1499
+ // provider error that kills the run mid-turn leaves the tail as the
1500
+ // tool_result (user role), while a run that replied normally ends on the
1501
+ // assistant message. Testing only the tail keeps the synthetic error row
1502
+ // below reachable for mid-turn failures, which is the only durable record
1503
+ // of why the turn stopped.
1504
+ const hasAssistantResponse =
1505
+ newMessages[newMessages.length - 1]?.role === "assistant";
1347
1506
  if (
1348
1507
  !hasAssistantResponse &&
1349
1508
  state.providerErrorUserMessage &&
@@ -1403,13 +1562,13 @@ export async function runAgentLoopImpl(
1403
1562
  providerErrorCategory: state.providerErrorCategory ?? undefined,
1404
1563
  };
1405
1564
  // The persisted row re-enters LLM history and is displayed as
1406
- // assistant speech, so managed-credits exhaustion swaps the
1407
- // context-neutral classification copy for assistant-voice wording.
1565
+ // assistant speech, so the managed-billing categories swap the
1566
+ // banner-voice classification copy for assistant-voice wording.
1408
1567
  const persistedErrorText =
1409
- state.providerErrorCode === "PROVIDER_BILLING" &&
1410
- state.providerErrorCategory === "credits_exhausted"
1411
- ? OUT_OF_CREDITS_ASSISTANT_REPLY
1412
- : state.providerErrorUserMessage;
1568
+ managedBillingAssistantReply(
1569
+ state.providerErrorCode,
1570
+ state.providerErrorCategory,
1571
+ ) ?? state.providerErrorUserMessage;
1413
1572
  const errorAssistantMessage =
1414
1573
  createAssistantMessage(persistedErrorText);
1415
1574
  const errorRow = await addMessage(
@@ -1471,10 +1630,10 @@ export async function runAgentLoopImpl(
1471
1630
  // compaction/overflow recovery (where a cache miss is expected).
1472
1631
  //
1473
1632
  // Post-turn tool-result truncation (spooling large results to disk and
1474
- // shrinking the next turn's context) is deferred to `runDeferredTurnTail`
1633
+ // shrinking the next turn's context) is deferred to `settleTurnContent`
1475
1634
  // below. It only rewrites the in-memory history the NEXT turn is built
1476
- // from — never the just-delivered reply — so it must not sit on the
1477
- // critical path to the terminal SSE that re-enables the composer.
1635
+ // from, never the just-delivered reply, so it must not sit on the critical
1636
+ // path to the terminal SSE that re-enables the composer.
1478
1637
  ctx.messages = restoredHistory;
1479
1638
 
1480
1639
  emitUsage(
@@ -1504,7 +1663,7 @@ export async function runAgentLoopImpl(
1504
1663
  // Fast-path: when the user cancelled, skip expensive post-loop work
1505
1664
  // (attachment resolution) and emit the cancellation event immediately
1506
1665
  // so the client can re-enable the UI without delay. Disk sync and the rest
1507
- // of the bookkeeping run in `runDeferredTurnTail` after this SSE.
1666
+ // of the bookkeeping run after this SSE.
1508
1667
  if (abortController.signal.aborted) {
1509
1668
  abnormalOutcome = { outcome: "cancelled" };
1510
1669
  ctx.emitActivityState("idle", "generation_cancelled", {
@@ -1600,24 +1759,42 @@ export async function runAgentLoopImpl(
1600
1759
 
1601
1760
  // The terminal SSE for this turn has now been emitted (message_complete,
1602
1761
  // generation_handoff, or generation_cancelled), so the composer is already
1603
- // re-enabling. Settle any pending debounced partial flush FIRST — a
1762
+ // re-enabling. Settle any pending debounced partial flush FIRST: a
1604
1763
  // cancelled turn exits with the timer still pending, and a flush firing
1605
- // after the tail's stranded fold (or the voice bridge's transcript
1606
- // hygiene) would write raw content into an already-settled row. Then
1607
- // drain the deferred bookkeeping — after the SSE, before the `finally`
1608
- // commits and drains the queue for the next turn.
1764
+ // after the stranded fold (or the voice bridge's transcript hygiene) would
1765
+ // write raw content into an already-settled row. Then settle the rest of
1766
+ // the turn's content, which rewrites state the next turn also writes and so
1767
+ // has to complete under the processing lock.
1609
1768
  await settlePendingPartialFlush(state, deps);
1610
- await runDeferredTurnTail({
1611
- ctx,
1612
- state,
1613
- rlog,
1614
- generationCompletedAt,
1615
- turnCompleted,
1616
- userMessageId: options?.notifyUserMessageId ?? userMessageId,
1617
- ...(options?.replyDeliveredInAppOnly
1618
- ? { replyDeliveredInAppOnly: true }
1619
- : {}),
1620
- });
1769
+ await settleTurnContent({ ctx, state, rlog });
1770
+
1771
+ // Content is settled, so the conversation is free. Release before the
1772
+ // deferred tail rather than in the `finally`: the tail's memory indexing is
1773
+ // network-bound and unbounded, and holding the lock across it is what makes
1774
+ // a fully-delivered reply still answer a follow-up send with a queue slot.
1775
+ // The queue itself is drained later, from the `finally` after the
1776
+ // turn-boundary commit.
1777
+ const criticalSectionMs = Date.now() - generationCompletedAt;
1778
+ releaseTurn();
1779
+
1780
+ // Detached, and chained on the conversation's tail chain so two turns'
1781
+ // tails run in turn order instead of concurrently. The chain is keyed by
1782
+ // conversation id rather than held on this instance, because the tail runs
1783
+ // while the conversation reads idle and can therefore outlive the instance
1784
+ // that scheduled it.
1785
+ chainTurnTail(ctx.conversationId, () =>
1786
+ runDeferredTurnTail({
1787
+ conversationId: ctx.conversationId,
1788
+ state,
1789
+ rlog,
1790
+ criticalSectionMs,
1791
+ turnCompleted,
1792
+ userMessageId: options?.notifyUserMessageId ?? userMessageId,
1793
+ ...(options?.replyDeliveredInAppOnly
1794
+ ? { replyDeliveredInAppOnly: true }
1795
+ : {}),
1796
+ }),
1797
+ );
1621
1798
  } catch (err) {
1622
1799
  clearConversationNotices(ctx.conversationId);
1623
1800
  const errorCtx = {
@@ -1625,9 +1802,9 @@ export async function runAgentLoopImpl(
1625
1802
  aborted: abortController.signal.aborted,
1626
1803
  };
1627
1804
  if (isUserCancellation(err, errorCtx)) {
1628
- // Only label the turn when it hadn't already replied — a cancellation
1629
- // surfacing after the terminal SSE (deferred turn-tail bookkeeping)
1630
- // must not relabel a visibly-replied turn.
1805
+ // Only label the turn when it hadn't already replied: a cancellation
1806
+ // surfacing after the terminal SSE (the content-settle steps) must not
1807
+ // relabel a visibly-replied turn.
1631
1808
  if (!turnReplied) {
1632
1809
  abnormalOutcome = { outcome: "cancelled" };
1633
1810
  }
@@ -1665,112 +1842,86 @@ export async function runAgentLoopImpl(
1665
1842
  publishLoopMessagesChanged();
1666
1843
  }
1667
1844
  } finally {
1668
- // Clear the processing flag first. It is the release that frees the
1669
- // conversation for its next turn, so nothing that can throw (the
1670
- // turn-boundary commit, profiling) is allowed to run ahead of it and leave
1671
- // the row latched "mid-turn". Stamp the turn's abnormal outcome first
1672
- // because the telemetry reporter's settled-turn barrier only releases this
1673
- // turn once processing stops; null `abortController` first so a concurrent
1674
- // abort takes the force-clear branch rather than signalling a dead one.
1675
- if (abnormalOutcome) {
1676
- stampTurnOutcome(userMessageId, abnormalOutcome.outcome, {
1677
- failureCode: abnormalOutcome.failureCode,
1678
- });
1679
- }
1680
- ctx.abortController = null;
1681
- ctx.setProcessing(false);
1682
- // The turn's content is fully flushed by here (`settlePendingPartialFlush`
1683
- // ran inside the try), so daemon-side callers can safely fall back to the
1684
- // live seq counter once this registration is gone.
1685
- unregisterInflightTurn(ctx.conversationId, state);
1845
+ // Backstop release for the cancel/error paths, which throw out of the try
1846
+ // before the happy path's release runs. Idempotent, so the happy path
1847
+ // reaches here already released. It stays first in the `finally` so the
1848
+ // turn-boundary commit below cannot throw ahead of it and leave the row
1849
+ // latched "mid-turn".
1850
+ releaseTurn();
1851
+
1852
+ // The queue drain runs after the turn-boundary commit, under a `finally`
1853
+ // so a throw in the commit section cannot strand the queue with nothing
1854
+ // left to re-trigger it. Ordering is load-bearing: `commitTurnChanges`
1855
+ // attributes the working tree's changes to the finished turn, so a queued
1856
+ // turn that started while it was preparing would have its first file
1857
+ // writes swept into the wrong commit. The wait is deadline-bounded, and
1858
+ // direct sends are already unblocked by the release above.
1859
+ try {
1860
+ if (turnStarted) {
1861
+ ctx.turnCount++;
1862
+ const runTurnCommit = async (): Promise<void> => {
1863
+ const config = getConfig();
1864
+ const maxWait = config.workspaceGit?.turnCommitMaxWaitMs ?? 4000;
1865
+ const deadlineMs = Date.now() + maxWait;
1866
+
1867
+ const commitTurnChangesFn =
1868
+ ctx.commitTurnChanges ?? commitTurnChanges;
1869
+ const commitPromise = commitTurnChangesFn(
1870
+ ctx.workingDir,
1871
+ ctx.conversationId,
1872
+ ctx.turnCount,
1873
+ undefined,
1874
+ deadlineMs,
1875
+ );
1876
+ const outcome = await raceWithTimeout(commitPromise, maxWait);
1877
+ if (outcome === "timed_out") {
1878
+ rlog.warn(
1879
+ {
1880
+ turnNumber: ctx.turnCount,
1881
+ maxWaitMs: maxWait,
1882
+ conversationId: ctx.conversationId,
1883
+ },
1884
+ "Turn-boundary commit timed out; continuing without waiting (commit still runs in background)",
1885
+ );
1886
+ }
1887
+ };
1686
1888
 
1687
- if (turnStarted) {
1688
- ctx.turnCount++;
1689
- const config = getConfig();
1690
- const maxWait = config.workspaceGit?.turnCommitMaxWaitMs ?? 4000;
1691
- const deadlineMs = Date.now() + maxWait;
1889
+ if (toolsDisabledForTurn) {
1890
+ // Let the caller and the queue drain below claim an immediate
1891
+ // follow-up turn before starting Git. That turn will commit the
1892
+ // accumulated disk view.
1893
+ setImmediate(() => {
1894
+ if (ctx.isProcessing()) {
1895
+ return;
1896
+ }
1897
+ void runTurnCommit().catch((err) => {
1898
+ rlog.warn({ err }, "Deferred turn-boundary commit failed");
1899
+ });
1900
+ });
1901
+ } else {
1902
+ await runTurnCommit();
1903
+ }
1692
1904
 
1693
- const commitTurnChangesFn = ctx.commitTurnChanges ?? commitTurnChanges;
1694
- const commitPromise = commitTurnChangesFn(
1695
- ctx.workingDir,
1696
- ctx.conversationId,
1697
- ctx.turnCount,
1698
- undefined,
1699
- deadlineMs,
1700
- );
1701
- const outcome = await raceWithTimeout(commitPromise, maxWait);
1702
- if (outcome === "timed_out") {
1703
- rlog.warn(
1704
- {
1705
- turnNumber: ctx.turnCount,
1706
- maxWaitMs: maxWait,
1707
- conversationId: ctx.conversationId,
1708
- },
1709
- "Turn-boundary commit timed out — continuing without waiting (commit still runs in background)",
1710
- );
1905
+ // Recompute relationship-state.json at turn boundary (fire-and-forget).
1906
+ // The writer swallows its own errors, but we still guard with catch()
1907
+ // here so a regression in the writer can never bubble out of the
1908
+ // agent loop and reject an otherwise-complete turn.
1909
+ void writeRelationshipState().catch(() => {});
1711
1910
  }
1712
1911
 
1713
- // Recompute relationship-state.json at turn boundary (fire-and-forget).
1714
- // The writer swallows its own errors, but we still guard with catch()
1715
- // here so a regression in the writer can never bubble out of the
1716
- // agent loop and reject an otherwise-complete turn.
1717
- void writeRelationshipState().catch(() => {});
1912
+ // Consolidation deferred to compaction: keeping assistant + tool_result
1913
+ // messages unconsolidated preserves the exact message structure sent to
1914
+ // the API, enabling stable prefix caching across turns. Compaction
1915
+ // consolidates when it summarizes old messages (cache miss is expected).
1916
+ } finally {
1917
+ // kickDrainQueue never rejects: a drain failure here would otherwise be
1918
+ // an unhandled rejection that strands the queue with nothing left to
1919
+ // re-trigger it.
1920
+ void ctx.kickDrainQueue(
1921
+ yieldedForHandoff ? "checkpoint_handoff" : "loop_complete",
1922
+ "agent_loop_finally",
1923
+ );
1718
1924
  }
1719
-
1720
- emitToolProfilingSummary(ctx.conversationId, reqId);
1721
-
1722
- // Tear down the remaining per-turn state. Abort reliably drives the loop to
1723
- // this `finally` within a bounded time — cooperative signal propagation
1724
- // (provider fetch + tool race) backed by the abort watchdog — so a
1725
- // cancelled turn always unwinds before any resend can start a new one.
1726
- // There is therefore only ever one turn alive, and clearing the shared
1727
- // state below cannot clobber a concurrent turn.
1728
- ctx.onConfirmationOutcome = undefined;
1729
- ctx.surfaceActionRequestIds.delete(ctx.currentRequestId ?? "");
1730
- ctx.approvedViaPromptThisTurn = false;
1731
- ctx.currentRequestId = undefined;
1732
- ctx.currentActiveSurfaceId = undefined;
1733
- ctx.allowedToolNames = undefined;
1734
- ctx.diskPressureCleanupModeActive = false;
1735
- ctx.preactivatedSkillIds = undefined;
1736
- ctx.currentTurnOverrideProfile = undefined;
1737
- ctx.currentTurnModelProfileNoticeKey = undefined;
1738
- // Turn-scoped interactivity. Clear it so paths that bypass this loop (e.g.
1739
- // opportunity wakes calling `agentLoop.run` directly) don't inherit a stale
1740
- // value and instead fall back to live client state in the tool context.
1741
- ctx.currentTurnIsNonInteractive = undefined;
1742
- // Turn-scoped request origin. Clear so a later turn on a reused
1743
- // conversation cannot inherit a stale origin-scoped permission grant.
1744
- ctx.currentTurnRequestOrigin = undefined;
1745
- // Channel command intents (e.g. Telegram /start) are single-turn metadata.
1746
- // Clear at turn end so they never leak into subsequent unrelated messages.
1747
- ctx.commandIntent = undefined;
1748
- // The app on screen is view state owned by the message that reported it,
1749
- // not durable conversation state: nothing tells the daemon when the user
1750
- // closes an app. Clear both copies at turn end so a later turn that brings
1751
- // no client transport (a scheduled wake, a background follow-up) cannot
1752
- // claim the user is looking at an app they may have closed. Every inbound
1753
- // send re-applies it from transport before its own turn, including the
1754
- // queue drain, so live turns are unaffected.
1755
- ctx.visibleAppId = undefined;
1756
- ctx.currentTurnVisibleAppId = undefined;
1757
- // taskRunId scopes ephemeral task-run permissions to a single turn. Clear
1758
- // before drainQueue so queued/drained turns on a reused conversation can't
1759
- // inherit stale in-task-run scope from the turn that just finished.
1760
- ctx.taskRunId = undefined;
1761
-
1762
- // Consolidation deferred to compaction: keeping assistant + tool_result
1763
- // messages unconsolidated preserves the exact message structure sent to
1764
- // the API, enabling stable prefix caching across turns. Compaction
1765
- // consolidates when it summarizes old messages (cache miss is expected).
1766
-
1767
- // kickDrainQueue never rejects — a drain failure here would otherwise be
1768
- // an unhandled rejection that strands the queue with nothing left to
1769
- // re-trigger it.
1770
- void ctx.kickDrainQueue(
1771
- yieldedForHandoff ? "checkpoint_handoff" : "loop_complete",
1772
- "agent_loop_finally",
1773
- );
1774
1925
  }
1775
1926
  }
1776
1927
 
@@ -163,7 +163,7 @@ export async function resolveAssistantAttachments(
163
163
  const draft = assistantAttachments[i];
164
164
  let stored;
165
165
  try {
166
- stored = attachInlineAttachmentToMessage(
166
+ stored = await attachInlineAttachmentToMessage(
167
167
  lastAssistantMessageId,
168
168
  i,
169
169
  draft.filename,
@@ -6,6 +6,7 @@ import type {
6
6
  ConversationErrorCode,
7
7
  ConversationErrorEvent,
8
8
  } from "../api/events/conversation-error.js";
9
+ import { getIsPlatform } from "../config/env-registry.js";
9
10
  import {
10
11
  isImageDimensionsTooLargeError,
11
12
  isImageMediaTypeMismatchError,
@@ -352,7 +353,7 @@ function connectionResolutionUserMessage(
352
353
  case "provider_mismatch":
353
354
  return `${connection}${usedBy} is bound to a different provider than the profile declares. Update the profile's connection in ${fixPath}.`;
354
355
  case "missing_connection":
355
- return `No provider connection is configured${usedBy}. Add an API key or log in via ${fixPath}.`;
356
+ return `No provider connection is configured${usedBy}. Ask me to set one up right here, or add an API key in ${fixPath}.`;
356
357
  case "unroutable_managed_model":
357
358
  return `The model "${error.model ?? "<unset>"}"${usedBy} isn't served by the Vellum managed route. Pick a model from the Vellum catalog, or choose a concrete provider in ${fixPath}.`;
358
359
  case "missing_credential":
@@ -361,6 +362,12 @@ function connectionResolutionUserMessage(
361
362
  // doesn't.
362
363
  return `${connection}${usedBy} has no stored credential. Add an API key or reconnect it in ${fixPath}.`;
363
364
  case "platform_unauthenticated":
365
+ // A platform-managed assistant cannot log in or switch providers, and
366
+ // this classifier only sees the reason code, so one honest wording covers
367
+ // both the transient and the re-provision cases.
368
+ if (getIsPlatform()) {
369
+ return `${connection}${usedBy} is unavailable. If this persists, the platform credential may need to be re-provisioned on the Vellum platform.`;
370
+ }
364
371
  return `${connection}${usedBy} requires a Vellum platform login. Log in, or pick a different provider in ${fixPath}.`;
365
372
  case "model_incompatible":
366
373
  return `${error.model ? `Model "${error.model}"` : "The requested model"} isn't available on ${connectionName ? `connection "${connectionName}"` : "the configured connection"}${usedBy}. Pick a different model or connection in ${fixPath}.`;