@vellumai/assistant 0.12.3-staging.1 → 0.12.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 (560) hide show
  1. package/ARCHITECTURE.md +43 -29
  2. package/Dockerfile +63 -0
  3. package/docs/architecture/security.md +10 -5
  4. package/docs/architecture/turn-actor.md +2 -0
  5. package/docs/desktop-browser-cli.md +45 -4
  6. package/docs/live-voice-task-outcomes.md +100 -0
  7. package/docs/mode-sessions.md +183 -0
  8. package/docs/voice-input-diagnostics.md +58 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
  17. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
  18. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
  19. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  20. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  22. package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
  23. package/openapi.yaml +665 -14
  24. package/package.json +1 -1
  25. package/scripts/smoke-desktop-browser-cli.ts +10 -26
  26. package/scripts/smoke-desktop-window-theme.ts +2 -3
  27. package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
  28. package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
  29. package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
  30. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  31. package/src/__tests__/answered-question-persistence.test.ts +2 -0
  32. package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
  33. package/src/__tests__/approval-message-composer.test.ts +1 -118
  34. package/src/__tests__/approval-routes-http.test.ts +3 -0
  35. package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
  36. package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
  37. package/src/__tests__/assistant-event-targeting.test.ts +186 -0
  38. package/src/__tests__/assistant-stream-state.test.ts +29 -9
  39. package/src/__tests__/browser-fill-credential.test.ts +11 -22
  40. package/src/__tests__/btw-routes.test.ts +4 -1
  41. package/src/__tests__/build-persisted-content.test.ts +1 -1
  42. package/src/__tests__/call-conversation-messages.test.ts +5 -0
  43. package/src/__tests__/call-recovery.test.ts +7 -2
  44. package/src/__tests__/call-routes-http.test.ts +12 -0
  45. package/src/__tests__/call-store.test.ts +7 -2
  46. package/src/__tests__/channel-retry-sweep.test.ts +9 -0
  47. package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
  48. package/src/__tests__/checker.test.ts +11 -3
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
  50. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
  51. package/src/__tests__/computer-use-tools.test.ts +1 -233
  52. package/src/__tests__/config-model-image-provider.test.ts +39 -0
  53. package/src/__tests__/config-schema.test.ts +16 -0
  54. package/src/__tests__/config-watcher.test.ts +24 -0
  55. package/src/__tests__/context-search-agent-runner.test.ts +260 -1
  56. package/src/__tests__/context-search-fanout.test.ts +1 -1
  57. package/src/__tests__/context-search-types.test.ts +7 -7
  58. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
  59. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
  60. package/src/__tests__/conversation-agent-loop.test.ts +546 -16
  61. package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
  62. package/src/__tests__/conversation-evictor.test.ts +62 -37
  63. package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
  64. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
  65. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  66. package/src/__tests__/conversation-retry-route.test.ts +22 -0
  67. package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
  68. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
  69. package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
  70. package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
  73. package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
  74. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
  75. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
  76. package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
  77. package/src/__tests__/conversation-undo.test.ts +61 -0
  78. package/src/__tests__/credential-execution-client.test.ts +6 -6
  79. package/src/__tests__/credential-prompt-route.test.ts +35 -3
  80. package/src/__tests__/cu-unified-flow.test.ts +82 -1
  81. package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
  82. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
  83. package/src/__tests__/dm-backfill.test.ts +91 -1
  84. package/src/__tests__/document-create-route.test.ts +88 -0
  85. package/src/__tests__/document-sync-tags.test.ts +27 -0
  86. package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
  87. package/src/__tests__/events-tail-route.test.ts +25 -0
  88. package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
  89. package/src/__tests__/guardian-action-sweep.test.ts +16 -0
  90. package/src/__tests__/guardian-dispatch.test.ts +13 -0
  91. package/src/__tests__/headless-browser-interactions.test.ts +64 -20
  92. package/src/__tests__/heartbeat-service.test.ts +2 -59
  93. package/src/__tests__/helpers/mock-conversation.ts +21 -0
  94. package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
  95. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
  96. package/src/__tests__/host-cu-proxy.test.ts +46 -2
  97. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  98. package/src/__tests__/image-credentials.test.ts +49 -0
  99. package/src/__tests__/image-service-dispatcher.test.ts +45 -0
  100. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  101. package/src/__tests__/interrupt-on-send.test.ts +57 -43
  102. package/src/__tests__/interrupt-turn-note.test.ts +5 -6
  103. package/src/__tests__/llm-catalog-parity.test.ts +2 -2
  104. package/src/__tests__/media-generate-image.test.ts +49 -1
  105. package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
  106. package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
  107. package/src/__tests__/mode-session-history.test.ts +245 -0
  108. package/src/__tests__/mode-session-recovery.test.ts +222 -0
  109. package/src/__tests__/mode-session-wire.test.ts +141 -0
  110. package/src/__tests__/notification-guardian-path.test.ts +7 -0
  111. package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
  112. package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  115. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  116. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +5 -0
  118. package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
  119. package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
  120. package/src/__tests__/process-message-display-content.test.ts +56 -0
  121. package/src/__tests__/reaction-persistence.test.ts +5 -0
  122. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
  123. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
  124. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
  125. package/src/__tests__/secret-ingress-http.test.ts +2 -0
  126. package/src/__tests__/secure-keys.test.ts +43 -1
  127. package/src/__tests__/send-endpoint-busy.test.ts +163 -1
  128. package/src/__tests__/server-history-render.test.ts +91 -1
  129. package/src/__tests__/settings-routes.test.ts +50 -1
  130. package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
  132. package/src/__tests__/subagent-manager-notify.test.ts +1 -2
  133. package/src/__tests__/subagent-notify-parent.test.ts +146 -39
  134. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
  135. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
  136. package/src/__tests__/thread-backfill.test.ts +53 -0
  137. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  138. package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
  139. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  140. package/src/__tests__/twilio-routes.test.ts +1 -0
  141. package/src/__tests__/web-search-backend-failure.test.ts +1 -1
  142. package/src/activation/progress-store.test.ts +12 -7
  143. package/src/agent/loop.ts +99 -41
  144. package/src/api/events/assistant-turn-start.ts +3 -0
  145. package/src/api/events/generation-handoff.ts +2 -0
  146. package/src/api/events/message-complete.ts +2 -0
  147. package/src/api/events/notification-conversation-created.ts +14 -16
  148. package/src/api/events/notification-intent.ts +4 -2
  149. package/src/api/events/tool-result.ts +85 -0
  150. package/src/api/events/tool-use-start.ts +3 -0
  151. package/src/api/events/user-message-echo.ts +3 -0
  152. package/src/api/index.ts +31 -6
  153. package/src/api/mode-session.test.ts +131 -0
  154. package/src/api/mode-session.ts +148 -0
  155. package/src/api/remember-tool.ts +15 -0
  156. package/src/api/responses/conversation-message.ts +6 -0
  157. package/src/approvals/AGENTS.md +4 -2
  158. package/src/browser/__tests__/operations.test.ts +29 -0
  159. package/src/browser/operation-meta.ts +2 -1
  160. package/src/browser/operations.ts +29 -0
  161. package/src/browser/virtual-desktop-target.ts +2 -8
  162. package/src/bundler/bundle-signer.ts +1 -3
  163. package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
  164. package/src/calls/__tests__/call-controller.test.ts +94 -0
  165. package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
  166. package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
  167. package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
  168. package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
  169. package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
  170. package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
  171. package/src/calls/barge-in-guard.ts +5 -0
  172. package/src/calls/call-controller.ts +196 -1
  173. package/src/calls/call-domain.ts +4 -0
  174. package/src/calls/call-store.ts +87 -2
  175. package/src/calls/guardian-action-sweep.ts +22 -8
  176. package/src/calls/media-stream-audio-transcode.ts +1 -3
  177. package/src/calls/typesafe-noul.ts +156 -0
  178. package/src/calls/voice-control-protocol.ts +6 -0
  179. package/src/calls/voice-escalation-judge.ts +145 -0
  180. package/src/calls/voice-escalation-target.ts +10 -0
  181. package/src/calls/voice-leg-coordinator.ts +121 -3
  182. package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
  183. package/src/calls/voice-session-bridge.ts +256 -112
  184. package/src/calls/voice-triage-escalate.ts +7 -0
  185. package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
  186. package/src/cli/commands/__tests__/notifications.test.ts +35 -10
  187. package/src/cli/commands/browser.help.ts +4 -5
  188. package/src/cli/commands/browser.ts +0 -1
  189. package/src/cli/commands/clients.help.ts +46 -0
  190. package/src/cli/commands/clients.ts +116 -0
  191. package/src/cli/commands/credentials.help.ts +6 -1
  192. package/src/cli/commands/notifications.help.ts +16 -2
  193. package/src/cli/commands/notifications.ts +30 -8
  194. package/src/cli/commands/roadmap.help.ts +18 -21
  195. package/src/cli/commands/roadmap.ts +4 -21
  196. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
  197. package/src/cli/lib/bundled-marketplace.json +84 -34
  198. package/src/cli/lib/plugin-marketplace.ts +8 -0
  199. package/src/cli/lib/publish-plugin.ts +1 -2
  200. package/src/config/__tests__/default-provider.test.ts +3 -1
  201. package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
  202. package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
  203. package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
  204. package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
  205. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
  206. package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
  207. package/src/config/call-site-defaults.ts +9 -0
  208. package/src/config/feature-flag-registry.json +16 -0
  209. package/src/config/profile-text-generation.test.ts +2 -2
  210. package/src/config/schemas/__tests__/tools.test.ts +31 -0
  211. package/src/config/schemas/call-site-catalog.ts +14 -0
  212. package/src/config/schemas/channels.ts +0 -18
  213. package/src/config/schemas/llm.ts +3 -1
  214. package/src/config/schemas/mcp.ts +2 -1
  215. package/src/config/schemas/memory-retrospective.ts +11 -2
  216. package/src/config/schemas/services.ts +15 -6
  217. package/src/config/schemas/tools.ts +10 -0
  218. package/src/config/session-groups-gate.ts +21 -0
  219. package/src/config/skills.ts +2 -6
  220. package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
  221. package/src/conversations/message-consolidation.ts +102 -5
  222. package/src/credential-execution/ces-connect.test.ts +69 -0
  223. package/src/credential-execution/ces-connect.ts +115 -0
  224. package/src/credential-execution/ces-runtime.ts +55 -93
  225. package/src/credential-execution/executable-discovery.ts +1 -11
  226. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
  227. package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
  228. package/src/daemon/browser-mode-session.test.ts +402 -0
  229. package/src/daemon/browser-mode-session.ts +186 -0
  230. package/src/daemon/computer-use-mode-session.test.ts +210 -0
  231. package/src/daemon/computer-use-mode-session.ts +105 -0
  232. package/src/daemon/config-watcher.ts +10 -0
  233. package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
  234. package/src/daemon/conversation-agent-loop.ts +219 -25
  235. package/src/daemon/conversation-evictor.ts +5 -24
  236. package/src/daemon/conversation-history.ts +10 -0
  237. package/src/daemon/conversation-interrupt-repair.ts +4 -43
  238. package/src/daemon/conversation-interrupt.ts +7 -11
  239. package/src/daemon/conversation-messaging.ts +58 -5
  240. package/src/daemon/conversation-mode-session.test.ts +1667 -0
  241. package/src/daemon/conversation-mode-session.ts +1008 -0
  242. package/src/daemon/conversation-process.ts +5 -0
  243. package/src/daemon/conversation-runtime-assembly.ts +10 -20
  244. package/src/daemon/conversation-skill-tools.ts +1 -0
  245. package/src/daemon/conversation-store.ts +4 -4
  246. package/src/daemon/conversation-surface-snapshots.ts +135 -0
  247. package/src/daemon/conversation-surfaces.ts +113 -5
  248. package/src/daemon/conversation-tool-setup.ts +8 -3
  249. package/src/daemon/conversation.ts +143 -41
  250. package/src/daemon/handlers/config-model.ts +7 -5
  251. package/src/daemon/handlers/shared.ts +55 -16
  252. package/src/daemon/host-cu-proxy.ts +51 -22
  253. package/src/daemon/lifecycle.ts +30 -13
  254. package/src/daemon/message-protocol.ts +0 -1
  255. package/src/daemon/message-provenance.ts +28 -4
  256. package/src/daemon/mode-session-startup-recovery.ts +23 -0
  257. package/src/daemon/mode-session-tracking.ts +54 -0
  258. package/src/daemon/process-message.ts +13 -4
  259. package/src/daemon/virtual-desktop-context.ts +21 -0
  260. package/src/desktop/__tests__/fake-desktop.ts +1 -0
  261. package/src/desktop/desktop-accessibility-script.ts +88 -0
  262. package/src/desktop/desktop-accessibility.test.ts +160 -0
  263. package/src/desktop/desktop-accessibility.ts +169 -0
  264. package/src/desktop/desktop-automation-lease.test.ts +76 -68
  265. package/src/desktop/desktop-automation-lease.ts +41 -50
  266. package/src/desktop/desktop-browser-endpoint.ts +4 -0
  267. package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
  268. package/src/desktop/desktop-computer-use.test.ts +449 -0
  269. package/src/desktop/desktop-computer-use.ts +502 -0
  270. package/src/desktop/desktop-dependencies.ts +47 -293
  271. package/src/desktop/desktop-display.ts +2 -2
  272. package/src/desktop/desktop-panel-config.test.ts +13 -3
  273. package/src/desktop/desktop-panel-config.ts +109 -15
  274. package/src/desktop/desktop-session-manager.test.ts +69 -21
  275. package/src/desktop/desktop-session-manager.ts +76 -22
  276. package/src/desktop/desktop-stream-bridge.test.ts +11 -122
  277. package/src/desktop/desktop-stream-bridge.ts +6 -8
  278. package/src/desktop/desktop-window-theme.ts +5 -25
  279. package/src/desktop/virtual-desktop-feature.ts +52 -2
  280. package/src/desktop/virtual-desktop-platform.test.ts +5 -9
  281. package/src/documents/document-store.ts +23 -0
  282. package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
  283. package/src/heartbeat/heartbeat-service.ts +5 -33
  284. package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
  285. package/src/ipc/assistant-server.ts +2 -0
  286. package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
  287. package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
  288. package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
  289. package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
  290. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
  291. package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
  292. package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
  293. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
  294. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
  295. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
  296. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
  297. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
  298. package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
  299. package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
  300. package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
  301. package/src/live-voice/__tests__/session-controls.test.ts +23 -0
  302. package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
  303. package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
  304. package/src/live-voice/camera-mode-session.test.ts +265 -0
  305. package/src/live-voice/camera-mode-session.ts +277 -0
  306. package/src/live-voice/continuation-judge.ts +123 -0
  307. package/src/live-voice/live-activity-reporter.ts +115 -6
  308. package/src/live-voice/live-voice-manager.ts +36 -1
  309. package/src/live-voice/live-voice-photo.ts +88 -7
  310. package/src/live-voice/live-voice-session-manager.ts +15 -0
  311. package/src/live-voice/live-voice-session.ts +1443 -747
  312. package/src/live-voice/live-voice-tts.ts +22 -4
  313. package/src/live-voice/microphone-idle-gate.ts +56 -0
  314. package/src/live-voice/protocol.ts +171 -4
  315. package/src/live-voice/session-controls.ts +11 -3
  316. package/src/live-voice/task-outcomes.ts +131 -0
  317. package/src/live-voice/voice-input-diagnostics.ts +117 -0
  318. package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
  319. package/src/mcp/__tests__/tool-caps.test.ts +28 -1
  320. package/src/mcp/manager.ts +3 -0
  321. package/src/mcp/tool-caps.ts +12 -4
  322. package/src/media/__tests__/image-models.test.ts +58 -0
  323. package/src/media/image-credentials.ts +5 -0
  324. package/src/media/image-models.ts +52 -0
  325. package/src/media/image-service.ts +8 -0
  326. package/src/media/openrouter-image-service.test.ts +133 -0
  327. package/src/media/openrouter-image-service.ts +153 -0
  328. package/src/media/types.ts +1 -1
  329. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
  330. package/src/messaging/providers/discord/send.ts +4 -17
  331. package/src/messaging/providers/discord/transport.ts +2 -2
  332. package/src/messaging/providers/outlook/client.ts +16 -22
  333. package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
  334. package/src/messaging/providers/telegram-bot/send.ts +10 -7
  335. package/src/messaging/providers/telegram-bot/transport.ts +3 -10
  336. package/src/messaging/providers/whatsapp/send.test.ts +169 -4
  337. package/src/monitoring/__tests__/control.test.ts +17 -5
  338. package/src/monitoring/control.ts +15 -5
  339. package/src/notifications/AGENTS.md +1 -1
  340. package/src/notifications/README.md +83 -99
  341. package/src/notifications/__tests__/broadcaster.test.ts +1 -1
  342. package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
  343. package/src/notifications/__tests__/copy-composer.test.ts +1 -1
  344. package/src/notifications/__tests__/decision-engine.test.ts +79 -2
  345. package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
  346. package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
  347. package/src/notifications/__tests__/notification-utils.test.ts +18 -0
  348. package/src/notifications/adapters/macos.ts +37 -46
  349. package/src/notifications/broadcaster.ts +5 -6
  350. package/src/notifications/channel-allowlist.ts +44 -0
  351. package/src/notifications/copy-composer.ts +0 -23
  352. package/src/notifications/decision-engine.ts +24 -14
  353. package/src/notifications/deterministic-checks.ts +1 -1
  354. package/src/notifications/emit-signal.ts +123 -37
  355. package/src/notifications/home-feed-side-effect.ts +9 -7
  356. package/src/notifications/notification-utils.ts +19 -0
  357. package/src/notifications/signal.ts +4 -19
  358. package/src/notifications/types.ts +15 -0
  359. package/src/oauth/connection-resolver.ts +1 -2
  360. package/src/oauth/oauth-store.ts +1 -3
  361. package/src/oauth/provider-serializer.ts +7 -0
  362. package/src/oauth/seed-providers.ts +30 -0
  363. package/src/onboarding/onboarding-events-store.ts +99 -12
  364. package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
  365. package/src/persistence/client-connection-events-store.ts +377 -0
  366. package/src/persistence/conversation-crud.ts +55 -6
  367. package/src/persistence/conversation-mode-sessions.test.ts +424 -0
  368. package/src/persistence/conversation-mode-sessions.ts +460 -0
  369. package/src/persistence/conversation-queries.ts +6 -19
  370. package/src/persistence/llm-usage-store.ts +3 -9
  371. package/src/persistence/message-metadata.ts +25 -0
  372. package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
  373. package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
  374. package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
  375. package/src/persistence/schema/conversations.ts +67 -0
  376. package/src/persistence/schema/infrastructure.ts +29 -0
  377. package/src/persistence/steps.ts +17 -0
  378. package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
  379. package/src/plugin-api/effective-context-window.ts +33 -0
  380. package/src/plugin-api/index.ts +24 -5
  381. package/src/plugin-api/plugin-name-env.ts +125 -0
  382. package/src/plugin-api/resolve-credential.ts +79 -20
  383. package/src/plugin-api/vision-support.test.ts +22 -0
  384. package/src/plugin-api/vision-support.ts +17 -5
  385. package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
  386. package/src/plugins/active-plugin-env.ts +51 -0
  387. package/src/plugins/defaults/index.ts +24 -0
  388. package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
  389. package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
  390. package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
  391. package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
  392. package/src/plugins/defaults/memory/context-search/format.ts +51 -7
  393. package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
  394. package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
  395. package/src/plugins/defaults/memory/context-search/search.ts +8 -14
  396. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
  397. package/src/plugins/defaults/memory/context-search/types.ts +1 -11
  398. package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
  399. package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
  400. package/src/plugins/defaults/memory/graph/tools.ts +35 -64
  401. package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
  402. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
  403. package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
  404. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
  405. package/src/plugins/defaults/memory/tools.test.ts +124 -9
  406. package/src/plugins/defaults/memory/tools.ts +38 -11
  407. package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
  408. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
  409. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
  410. package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
  411. package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
  412. package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
  413. package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
  414. package/src/plugins/defaults/memory/worker-control.ts +15 -5
  415. package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
  416. package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
  417. package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
  418. package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
  419. package/src/plugins/defaults/task-progress/package.json +14 -0
  420. package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
  421. package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
  422. package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
  423. package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
  424. package/src/providers/anthropic/client.ts +10 -1
  425. package/src/providers/anthropic-gateway-shared.ts +6 -0
  426. package/src/providers/fetch-provider-catalog.ts +11 -0
  427. package/src/providers/gemini/client.ts +25 -8
  428. package/src/providers/inference/adapter-factory.ts +1 -1
  429. package/src/providers/jev/client.test.ts +10 -0
  430. package/src/providers/jev/client.ts +20 -4
  431. package/src/providers/model-catalog.ts +4 -3
  432. package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
  433. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
  434. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
  435. package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
  436. package/src/providers/openai/chat-completions-provider.ts +100 -29
  437. package/src/providers/openai/google-thought-signature.ts +0 -6
  438. package/src/providers/openai/responses-provider.ts +23 -1
  439. package/src/providers/ratelimit.ts +1 -3
  440. package/src/providers/search-provider-catalog.ts +24 -0
  441. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
  442. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
  443. package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
  444. package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
  445. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
  446. package/src/providers/types.ts +5 -0
  447. package/src/routes/control.ts +7 -5
  448. package/src/runtime/AGENTS.md +4 -2
  449. package/src/runtime/actor-trust-resolver.ts +3 -3
  450. package/src/runtime/agent-wake.ts +1 -1
  451. package/src/runtime/approval-message-composer.ts +3 -69
  452. package/src/runtime/assistant-event-hub.ts +73 -75
  453. package/src/runtime/assistant-event-publish-options.ts +7 -0
  454. package/src/runtime/assistant-event-targeting.ts +79 -0
  455. package/src/runtime/assistant-stream-state.ts +13 -100
  456. package/src/runtime/background-job-runner.ts +5 -5
  457. package/src/runtime/channel-retry-sweep.ts +1 -0
  458. package/src/runtime/guardian-action-message-composer.ts +8 -161
  459. package/src/runtime/guardian-reply-router.ts +28 -32
  460. package/src/runtime/http-types.ts +6 -0
  461. package/src/runtime/message-composer-types.ts +3 -61
  462. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
  463. package/src/runtime/migrations/vbundle-builder.ts +24 -1
  464. package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
  465. package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
  466. package/src/runtime/migrations/vbundle-importer.ts +4 -1
  467. package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
  468. package/src/runtime/migrations/vbundle-validator.ts +1 -0
  469. package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
  470. package/src/runtime/pending-interactions.ts +29 -0
  471. package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
  472. package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
  473. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
  474. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
  475. package/src/runtime/routes/browser-context.ts +31 -11
  476. package/src/runtime/routes/browser-routes.ts +43 -4
  477. package/src/runtime/routes/btw-routes.ts +9 -4
  478. package/src/runtime/routes/client-routes.ts +108 -0
  479. package/src/runtime/routes/conversation-management-routes.ts +4 -1
  480. package/src/runtime/routes/conversation-routes.ts +113 -27
  481. package/src/runtime/routes/credential-prompt-routes.ts +4 -3
  482. package/src/runtime/routes/credential-routes.ts +1 -2
  483. package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
  484. package/src/runtime/routes/desktop-setup-routes.ts +4 -6
  485. package/src/runtime/routes/documents-routes.ts +51 -0
  486. package/src/runtime/routes/email-routes.ts +3 -7
  487. package/src/runtime/routes/events-routes.ts +75 -54
  488. package/src/runtime/routes/identity-routes.ts +1 -5
  489. package/src/runtime/routes/image-generation-routes.ts +8 -14
  490. package/src/runtime/routes/inbound-message-handler.ts +35 -38
  491. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
  492. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
  493. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
  494. package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
  495. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
  496. package/src/runtime/routes/llm-context-normalization.ts +1 -7
  497. package/src/runtime/routes/migration-routes.ts +73 -2
  498. package/src/runtime/routes/notification-routes.ts +10 -0
  499. package/src/runtime/routes/oauth-providers.ts +7 -0
  500. package/src/runtime/routes/plugins-routes.ts +6 -0
  501. package/src/runtime/routes/roadmap-routes.ts +47 -8
  502. package/src/runtime/routes/secret-routes.ts +1 -1
  503. package/src/runtime/routes/settings-routes.ts +10 -4
  504. package/src/runtime/routes/watcher-routes.ts +1 -3
  505. package/src/runtime/trust-class.test.ts +7 -6
  506. package/src/runtime/trust-class.ts +9 -17
  507. package/src/schedule/__tests__/worker-control.test.ts +36 -5
  508. package/src/schedule/worker-control.ts +15 -5
  509. package/src/security/secure-keys.ts +53 -103
  510. package/src/skills/clawhub.ts +3 -8
  511. package/src/skills/managed-store.ts +1 -5
  512. package/src/stt/types.ts +4 -0
  513. package/src/subagent/notify.ts +54 -34
  514. package/src/subagent/parent-notification.ts +6 -0
  515. package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
  516. package/src/telemetry/phone-call-funnel.ts +144 -0
  517. package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
  518. package/src/tools/ask-question/ask-question-tool.ts +6 -4
  519. package/src/tools/browser/browser-execution.ts +22 -59
  520. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
  521. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
  522. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
  523. package/src/tools/client-os.ts +15 -0
  524. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  525. package/src/tools/computer-use/target.ts +42 -0
  526. package/src/tools/document/document-tool.ts +13 -18
  527. package/src/tools/execution-target.ts +14 -16
  528. package/src/tools/host-terminal/host-shell.ts +4 -1
  529. package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
  530. package/src/tools/network/__tests__/web-search.test.ts +203 -0
  531. package/src/tools/network/web-fetch.ts +274 -7
  532. package/src/tools/network/web-search.ts +468 -6
  533. package/src/tools/permission-checker.ts +3 -3
  534. package/src/tools/policy-context.ts +3 -1
  535. package/src/tools/skills/load.ts +2 -1
  536. package/src/tools/skills/sandbox-runner.ts +11 -2
  537. package/src/tools/skills/skill-script-runner.ts +7 -0
  538. package/src/tools/skills/skill-tool-factory.ts +15 -1
  539. package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
  540. package/src/tools/terminal/safe-env.ts +4 -0
  541. package/src/tools/terminal/shell.ts +2 -0
  542. package/src/tools/tool-approval-handler.ts +5 -1
  543. package/src/tools/tool-types.ts +3 -3
  544. package/src/tools/types.ts +8 -2
  545. package/src/util/__tests__/worker-process-command.test.ts +5 -4
  546. package/src/util/abort-reasons.ts +18 -26
  547. package/src/util/canonicalize-identity.test.ts +21 -0
  548. package/src/util/canonicalize-identity.ts +16 -0
  549. package/src/util/errors.ts +13 -3
  550. package/src/util/provider-error-patterns.ts +3 -0
  551. package/src/util/worker-process.ts +77 -35
  552. package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
  553. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
  554. package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
  555. package/src/workspace/migrations/registry.ts +2 -0
  556. package/src/__tests__/computer-use-window-schema.test.ts +0 -24
  557. package/src/api/events/heartbeat-alert.ts +0 -20
  558. package/src/daemon/message-types/web-activity.ts +0 -65
  559. package/src/desktop/desktop-dependencies.test.ts +0 -131
  560. package/src/tools/computer-use/definitions.ts +0 -607
