@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
@@ -9,10 +9,16 @@ import {
9
9
  setCesReconnect,
10
10
  } from "../security/secure-keys.js";
11
11
  import { getLogger } from "../util/logger.js";
12
- import { type CesClient, createCesClient } from "./client.js";
12
+ import {
13
+ openCesRpcSession,
14
+ reconnectCesRpcSession,
15
+ } from "./ces-connect.js";
16
+ import {
17
+ type CesClient,
18
+ type CesClientHandshakeOptions,
19
+ } from "./client.js";
13
20
  import {
14
21
  type CesProcessManager,
15
- CesUnavailableError,
16
22
  createCesProcessManager,
17
23
  } from "./process-manager.js";
18
24
  import {
@@ -45,70 +51,69 @@ interface CesStartupResult {
45
51
  }
46
52
 
47
53
  /**
48
- * Start the CES process and perform the RPC handshake. Returns immediately with
49
- * handles to the in-flight initialization — callers don't need to await this
50
- * for startup to continue.
54
+ * Open the assistant's CES RPC client and perform the handshake. Returns
55
+ * immediately with handles to the in-flight initialization: callers don't
56
+ * need to await this for startup to continue.
51
57
  *
52
- * CES serves a multi-connection Unix socket, so this is called at the
53
- * process level and child processes may open their own connections.
58
+ * Claims reconnect ownership before any credential read so boot identity
59
+ * loading cannot open a second, identity-less session through the child
60
+ * entry point. CES serves a multi-connection Unix socket, so child
61
+ * processes in other address spaces still open their own connections.
54
62
  */
55
63
  function startCesProcess(config: AssistantConfig): CesStartupResult {
56
64
  const pm = createCesProcessManager({ assistantConfig: config });
57
65
  const abortController = new AbortController();
58
66
  let currentClient: CesClient | undefined;
67
+ let handshake: CesClientHandshakeOptions = {};
68
+
69
+ // Own this process's CES session before resolveManagedProxyContext()
70
+ // reads the API key. That read must not take the child open path.
71
+ setCesReconnect(async () => {
72
+ const client = await reconnectCesRpcSession(pm, handshake);
73
+ if (client) {
74
+ log.info("CES reconnection handshake accepted");
75
+ }
76
+ return client;
77
+ });
59
78
 
60
79
  const handshakePromise = (async (): Promise<CesClient | undefined> => {
61
80
  try {
62
- const transport = await pm.start();
63
- if (abortController.signal.aborted) {
64
- throw new Error("CES initialization aborted during shutdown");
65
- }
66
- const client = createCesClient(transport);
67
- currentClient = client;
68
81
  // Resolve the assistant API key so CES can use it for platform
69
82
  // credential materialisation. In managed mode the key is provisioned
70
- // after hatch and stored in the credential store — CES can't read
71
- // the env var, so we pass it via the handshake.
83
+ // after hatch and stored in the credential store. CES can't read
84
+ // the env var, so we pass it via the handshake. Reconnect ownership
85
+ // is already claimed, so this read uses HTTP or the encrypted store,
86
+ // not a second RPC session.
72
87
  const proxyCtx = await resolveManagedProxyContext();
88
+ if (abortController.signal.aborted) {
89
+ return undefined;
90
+ }
73
91
  const assistantId = getPlatformAssistantId();
74
- const { accepted, reason } = await client.handshake({
92
+ handshake = {
75
93
  ...(proxyCtx.assistantApiKey
76
94
  ? { assistantApiKey: proxyCtx.assistantApiKey }
77
95
  : {}),
78
96
  ...(assistantId ? { assistantId } : {}),
97
+ };
98
+ const session = await openCesRpcSession({
99
+ processManager: pm,
100
+ signal: abortController.signal,
101
+ handshake,
79
102
  });
80
- if (abortController.signal.aborted) {
81
- client.close();
82
- throw new Error("CES initialization aborted during shutdown");
83
- }
84
- if (accepted) {
103
+ currentClient = session?.client;
104
+ if (session) {
85
105
  log.info(
86
106
  "CES client initialized and handshake accepted (server-level)",
87
107
  );
88
- return client;
89
108
  }
109
+ return session?.client;
110
+ } catch (err) {
90
111
  log.warn(
91
- { reason },
92
- "CES handshake rejected — CES tools will be unavailable",
112
+ { error: err instanceof Error ? err.message : String(err) },
113
+ "Failed to initialize CES client",
93
114
  );
94
- client.close();
95
115
  currentClient = undefined;
96
- await pm.stop();
97
- return undefined;
98
- } catch (err) {
99
- if (err instanceof CesUnavailableError) {
100
- log.info(
101
- { reason: err.message },
102
- "CES is not available — CES tools will be unavailable",
103
- );
104
- } else {
105
- log.warn(
106
- { error: err instanceof Error ? err.message : String(err) },
107
- "Failed to initialize CES client — CES tools will be unavailable",
108
- );
109
- }
110
116
  await pm.stop().catch(() => {});
111
- currentClient = undefined;
112
117
  return undefined;
113
118
  }
114
119
  })();
@@ -154,11 +159,11 @@ function updateClientRef(client: CesClient | undefined): void {
154
159
  }
155
160
 
156
161
  /**
157
- * Bring up the daemon's CES connection: start the process, run the handshake
158
- * (blocking up to a 20s timeout so credential reads can route through CES
159
- * before provider init), register the reconnection callback, and keep the live
160
- * client reference in sync. Non-fatal — on failure the daemon falls back to the
161
- * direct credential store.
162
+ * Open the assistant's CES RPC client: handshake (blocking up to a 20s
163
+ * timeout so credential reads can route through CES before provider init)
164
+ * and keep the live client reference in sync. Reconnect ownership is
165
+ * claimed before the identity read. Non-fatal: on failure the assistant
166
+ * falls back to the direct credential store.
162
167
  */
163
168
  export async function startCes(config: AssistantConfig): Promise<void> {
164
169
  const cesResult = startCesProcess(config);
@@ -171,7 +176,7 @@ export async function startCes(config: AssistantConfig): Promise<void> {
171
176
  timeoutMs: DEFAULT_CES_STARTUP_TIMEOUT_MS,
172
177
  onTimeout: () => {
173
178
  log.warn(
174
- "CES handshake timed out after 20s — falling back to direct credential store",
179
+ "CES handshake timed out after 20s, falling back to direct credential store",
175
180
  );
176
181
  },
177
182
  });
@@ -193,56 +198,13 @@ export async function startCes(config: AssistantConfig): Promise<void> {
193
198
  }
194
199
  }
195
200
 
196
- // Register CES reconnection callback so the credential layer can re-establish
197
- // the connection when the transport dies, instead of falling back to the
198
- // encrypted file store.
201
+ // Reconnect ownership is claimed inside startCesProcess before the
202
+ // identity read. Snapshotting the API key there (not here) avoids a
203
+ // second resolveManagedProxyContext() after setCesClient, which would
204
+ // read the key through CES while reconnecting.
199
205
  if (cesResult.processManager) {
200
206
  const pm = cesResult.processManager;
201
207
 
202
- // Snapshot the managed-proxy context and assistant ID at CES startup so the
203
- // reconnect closure below never calls back into `resolveManagedProxyContext()`.
204
- // That function reads the assistant API key via `getSecureKeyAsync()`, which
205
- // — once `setCesClient()` has resolved the backend to CES RPC — routes the
206
- // read through CES itself. During a reconnect the old transport is dead and
207
- // a new one is being set up by this very closure, so the nested credential
208
- // read recursively awaits its own in-flight reconnection and deadlocks until
209
- // `CREDENTIAL_OP_TIMEOUT_MS` (45s) fires. That 45-second stall delays every
210
- // CES restart and causes dependent credential reads (e.g. Meet's STT
211
- // provider resolution) to return `undefined` during the window. API key
212
- // rotation uses the `updateAssistantApiKey` RPC on the live client, not a
213
- // reconnect, so caching at startup is safe.
214
- const startupProxyCtx = await resolveManagedProxyContext();
215
- const startupAssistantId = getPlatformAssistantId();
216
-
217
- setCesReconnect(async () => {
218
- try {
219
- await pm.stop();
220
- const transport = await pm.start();
221
- const newClient = createCesClient(transport);
222
- const { accepted, reason } = await newClient.handshake({
223
- ...(startupProxyCtx.assistantApiKey
224
- ? { assistantApiKey: startupProxyCtx.assistantApiKey }
225
- : {}),
226
- ...(startupAssistantId ? { assistantId: startupAssistantId } : {}),
227
- });
228
- if (accepted) {
229
- log.info("CES reconnection handshake accepted");
230
- return newClient;
231
- }
232
- log.warn({ reason }, "CES reconnection handshake rejected");
233
- newClient.close();
234
- await pm.stop().catch(() => {});
235
- return undefined;
236
- } catch (err) {
237
- log.warn(
238
- { error: err instanceof Error ? err.message : String(err) },
239
- "CES reconnection attempt failed",
240
- );
241
- await pm.stop().catch(() => {});
242
- return undefined;
243
- }
244
- });
245
-
246
208
  // Proactive reconnect: when the transport dies (socket close, process
247
209
  // exit), start a retry-with-backoff loop immediately instead of waiting
248
210
  // for the next credential operation to trigger the lazy reconnect path.
@@ -53,9 +53,7 @@ export type DiscoveryResult = ManagedDiscoverySuccess | DiscoveryFailure;
53
53
  * processes can each connect. The actual connection is made later by
54
54
  * `CesProcessManager.start()`.
55
55
  */
56
- export function discoverManagedCes():
57
- | ManagedDiscoverySuccess
58
- | DiscoveryFailure {
56
+ export function discoverCes(): DiscoveryResult {
59
57
  const socketPath = getCesSocketPath();
60
58
 
61
59
  if (!isNamedPipePath(socketPath) && !existsSync(socketPath)) {
@@ -68,14 +66,6 @@ export function discoverManagedCes():
68
66
  return { mode: "managed", socketPath };
69
67
  }
70
68
 
71
- /**
72
- * Discover CES for the current process. Local and managed topologies use
73
- * the same bootstrap socket path.
74
- */
75
- export function discoverCes(): DiscoveryResult {
76
- return discoverManagedCes();
77
- }
78
-
79
69
  /** How long to poll for the CES socket before giving up. */
80
70
  const MANAGED_DISCOVERY_TIMEOUT_MS = 3_000;
81
71
 
@@ -440,7 +440,12 @@ describe("handleSurfaceAction — launch_conversation dispatch", () => {
440
440
  // claimed to eliminate that round-trip; this test enforces it.
441
441
  nextKeyStoreResult = { conversationId: "conv-pending-set" };
442
442
 
443
- const ctx = makeContext();
443
+ const settleStructuralWait = mock(() => true);
444
+ const ctx = makeContext({
445
+ modeSessions: {
446
+ settleStructuralWait,
447
+ } as unknown as Conversation["modeSessions"],
448
+ });
444
449
  registerCardSurface(ctx, "surface-pending");
445
450
  // Simulate `ui_show` having stamped a pending entry for this surface
446
451
  // (which it does for any interactive card, including persistent ones).
@@ -473,6 +478,10 @@ describe("handleSurfaceAction — launch_conversation dispatch", () => {
473
478
  // Pending entry was deleted so subsequent sibling clicks on the same
474
479
  // persistent card aren't blocked behind a stale "owes-an-answer" flag.
475
480
  expect(ctx.pendingSurfaceActions.has("surface-pending")).toBe(false);
481
+ expect(settleStructuralWait).toHaveBeenCalledWith(
482
+ { kind: "surface", responseId: "surface-pending" },
483
+ { status: "completed", endReason: "surface_launch_settled" },
484
+ );
476
485
 
477
486
  await processStartedPromise;
478
487
  resolveProcess();
@@ -47,6 +47,7 @@ mock.module("../../plugins/defaults/memory/v2/activation-log-store.js", () => ({
47
47
  }));
48
48
 
49
49
  // ── Imports (after mocks) ────────────────────────────────────────────────────
50
+ import { mockUnownedModeSessions } from "../../__tests__/helpers/mock-conversation.js";
50
51
  import type { AgentEvent } from "../../agent/loop.js";
51
52
  import type { Message } from "../../providers/types.js";
52
53
  import type {
@@ -68,6 +69,7 @@ function makeDeps(): EventHandlerDeps {
68
69
  provider: { name: "mock-provider" },
69
70
  currentTurnSurfaces: [],
70
71
  trustContext: undefined,
72
+ modeSessions: mockUnownedModeSessions(),
71
73
  } as unknown as EventHandlerDeps["ctx"],
72
74
  onEvent: () => {},
73
75
  reqId: "req-profile-notify",
@@ -0,0 +1,402 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+
3
+ import { setOverridesForTesting } from "../__tests__/feature-flag-test-helpers.js";
4
+ import type { ModeSession } from "../api/mode-session.js";
5
+ import { isSessionGroupsEnabled } from "../config/session-groups-gate.js";
6
+ import { BrowserModeSessionProducer } from "./browser-mode-session.js";
7
+ import type {
8
+ ModeSessionSourceHandle,
9
+ ModeSessionTerminalDisposition,
10
+ } from "./conversation-mode-session.js";
11
+
12
+ function createCoordinator() {
13
+ let owner: ModeSession | undefined;
14
+ let activation = 0;
15
+ const activated: ModeSessionSourceHandle[] = [];
16
+ const retired: Array<{
17
+ handle: ModeSessionSourceHandle;
18
+ disposition?: ModeSessionTerminalDisposition;
19
+ }> = [];
20
+ const activity: Array<{ turnId: string; at: number }> = [];
21
+
22
+ const coordinator = {
23
+ activateSource(input: {
24
+ sourceId: string;
25
+ generation: number;
26
+ mode: "browser";
27
+ sourceStartedAt: number;
28
+ }): ModeSessionSourceHandle {
29
+ activation += 1;
30
+ const handle = {
31
+ sourceId: input.sourceId,
32
+ generation: input.generation,
33
+ activation,
34
+ id: `browser-${activation}`,
35
+ mode: input.mode,
36
+ } as const;
37
+ activated.push(handle);
38
+ return handle;
39
+ },
40
+ claimTurn(
41
+ _turnId: string,
42
+ handle: ModeSessionSourceHandle,
43
+ _at: number,
44
+ ): ModeSession {
45
+ owner ??= { id: handle.id, mode: handle.mode };
46
+ return owner;
47
+ },
48
+ getTurnOwner: () => owner,
49
+ recordActivity: (turnId: string, at: number) => {
50
+ activity.push({ turnId, at });
51
+ return true;
52
+ },
53
+ retireSource: (
54
+ handle: ModeSessionSourceHandle,
55
+ disposition?: ModeSessionTerminalDisposition,
56
+ ) => {
57
+ retired.push({ handle, disposition });
58
+ return true;
59
+ },
60
+ };
61
+
62
+ return {
63
+ coordinator,
64
+ activated,
65
+ retired,
66
+ activity,
67
+ clearOwner: () => {
68
+ owner = undefined;
69
+ },
70
+ setOwner: (next: ModeSession) => {
71
+ owner = next;
72
+ },
73
+ };
74
+ }
75
+
76
+ describe("BrowserModeSessionProducer", () => {
77
+ afterEach(() => {
78
+ setOverridesForTesting({});
79
+ });
80
+
81
+ test.each([false, true])(
82
+ "retires failed admission while preserving a partial turn owner (%s)",
83
+ (partial) => {
84
+ const state = createCoordinator();
85
+ const claim = state.coordinator.claimTurn;
86
+ state.coordinator.claimTurn = (...args) => {
87
+ if (partial) {
88
+ claim(...args);
89
+ }
90
+ throw new Error("tracking unavailable");
91
+ };
92
+ const producer = new BrowserModeSessionProducer(state.coordinator);
93
+ expect(() =>
94
+ producer.beginOperation({
95
+ turnId: "turn-123",
96
+ lifecycle: "action",
97
+ at: 100,
98
+ }),
99
+ ).toThrow("tracking unavailable");
100
+ expect(state.retired).toHaveLength(1);
101
+ expect(state.retired[0]?.disposition).toMatchObject({
102
+ status: "interrupted",
103
+ endReason: "tracking_failed",
104
+ });
105
+ if (partial) {
106
+ expect(state.coordinator.getTurnOwner()).toBeDefined();
107
+ } else {
108
+ expect(state.coordinator.getTurnOwner()).toBeUndefined();
109
+ }
110
+ },
111
+ );
112
+
113
+ test("admits new tracking only while the shared feature flag is enabled", () => {
114
+ const state = createCoordinator();
115
+ const producer = new BrowserModeSessionProducer(
116
+ state.coordinator,
117
+ 1,
118
+ isSessionGroupsEnabled,
119
+ );
120
+
121
+ setOverridesForTesting({});
122
+ expect(isSessionGroupsEnabled()).toBe(false);
123
+ expect(
124
+ producer.beginOperation({
125
+ turnId: "turn-disabled",
126
+ lifecycle: "action",
127
+ at: 100,
128
+ }),
129
+ ).toBeUndefined();
130
+ expect(state.activated).toEqual([]);
131
+
132
+ setOverridesForTesting({ "session-groups": true });
133
+ const admitted = producer.beginOperation({
134
+ turnId: "turn-enabled",
135
+ lifecycle: "action",
136
+ at: 110,
137
+ });
138
+ expect(admitted?.owner).toEqual({ id: "browser-1", mode: "browser" });
139
+
140
+ setOverridesForTesting({ "session-groups": false });
141
+ state.clearOwner();
142
+ const close = producer.beginOperation({
143
+ turnId: "turn-close",
144
+ lifecycle: "terminal",
145
+ at: 120,
146
+ });
147
+ expect(close?.owner).toEqual({ id: "browser-1", mode: "browser" });
148
+ expect(
149
+ producer.finishOperation(close, {
150
+ at: 130,
151
+ isError: false,
152
+ cancelled: false,
153
+ terminalReason: "browser_closed",
154
+ }),
155
+ ).toBe(true);
156
+ expect(state.retired).toHaveLength(1);
157
+ });
158
+
159
+ test("claims the first action and records repeated successful activity", () => {
160
+ const state = createCoordinator();
161
+ const producer = new BrowserModeSessionProducer(state.coordinator);
162
+
163
+ const first = producer.beginOperation({
164
+ turnId: "turn-1",
165
+ lifecycle: "action",
166
+ at: 100,
167
+ });
168
+ expect(first?.owner).toEqual({ id: "browser-1", mode: "browser" });
169
+ expect(state.activated).toHaveLength(1);
170
+ expect(
171
+ producer.finishOperation(first, {
172
+ at: 120,
173
+ isError: false,
174
+ cancelled: false,
175
+ }),
176
+ ).toBe(true);
177
+ expect(state.activity).toEqual([{ turnId: "turn-1", at: 120 }]);
178
+
179
+ const repeated = producer.beginOperation({
180
+ turnId: "turn-1",
181
+ lifecycle: "action",
182
+ at: 130,
183
+ });
184
+ expect(repeated?.owner.id).toBe("browser-1");
185
+ expect(state.activated).toHaveLength(1);
186
+ });
187
+
188
+ test("status and a terminal operation without an active run are inert", () => {
189
+ const state = createCoordinator();
190
+ const producer = new BrowserModeSessionProducer(state.coordinator);
191
+
192
+ expect(
193
+ producer.beginOperation({
194
+ turnId: "turn-1",
195
+ lifecycle: "status",
196
+ at: 100,
197
+ }),
198
+ ).toBeUndefined();
199
+ expect(
200
+ producer.beginOperation({
201
+ turnId: "turn-1",
202
+ lifecycle: "terminal",
203
+ at: 110,
204
+ }),
205
+ ).toBeUndefined();
206
+ expect(state.activated).toEqual([]);
207
+ });
208
+
209
+ test("keeps an existing non-browser owner without activating a nested run", () => {
210
+ const state = createCoordinator();
211
+ state.setOwner({ id: "live-1", mode: "live_vision" });
212
+ const producer = new BrowserModeSessionProducer(state.coordinator);
213
+
214
+ const token = producer.beginOperation({
215
+ turnId: "turn-1",
216
+ lifecycle: "action",
217
+ at: 100,
218
+ });
219
+ expect(token).toMatchObject({ owner: { id: "live-1" } });
220
+ expect(token?.handle).toBeUndefined();
221
+ expect(state.activated).toEqual([]);
222
+ expect(
223
+ producer.finishOperation(token, {
224
+ at: 120,
225
+ isError: false,
226
+ cancelled: false,
227
+ }),
228
+ ).toBe(true);
229
+ expect(state.activity).toEqual([{ turnId: "turn-1", at: 120 }]);
230
+ });
231
+
232
+ test("successful close retires the active run with a completed disposition", () => {
233
+ const state = createCoordinator();
234
+ const producer = new BrowserModeSessionProducer(state.coordinator);
235
+ producer.beginOperation({
236
+ turnId: "turn-1",
237
+ lifecycle: "action",
238
+ at: 100,
239
+ });
240
+ state.clearOwner();
241
+
242
+ const close = producer.beginOperation({
243
+ turnId: "turn-close",
244
+ lifecycle: "terminal",
245
+ at: 200,
246
+ });
247
+ expect(close?.owner.id).toBe("browser-1");
248
+ expect(
249
+ producer.finishOperation(close, {
250
+ at: 220,
251
+ isError: false,
252
+ cancelled: false,
253
+ terminalReason: "browser_closed",
254
+ }),
255
+ ).toBe(true);
256
+ expect(state.retired).toEqual([
257
+ {
258
+ handle: expect.objectContaining({ id: "browser-1", activation: 1 }),
259
+ disposition: {
260
+ status: "completed",
261
+ endReason: "browser_closed",
262
+ },
263
+ },
264
+ ]);
265
+ });
266
+
267
+ test("a foreign-owned terminal operation retires only the captured browser run", () => {
268
+ const state = createCoordinator();
269
+ const producer = new BrowserModeSessionProducer(state.coordinator);
270
+ producer.beginOperation({
271
+ turnId: "turn-browser",
272
+ lifecycle: "action",
273
+ at: 100,
274
+ });
275
+ state.setOwner({ id: "live-1", mode: "live_vision" });
276
+
277
+ const close = producer.beginOperation({
278
+ turnId: "turn-live",
279
+ lifecycle: "terminal",
280
+ at: 200,
281
+ });
282
+ expect(close).toMatchObject({
283
+ owner: { id: "live-1", mode: "live_vision" },
284
+ handle: { id: "browser-1", activation: 1 },
285
+ });
286
+ expect(
287
+ producer.finishOperation(close, {
288
+ at: 220,
289
+ isError: false,
290
+ cancelled: false,
291
+ terminalReason: "browser_detached",
292
+ }),
293
+ ).toBe(true);
294
+ expect(state.retired.at(-1)).toEqual({
295
+ handle: expect.objectContaining({ id: "browser-1", activation: 1 }),
296
+ disposition: {
297
+ status: "completed",
298
+ endReason: "browser_detached",
299
+ },
300
+ });
301
+
302
+ state.clearOwner();
303
+ producer.beginOperation({
304
+ turnId: "turn-browser-2",
305
+ lifecycle: "action",
306
+ at: 300,
307
+ });
308
+ expect(
309
+ producer.finishOperation(close, {
310
+ at: 320,
311
+ isError: true,
312
+ cancelled: false,
313
+ }),
314
+ ).toBe(false);
315
+ expect(state.retired).toHaveLength(1);
316
+ });
317
+
318
+ test("a failed close keeps the session active until a successful retry", () => {
319
+ const state = createCoordinator();
320
+ const producer = new BrowserModeSessionProducer(state.coordinator);
321
+ const action = producer.beginOperation({
322
+ turnId: "turn-1",
323
+ lifecycle: "action",
324
+ at: 100,
325
+ });
326
+ const close = producer.beginOperation({
327
+ turnId: "turn-1",
328
+ lifecycle: "terminal",
329
+ at: 110,
330
+ });
331
+ expect(
332
+ producer.finishOperation(close, {
333
+ at: 120,
334
+ isError: true,
335
+ cancelled: false,
336
+ }),
337
+ ).toBe(true);
338
+ expect(state.retired).toHaveLength(0);
339
+ expect(state.activity).toEqual([{ turnId: "turn-1", at: 120 }]);
340
+
341
+ const retry = producer.beginOperation({
342
+ turnId: "turn-1",
343
+ lifecycle: "terminal",
344
+ at: 130,
345
+ });
346
+ expect(retry?.handle).toEqual(action?.handle);
347
+ producer.finishOperation(retry, {
348
+ at: 140,
349
+ isError: false,
350
+ cancelled: false,
351
+ });
352
+ expect(state.retired[0]?.disposition).toEqual({
353
+ status: "completed",
354
+ endReason: "browser_closed",
355
+ });
356
+ });
357
+
358
+ test("cancellation retires the exact run while accepted output remains on its captured turn", () => {
359
+ const state = createCoordinator();
360
+ const producer = new BrowserModeSessionProducer(state.coordinator);
361
+ const stale = producer.beginOperation({
362
+ turnId: "turn-1",
363
+ lifecycle: "action",
364
+ at: 100,
365
+ });
366
+ expect(
367
+ producer.finishOperation(stale, {
368
+ at: 120,
369
+ isError: true,
370
+ cancelled: true,
371
+ }),
372
+ ).toBe(true);
373
+ expect(state.retired[0]?.disposition).toEqual({
374
+ status: "interrupted",
375
+ endReason: "browser_operation_cancelled",
376
+ });
377
+
378
+ state.clearOwner();
379
+ const current = producer.beginOperation({
380
+ turnId: "turn-2",
381
+ lifecycle: "action",
382
+ at: 200,
383
+ });
384
+ expect(current?.handle?.activation).toBe(2);
385
+ expect(
386
+ producer.finishOperation(stale, {
387
+ at: 210,
388
+ isError: true,
389
+ cancelled: true,
390
+ }),
391
+ ).toBe(false);
392
+ expect(
393
+ producer.finishOperation(stale, {
394
+ at: 220,
395
+ isError: false,
396
+ cancelled: false,
397
+ }),
398
+ ).toBe(true);
399
+ expect(state.activity).toEqual([{ turnId: "turn-1", at: 220 }]);
400
+ expect(state.retired).toHaveLength(1);
401
+ });
402
+ });