@@ -15,6 +15,7 @@ import type {
15
15
  AgentEvent,
16
16
  AgentLoopExitReason,
17
17
  CheckpointDecision,
18
+ PreparedModelCall,
18
19
  } from "../agent/loop.js";
19
20
  import { createAssistantMessage } from "../agent/message-types.js";
20
21
  import type { AssistantEvent } from "../api/index.js";
@@ -135,6 +136,7 @@ import {
135
136
  } from "./conversation-runtime-assembly.js";
136
137
  import type { CurrentTurnSurface } from "./conversation-surfaces.js";
137
138
  import {
139
+ blockingPendingSurfaceIds,
138
140
  hasBlockingPendingSurface,
139
141
  markSurfaceCompleted,
140
142
  settleRunningTaskProgressSurfaces,
@@ -152,6 +154,7 @@ import {
152
154
  unregisterInflightTurn,
153
155
  } from "./inflight-turn-registry.js";
154
156
  import type { UsageStats } from "./message-protocol.js";
157
+ import { bestEffortModeSessionTracking } from "./mode-session-tracking.js";
155
158
  import {
156
159
  persistReactionRecords,
157
160
  type QueuedReactionRecord,
@@ -291,6 +294,89 @@ function turnEndedAwaitingUser(ctx: Conversation): boolean {
291
294
  );
292
295
  }
293
296
 
297
+ function recordModeSessionStructuralWaits(
298
+ ctx: Conversation,
299
+ turnId: string,
300
+ ): boolean {
301
+ let recorded = false;
302
+ for (const interaction of getPendingInteractionsByConversation(
303
+ ctx.conversationId,
304
+ )) {
305
+ const kind =
306
+ interaction.kind === "acp_confirmation"
307
+ ? "confirmation"
308
+ : interaction.kind;
309
+ if (kind !== "confirmation" && kind !== "question" && kind !== "secret") {
310
+ continue;
311
+ }
312
+ recorded =
313
+ ctx.modeSessions.recordStructuralWait(turnId, {
314
+ kind,
315
+ responseId: interaction.requestId,
316
+ }) || recorded;
317
+ }
318
+ for (const surfaceId of blockingPendingSurfaceIds(ctx)) {
319
+ recorded =
320
+ ctx.modeSessions.recordStructuralWait(turnId, {
321
+ kind: "surface",
322
+ responseId: surfaceId,
323
+ }) || recorded;
324
+ }
325
+ return recorded;
326
+ }
327
+
328
+ function settleModeSessionTurn(
329
+ ctx: Conversation,
330
+ turnId: string,
331
+ fallback: { status: "completed" | "interrupted"; endReason: string },
332
+ ): void {
333
+ try {
334
+ if (!ctx.modeSessions.getTurnOwner(turnId)) {
335
+ ctx.modeSessions.releaseTurn(turnId);
336
+ return;
337
+ }
338
+ if (
339
+ fallback.status === "completed" &&
340
+ recordModeSessionStructuralWaits(ctx, turnId)
341
+ ) {
342
+ ctx.modeSessions.releaseTurn(turnId);
343
+ return;
344
+ }
345
+ if (ctx.modeSessions.keepsSessionOpenAfterTurn(turnId)) {
346
+ ctx.modeSessions.releaseTurn(turnId);
347
+ return;
348
+ }
349
+ const disposition = ctx.modeSessions.getTerminalDisposition(turnId);
350
+ if (disposition) {
351
+ ctx.modeSessions.releaseTurn(turnId, fallback);
352
+ return;
353
+ }
354
+ const finalized = ctx.modeSessions.finalizeTurn({
355
+ turnId,
356
+ status: fallback.status,
357
+ endedAt: Date.now(),
358
+ endReason: fallback.endReason,
359
+ lastActivityAt: Date.now(),
360
+ });
361
+ if (!finalized) {
362
+ ctx.modeSessions.releaseTurn(turnId, fallback);
363
+ }
364
+ } catch (err) {
365
+ log.warn(
366
+ { err, conversationId: ctx.conversationId, turnId },
367
+ "Mode-session turn settlement failed",
368
+ );
369
+ try {
370
+ ctx.modeSessions.releaseTurn(turnId, fallback);
371
+ } catch (releaseErr) {
372
+ log.warn(
373
+ { err: releaseErr, conversationId: ctx.conversationId, turnId },
374
+ "Mode-session turn cleanup failed",
375
+ );
376
+ }
377
+ }
378
+ }
379
+
294
380
  // ── abort watchdog ───────────────────────────────────────────────────
295
381
 
296
382
  /**
@@ -392,12 +478,16 @@ export async function runAgentLoopImpl(
392
478
  */
393
479
  replyDeliveredInAppOnly?: boolean;
394
480
  /**
395
- * LLM call-site identifier threaded into the per-call provider config.
396
- * Adapter callers (heartbeat, filing, scheduler, etc.) pass their own
397
- * call-site id so the resolver picks `llm.callSites.<id>`. When unset,
398
- * the agent loop defaults to `'mainAgent'` for user-initiated turns.
481
+ * Semantic call-site identifier for the turn. It also selects provider
482
+ * configuration unless `inferenceCallSite` is set. When unset, the agent
483
+ * loop defaults to `'mainAgent'` for user-initiated turns.
399
484
  */
400
485
  callSite?: LLMCallSite;
486
+ /**
487
+ * Provider configuration source when it differs from the turn's semantic
488
+ * call site. The semantic call site still controls tools and UI behavior.
489
+ */
490
+ inferenceCallSite?: LLMCallSite;
401
491
  /**
402
492
  * Optional ad-hoc inference-profile override applied to every LLM call
403
493
  * the loop issues. When set, the agent loop sets
@@ -413,6 +503,8 @@ export async function runAgentLoopImpl(
413
503
  * sites. Used when a caller explicitly pins a background run to a profile.
414
504
  */
415
505
  forceOverrideProfile?: boolean;
506
+ /** Observe the first finalized model request without delaying it. */
507
+ onFirstModelCallPrepared?: (prepared: PreparedModelCall) => void;
416
508
  /**
417
509
  * Origin tag of this turn (the conversation's `TitleOrigin`, e.g.
418
510
  * "memory_consolidation"), threaded from `runBackgroundJob`. Exposed on
@@ -472,12 +564,12 @@ export async function runAgentLoopImpl(
472
564
  ctx.currentTurnIsNonInteractive = isNonInteractive;
473
565
 
474
566
  // Default user-initiated turns to the `mainAgent` call site; other invocation
475
- // contexts (heartbeat, filing, analyze, etc.) pass their own `callSite`. The
476
- // provider layer resolves provider/model/maxTokens via `resolveCallSiteConfig`,
477
- // picking up any user overrides under `llm.callSites.<id>` (falling back to
478
- // the shipped call-site defaults when absent). `resolveTurnCallSite` keeps subagent
479
- // conversations on `subagentSpawn` when no call site is supplied.
567
+ // contexts pass their own semantic `callSite`. Provider configuration uses
568
+ // that site unless a caller supplies `inferenceCallSite` separately.
569
+ // `resolveTurnCallSite` keeps subagent conversations on `subagentSpawn` when
570
+ // no semantic call site is supplied.
480
571
  const turnCallSite = resolveTurnCallSite(options?.callSite, ctx);
572
+ const inferenceCallSite = options?.inferenceCallSite ?? turnCallSite;
481
573
  // Expose the turn's call site on the live conversation so the runtime
482
574
  // injection assembly self-resolves it for the turn's plugin contexts. Set
483
575
  // before the prompt sync below: the tool-gated reply section and the tool
@@ -582,7 +674,11 @@ export async function runAgentLoopImpl(
582
674
  isResolvableProvider: dispatchProviderResolvable,
583
675
  };
584
676
  const { config: resolved, profileName } =
585
- resolveCallSiteConfigWithProfile(turnCallSite, config.llm, resolveOpts);
677
+ resolveCallSiteConfigWithProfile(
678
+ inferenceCallSite,
679
+ config.llm,
680
+ resolveOpts,
681
+ );
586
682
  let connectionName = resolved.provider_connection;
587
683
  try {
588
684
  connectionName =
@@ -617,7 +713,7 @@ export async function runAgentLoopImpl(
617
713
 
618
714
  const effectiveContextWindow = resolveEffectiveContextWindow({
619
715
  llm: config.llm,
620
- callSite: turnCallSite,
716
+ callSite: inferenceCallSite,
621
717
  overrideProfile: turnOverrideProfile ?? undefined,
622
718
  forceOverrideProfile,
623
719
  selectionSeed: ctx.conversationId,
@@ -636,7 +732,7 @@ export async function runAgentLoopImpl(
636
732
  };
637
733
 
638
734
  let currentContextWindowConfig = contextWindowConfigFromEffective(
639
- resolveCallSiteConfig(turnCallSite, config.llm, {
735
+ resolveCallSiteConfig(inferenceCallSite, config.llm, {
640
736
  overrideProfile: turnOverrideProfile ?? undefined,
641
737
  forceOverrideProfile,
642
738
  selectionSeed: ctx.conversationId,
@@ -652,13 +748,13 @@ export async function runAgentLoopImpl(
652
748
  if (currentOverrideProfile !== appliedOverrideProfile) {
653
749
  currentEffectiveContextWindow = resolveEffectiveContextWindow({
654
750
  llm: config.llm,
655
- callSite: turnCallSite,
751
+ callSite: inferenceCallSite,
656
752
  overrideProfile: currentOverrideProfile,
657
753
  forceOverrideProfile,
658
754
  selectionSeed: ctx.conversationId,
659
755
  });
660
756
  currentContextWindowConfig = contextWindowConfigFromEffective(
661
- resolveCallSiteConfig(turnCallSite, config.llm, {
757
+ resolveCallSiteConfig(inferenceCallSite, config.llm, {
662
758
  overrideProfile: currentOverrideProfile,
663
759
  forceOverrideProfile,
664
760
  onResolutionFallback: logResolutionFallback,
@@ -677,8 +773,6 @@ export async function runAgentLoopImpl(
677
773
  ctx.currentTurnOverrideProfile = currentOverrideProfile;
678
774
  return currentOverrideProfile;
679
775
  };
680
- const resolveCurrentOverrideProfile = (): string | undefined =>
681
- refreshCurrentProfileState();
682
776
  const resolveCurrentMaxInputTokens = (): number => {
683
777
  refreshCurrentProfileState();
684
778
  return currentEffectiveContextWindow.maxInputTokens;
@@ -704,7 +798,7 @@ export async function runAgentLoopImpl(
704
798
  };
705
799
 
706
800
  // Initial value for `createToolExecutor` to read into
707
- // `ToolContext.overrideProfile`. `resolveCurrentOverrideProfile` refreshes
801
+ // `ToolContext.overrideProfile`. `refreshCurrentProfileState` refreshes
708
802
  // this between model calls so a confirmed profile session opened by a tool
709
803
  // applies to later tool executions and nested subagents in the same turn.
710
804
  ctx.currentTurnOverrideProfile = turnOverrideProfile;
@@ -980,6 +1074,7 @@ export async function runAgentLoopImpl(
980
1074
  publishConversationMessagesChanged(ctx.conversationId);
981
1075
  }
982
1076
  };
1077
+ let eventHandlerDeps: EventHandlerDeps | undefined;
983
1078
 
984
1079
  try {
985
1080
  closeTurnFinalization = beginTurnFinalization(ctx.conversationId);
@@ -1064,13 +1159,19 @@ export async function runAgentLoopImpl(
1064
1159
  if (!markSurfaceCompleted(ctx, surfaceId, "Dismissed")) {
1065
1160
  continue;
1066
1161
  }
1162
+ ctx.pendingSurfaceActions.delete(surfaceId);
1163
+ bestEffortModeSessionTracking("stale surface wait invalidation", () =>
1164
+ ctx.modeSessions.invalidateStructuralWait({
1165
+ kind: "surface",
1166
+ responseId: surfaceId,
1167
+ }),
1168
+ );
1067
1169
  onEvent({
1068
1170
  type: "ui_surface_complete",
1069
1171
  conversationId: ctx.conversationId,
1070
1172
  surfaceId,
1071
1173
  summary: "Dismissed",
1072
1174
  });
1073
- ctx.pendingSurfaceActions.delete(surfaceId);
1074
1175
  }
1075
1176
  }
1076
1177
 
@@ -1305,13 +1406,13 @@ export async function runAgentLoopImpl(
1305
1406
  // a hand-rolled chain would credit profiles the resolver never consulted
1306
1407
  // (e.g. activeProfile on a non-mainAgent turn).
1307
1408
  const effectiveProfileKey =
1308
- selectWinningProfile(turnCallSite, config.llm, {
1409
+ selectWinningProfile(inferenceCallSite, config.llm, {
1309
1410
  ...(turnOverrideProfile != null
1310
1411
  ? { overrideProfile: turnOverrideProfile }
1311
1412
  : {}),
1312
1413
  selectionSeed: ctx.conversationId,
1313
1414
  }).profileName ??
1314
- resolveProfilelessModelKey(turnCallSite, config.llm, {
1415
+ resolveProfilelessModelKey(inferenceCallSite, config.llm, {
1315
1416
  ...(turnOverrideProfile != null
1316
1417
  ? { overrideProfile: turnOverrideProfile }
1317
1418
  : {}),
@@ -1412,6 +1513,7 @@ export async function runAgentLoopImpl(
1412
1513
  latencyTracker,
1413
1514
  errorAttribution: turnErrorAttribution,
1414
1515
  };
1516
+ eventHandlerDeps = deps;
1415
1517
  const eventHandler = (event: AgentEvent): Promise<void> => {
1416
1518
  if (
1417
1519
  event.type === "agent_loop_exit" &&
@@ -1449,7 +1551,10 @@ export async function runAgentLoopImpl(
1449
1551
 
1450
1552
  turnStarted = true;
1451
1553
 
1452
- rlog.info({ callSite: turnCallSite }, "Starting agent loop run");
1554
+ rlog.info(
1555
+ { callSite: turnCallSite, inferenceCallSite },
1556
+ "Starting agent loop run",
1557
+ );
1453
1558
 
1454
1559
  // Trust snapshot the loop forwards to its mid-loop in-place compaction
1455
1560
  // (scoping the compactor's image manifest) and the post-compaction
@@ -1459,6 +1564,18 @@ export async function runAgentLoopImpl(
1459
1564
  // fields self-resolve from its own conversation id.
1460
1565
  const loopTrust = ctx.getTurnOrRestingTrust() ?? FALLBACK_TURN_TRUST;
1461
1566
 
1567
+ const notifyFirstModelCallPrepared = options?.onFirstModelCallPrepared;
1568
+ let firstModelCallPrepared = false;
1569
+ const onModelCallPrepared = notifyFirstModelCallPrepared
1570
+ ? (prepared: PreparedModelCall): void => {
1571
+ if (firstModelCallPrepared) {
1572
+ return;
1573
+ }
1574
+ firstModelCallPrepared = true;
1575
+ notifyFirstModelCallPrepared(prepared);
1576
+ }
1577
+ : undefined;
1578
+
1462
1579
  /**
1463
1580
  * Shared closure: runs the agent loop with the wrapper's turn context and
1464
1581
  * maps the loop's returned checkpoint pause-reason into the wrapper's yield
@@ -1481,12 +1598,14 @@ export async function runAgentLoopImpl(
1481
1598
  requestId: reqId,
1482
1599
  onCheckpoint,
1483
1600
  callSite: turnCallSite,
1601
+ inferenceCallSite,
1484
1602
  suppressAssistantText: sendUserMessageActive,
1485
1603
  supportsDynamicUi: conversationSupportsDynamicUi(ctx),
1486
1604
  trust: loopTrust,
1487
1605
  overrideProfile: turnOverrideProfile,
1488
1606
  ...(forceOverrideProfile ? { forceOverrideProfile: true } : {}),
1489
- resolveOverrideProfile: resolveCurrentOverrideProfile,
1607
+ resolveOverrideProfile: refreshCurrentProfileState,
1608
+ ...(onModelCallPrepared !== undefined ? { onModelCallPrepared } : {}),
1490
1609
  resolveContextWindow,
1491
1610
  compactInPlace,
1492
1611
  isNonInteractive,
@@ -1585,13 +1704,33 @@ export async function runAgentLoopImpl(
1585
1704
  userMessageInterface: capturedTurnInterfaceContext.userMessageInterface,
1586
1705
  assistantMessageInterface:
1587
1706
  capturedTurnInterfaceContext.assistantMessageInterface,
1707
+ modeSession: ctx.modeSessions.getTurnOwner(reqId),
1588
1708
  };
1589
- await finalizePendingToolResultRow(
1709
+ const toolResultRowId = await finalizePendingToolResultRow(
1590
1710
  state,
1591
1711
  ctx.conversationId,
1592
1712
  toolResultMetadata,
1593
1713
  rlog,
1594
1714
  );
1715
+ if (toolResultRowId) {
1716
+ bestEffortModeSessionTracking(
1717
+ "remaining tool result finalization",
1718
+ () => {
1719
+ const toolResultRow = getMessageById(
1720
+ toolResultRowId,
1721
+ ctx.conversationId,
1722
+ );
1723
+ if (toolResultRow) {
1724
+ ctx.modeSessions.trackPersistedRow(
1725
+ reqId,
1726
+ toolResultRowId,
1727
+ toolResultRow.createdAt,
1728
+ { startsDisplayBoundary: false },
1729
+ );
1730
+ }
1731
+ },
1732
+ );
1733
+ }
1595
1734
  }
1596
1735
 
1597
1736
  // Persist the budget_yield_unrecovered notice now that any pending
@@ -1612,6 +1751,7 @@ export async function runAgentLoopImpl(
1612
1751
  userMessageInterface: capturedTurnInterfaceContext.userMessageInterface,
1613
1752
  assistantMessageInterface:
1614
1753
  capturedTurnInterfaceContext.assistantMessageInterface,
1754
+ modeSession: ctx.modeSessions.getTurnOwner(reqId),
1615
1755
  };
1616
1756
  let yieldNoticePersistedId: string | null = null;
1617
1757
  try {
@@ -1622,6 +1762,13 @@ export async function runAgentLoopImpl(
1622
1762
  { metadata: yieldNoticeMetadata },
1623
1763
  );
1624
1764
  yieldNoticePersistedId = yieldRow.id;
1765
+ bestEffortModeSessionTracking("budget yield notice persistence", () =>
1766
+ ctx.modeSessions.trackPersistedRow(
1767
+ reqId,
1768
+ yieldRow.id,
1769
+ yieldRow.createdAt,
1770
+ ),
1771
+ );
1625
1772
  } catch (err) {
1626
1773
  // Non-fatal — a DB hiccup must not escalate a budget-yield exit into
1627
1774
  // a turn-level throw. The live SSE event was already emitted, so the
@@ -1796,6 +1943,7 @@ export async function runAgentLoopImpl(
1796
1943
  messageKind: PROVIDER_ERROR_MESSAGE_KIND,
1797
1944
  providerErrorCode: state.providerErrorCode ?? undefined,
1798
1945
  providerErrorCategory: state.providerErrorCategory ?? undefined,
1946
+ modeSession: ctx.modeSessions.getTurnOwner(reqId),
1799
1947
  };
1800
1948
  // The persisted row re-enters LLM history and is displayed as
1801
1949
  // assistant speech, so the managed-billing categories swap the
@@ -1822,6 +1970,13 @@ export async function runAgentLoopImpl(
1822
1970
  // (or a downstream handler) doesn't try to clean up an id that
1823
1971
  // already corresponds to a finalized row.
1824
1972
  state.lastAssistantMessageId = errorRow.id;
1973
+ bestEffortModeSessionTracking("provider error notice persistence", () =>
1974
+ ctx.modeSessions.trackPersistedRow(
1975
+ reqId,
1976
+ errorRow.id,
1977
+ errorRow.createdAt,
1978
+ ),
1979
+ );
1825
1980
  state.assistantRowAwaitingFinalization = false;
1826
1981
  newMessages.push(errorAssistantMessage);
1827
1982
  // Pipe the just-assigned message id into any orphaned LLM request log
@@ -1898,10 +2053,10 @@ export async function runAgentLoopImpl(
1898
2053
  // contradicts itself. `forceOverrideProfile` floats it above the
1899
2054
  // call-site profile exactly as the fallback dispatch did.
1900
2055
  {
1901
- callSite: turnCallSite,
2056
+ callSite: inferenceCallSite,
1902
2057
  overrideProfile:
1903
2058
  state.exchangeInferenceProfile ??
1904
- resolveCurrentOverrideProfile() ??
2059
+ refreshCurrentProfileState() ??
1905
2060
  null,
1906
2061
  ...(state.exchangeInferenceProfile !== undefined
1907
2062
  ? { forceOverrideProfile: true }
@@ -2012,6 +2167,7 @@ export async function runAgentLoopImpl(
2012
2167
  ...(state.lastAssistantMessageId
2013
2168
  ? { messageId: state.lastAssistantMessageId }
2014
2169
  : {}),
2170
+ modeSession: ctx.modeSessions.getTurnOwner(reqId),
2015
2171
  });
2016
2172
  publishLoopMessagesChanged();
2017
2173
  } else {
@@ -2024,6 +2180,7 @@ export async function runAgentLoopImpl(
2024
2180
  onEvent({
2025
2181
  type: "message_complete",
2026
2182
  conversationId: ctx.conversationId,
2183
+ modeSession: ctx.modeSessions.getTurnOwner(reqId),
2027
2184
  ...(emittedAttachments.length > 0
2028
2185
  ? { attachments: emittedAttachments }
2029
2186
  : {}),
@@ -2059,6 +2216,28 @@ export async function runAgentLoopImpl(
2059
2216
  await settlePendingPartialFlush(state, deps);
2060
2217
  await settleTurnContent({ ctx, state, rlog });
2061
2218
 
2219
+ if (yieldedForHandoff) {
2220
+ const nextRequestId = ctx.queue.snapshot()[0]?.requestId;
2221
+ if (nextRequestId) {
2222
+ ctx.modeSessions.transferTurn(reqId, nextRequestId);
2223
+ } else {
2224
+ settleModeSessionTurn(ctx, reqId, {
2225
+ status: "completed",
2226
+ endReason: "handoff_settled",
2227
+ });
2228
+ }
2229
+ } else if (abortController.signal.aborted) {
2230
+ settleModeSessionTurn(ctx, reqId, {
2231
+ status: "interrupted",
2232
+ endReason: "cancelled",
2233
+ });
2234
+ } else {
2235
+ settleModeSessionTurn(ctx, reqId, {
2236
+ status: "completed",
2237
+ endReason: "turn_settled",
2238
+ });
2239
+ }
2240
+
2062
2241
  // Content is settled, so the conversation is free. Release before the
2063
2242
  // deferred tail rather than in the `finally`: the tail's memory indexing is
2064
2243
  // network-bound and unbounded, and holding the lock across it is what makes
@@ -2136,6 +2315,21 @@ export async function runAgentLoopImpl(
2136
2315
  onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
2137
2316
  publishLoopMessagesChanged();
2138
2317
  }
2318
+ try {
2319
+ if (eventHandlerDeps) {
2320
+ await settlePendingPartialFlush(state, eventHandlerDeps);
2321
+ }
2322
+ await settleTurnContent({ ctx, state, rlog });
2323
+ } catch (settleErr) {
2324
+ rlog.warn(
2325
+ { err: settleErr },
2326
+ "Failed to settle interrupted turn content before mode-session finalization",
2327
+ );
2328
+ }
2329
+ settleModeSessionTurn(ctx, reqId, {
2330
+ status: "interrupted",
2331
+ endReason: isUserCancellation(err, errorCtx) ? "cancelled" : "error",
2332
+ });
2139
2333
  } finally {
2140
2334
  // Backstop release for the cancel/error paths, which throw out of the try
2141
2335
  // before the happy path's release runs. Idempotent, so the happy path
@@ -6,8 +6,7 @@ const log = getLogger("conversation-evictor");
6
6
 
7
7
  /** Minimal interface a conversation must satisfy to be evictable. */
8
8
  export interface EvictableConversation {
9
- isProcessing(): boolean;
10
- hasQueuedMessages(): boolean;
9
+ hasInFlightWork(): boolean;
11
10
  dispose(): void;
12
11
  }
13
12
 
@@ -29,7 +28,7 @@ export interface EvictionResult {
29
28
  lruEvicted: number;
30
29
  /** Conversations evicted due to memory pressure. */
31
30
  memoryEvicted: number;
32
- /** Conversations skipped because they were actively processing. */
31
+ /** Conversations skipped because they retained in-flight work. */
33
32
  skipped: number;
34
33
  }
35
34
 
@@ -74,11 +73,6 @@ export class ConversationEvictor {
74
73
  getSubagentManager().abortAllForParent(conversationId);
75
74
  }
76
75
 
77
- /** Protect conversations with in-flight subagents from eviction. */
78
- shouldProtect(conversationId: string): boolean {
79
- return getSubagentManager().hasActiveChildren(conversationId);
80
- }
81
-
82
76
  /** Record an access for the given conversation (resets its idle clock). */
83
77
  touch(conversationId: string): void {
84
78
  this.lastAccess.set(conversationId, Date.now());
@@ -141,11 +135,7 @@ export class ConversationEvictor {
141
135
  if (now - lastAccessTime < this.ttlMs) {
142
136
  continue;
143
137
  }
144
- if (
145
- conversation.isProcessing() ||
146
- conversation.hasQueuedMessages() ||
147
- this.shouldProtect(id)
148
- ) {
138
+ if (conversation.hasInFlightWork()) {
149
139
  result.skipped++;
150
140
  continue;
151
141
  }
@@ -216,20 +206,11 @@ export class ConversationEvictor {
216
206
  log.debug({ conversationId: id }, "Evicted idle conversation");
217
207
  }
218
208
 
219
- /**
220
- * Return idle (non-processing) conversations sorted by last access time
221
- * ascending (least recently used first).
222
- */
209
+ /** Return evictable conversations in least-recently-used order. */
223
210
  private idleConversationsByLru(): Array<[string, EvictableConversation]> {
224
211
  const idle: Array<[string, EvictableConversation, number]> = [];
225
212
  for (const [id, conversation] of this.conversations) {
226
- if (conversation.isProcessing()) {
227
- continue;
228
- }
229
- if (conversation.hasQueuedMessages()) {
230
- continue;
231
- }
232
- if (this.shouldProtect(id)) {
213
+ if (conversation.hasInFlightWork()) {
233
214
  continue;
234
215
  }
235
216
  idle.push([id, conversation, this.lastAccess.get(id) ?? 0]);
@@ -16,6 +16,8 @@ import { relinkLlmRequestLogs } from "../persistence/llm-request-log-store.js";
16
16
  import { getSummaryFromContextMessage } from "../plugins/defaults/compaction/window-manager.js";
17
17
  import type { ContentBlock, Message } from "../providers/types.js";
18
18
  import { getLogger } from "../util/logger.js";
19
+ import type { ConversationModeSessionCoordinator } from "./conversation-mode-session.js";
20
+ import { bestEffortModeSessionTracking } from "./mode-session-tracking.js";
19
21
  import { startsNewTurn } from "./summarize-boundary.js";
20
22
 
21
23
  const log = getLogger("conversation-history");
@@ -360,6 +362,10 @@ export function consolidateAssistantMessages(
360
362
  export interface HistoryConversationContext {
361
363
  readonly conversationId: string;
362
364
  messages: Message[];
365
+ modeSessions: Pick<
366
+ ConversationModeSessionCoordinator,
367
+ "invalidateAllStructuralWaits"
368
+ >;
363
369
  isProcessing(): boolean;
364
370
  }
365
371
 
@@ -377,6 +383,10 @@ export function undo(conversation: HistoryConversationContext): number {
377
383
  return 0;
378
384
  }
379
385
 
386
+ bestEffortModeSessionTracking("conversation wait invalidation", () =>
387
+ conversation.modeSessions.invalidateAllStructuralWaits(),
388
+ );
389
+
380
390
  const removed = conversation.messages.length - lastUserIdx;
381
391
  conversation.messages = conversation.messages.slice(0, lastUserIdx);
382
392
 
@@ -15,37 +15,6 @@ import type { Conversation } from "./conversation.js";
15
15
 
16
16
  const log = getLogger("conversation-interrupt-repair");
17
17
 
18
- /**
19
- * What the repair leaves behind, for the caller that has to decide whether the
20
- * model was told its turn was cut off.
21
- */
22
- export interface InterruptRepairResult {
23
- /**
24
- * True when the tail of the history carries a preempted `tool_result`,
25
- * whether the agent loop's own abort handler wrote it or this repair did.
26
- *
27
- * That result is the model's notice that a message preempted it. False means
28
- * the abort landed with no tool call in flight, so the notice has to ride on
29
- * the interrupting user message instead.
30
- */
31
- preemptedToolResultOnTail: boolean;
32
- }
33
-
34
- /** Whether the last message answers a tool call with the preemption text. */
35
- function tailCarriesPreemptedToolResult(messages: Message[]): boolean {
36
- const tail = messages.at(-1);
37
- if (!tail || tail.role !== "user") {
38
- return false;
39
- }
40
- return tail.content.some(
41
- (block) =>
42
- // Only a synthetic tool_result carries the preemption text; a
43
- // server-side web_search_tool_result never does.
44
- block.type === "tool_result" &&
45
- block.content === PREEMPTED_TOOL_RESULT_TEXT,
46
- );
47
- }
48
-
49
18
  /**
50
19
  * Give every abandoned `tool_use` block at the tail of the history a
51
20
  * `tool_result`, so the next provider request is well formed.
@@ -78,22 +47,18 @@ function tailCarriesPreemptedToolResult(messages: Message[]): boolean {
78
47
  export async function repairInterruptedToolUseBlocks(
79
48
  conversation: Conversation,
80
49
  options: { force?: boolean; requireDurable?: boolean } = {},
81
- ): Promise<InterruptRepairResult> {
50
+ ): Promise<void> {
82
51
  const wasSteerArmed = conversation.pendingSteerRepair;
83
52
  const wasArmed = conversation.pendingInterruptRepair;
84
53
  if (!wasSteerArmed && !wasArmed && options.force !== true) {
85
- return {
86
- preemptedToolResultOnTail: tailCarriesPreemptedToolResult(
87
- conversation.messages,
88
- ),
89
- };
54
+ return;
90
55
  }
91
56
  conversation.pendingSteerRepair = false;
92
57
  conversation.pendingInterruptRepair = false;
93
58
 
94
59
  const messages = conversation.messages;
95
60
  if (messages.length === 0) {
96
- return { preemptedToolResultOnTail: false };
61
+ return;
97
62
  }
98
63
 
99
64
  // Walk backwards from the tail to find the last assistant message with
@@ -127,10 +92,7 @@ export async function repairInterruptedToolUseBlocks(
127
92
  if (pendingToolUseIds.length === 0) {
128
93
  // Nothing dangling either because the turn made no tool call before the
129
94
  // abort, or because the loop's abort handler already answered the batch.
130
- // Only the second case leaves the model a notice.
131
- return {
132
- preemptedToolResultOnTail: tailCarriesPreemptedToolResult(messages),
133
- };
95
+ return;
134
96
  }
135
97
 
136
98
  log.info(
@@ -181,5 +143,4 @@ export async function repairInterruptedToolUseBlocks(
181
143
  "Failed to persist the synthetic tool_result repair row; the in-memory history carries the repair and the next turn runs on it",
182
144
  );
183
145
  }
184
- return { preemptedToolResultOnTail: true };
185
146
  }