@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
@@ -203,6 +203,7 @@ describe("DesktopSessionManager process tree", () => {
203
203
  // The dock waits on the Chromium path its launcher points at, so it comes
204
204
  // up alongside the browser rather than with the rest of the tree.
205
205
  expect(h.roles()).toEqual([
206
+ "session-bus",
206
207
  "x-server",
207
208
  "window-manager",
208
209
  "compositor",
@@ -216,8 +217,9 @@ describe("DesktopSessionManager process tree", () => {
216
217
  expect(x).toContain("-localhost");
217
218
  expect(x[x.indexOf("-SecurityTypes") + 1]).toBe("None");
218
219
  expect(x[x.indexOf("-rfbport") + 1]).toBe(String(DESKTOP_VNC_PORT));
219
- expect(x[x.indexOf("-geometry") + 1]).toBe("1600x900");
220
+ expect(x[x.indexOf("-geometry") + 1]).toBe("1440x810");
220
221
  for (const role of [
222
+ "session-bus",
221
223
  "window-manager",
222
224
  "compositor",
223
225
  "clipboard",
@@ -228,6 +230,11 @@ describe("DesktopSessionManager process tree", () => {
228
230
  HOME: "/data",
229
231
  LANG: "C.UTF-8",
230
232
  DISPLAY: ":99",
233
+ XDG_CONFIG_HOME: panelConfigDir,
234
+ XDG_DATA_HOME: panelConfigDir,
235
+ DBUS_SESSION_BUS_ADDRESS: h.manager.accessibilityBusAddress,
236
+ NO_AT_BRIDGE: "0",
237
+ GTK_A11Y: "atspi",
231
238
  });
232
239
  }
233
240
  expect(h.child("window-manager").request.cmd[0]).toBe("/usr/bin/python3");
@@ -239,11 +246,7 @@ describe("DesktopSessionManager process tree", () => {
239
246
  ]);
240
247
  // The compositor precedes the dock so it has an ARGB visual.
241
248
  expect(h.child("compositor").request.cmd).toEqual(["/usr/bin/xcompmgr"]);
242
- expect(h.child("panel").request.cmd).toEqual([
243
- "/usr/bin/dbus-run-session",
244
- "--",
245
- "/usr/bin/plank",
246
- ]);
249
+ expect(h.child("panel").request.cmd).toEqual(["/usr/bin/plank"]);
247
250
  expect(h.child("panel").request.env).toEqual({
248
251
  ...h.child("browser").request.env,
249
252
  XDG_CONFIG_HOME: panelConfigDir,
@@ -280,7 +283,7 @@ describe("DesktopSessionManager process tree", () => {
280
283
  "plank",
281
284
  "tigervncconfig",
282
285
  "vncconfig",
283
- "xterm",
286
+ "wezterm",
284
287
  ],
285
288
  });
286
289
  await expect(h.manager.ensureDesktopRunning()).rejects.toThrow(
@@ -306,7 +309,7 @@ describe("DesktopSessionManager process tree", () => {
306
309
  join(panelConfigDir, "applications", "xterm.desktop"),
307
310
  "utf8",
308
311
  );
309
- expect(terminal).toContain(`Exec="/usr/bin/xterm"`);
312
+ expect(terminal).toContain(`Exec="/usr/bin/wezterm"`);
310
313
  expect(terminal).toContain(`Icon=${join(panelConfigDir, "terminal.png")}`);
311
314
 
312
315
  const bytes = readFileSync(join(panelConfigDir, "terminal.png"));
@@ -324,7 +327,10 @@ describe("DesktopSessionManager process tree", () => {
324
327
  h.setVncReady(false);
325
328
 
326
329
  await expect(h.manager.ensureDesktopRunning()).rejects.toThrow(/not ready/);
327
- expect(h.terminated()).toEqual([h.child("x-server")]);
330
+ expect(h.terminated()).toEqual([
331
+ h.child("session-bus"),
332
+ h.child("x-server"),
333
+ ]);
328
334
 
329
335
  h.setVncReady(true);
330
336
  await h.manager.ensureDesktopRunning();
@@ -341,7 +347,9 @@ describe("DesktopSessionManager process tree", () => {
341
347
  // The viewer hears about it before the kill grace, not after.
342
348
  expect(lost).toEqual([{ code: 4011, reason: "Desktop failed to start" }]);
343
349
  const first = h.child("x-server");
344
- expect(h.killed).toEqual([{ child: first, signal: "SIGTERM" }]);
350
+ expect(h.killed.filter((k) => k.child === first)).toEqual([
351
+ { child: first, signal: "SIGTERM" },
352
+ ]);
345
353
 
346
354
  // Reconnect while the first X server is still ignoring its SIGTERM.
347
355
  h.setVncReady(true);
@@ -350,8 +358,11 @@ describe("DesktopSessionManager process tree", () => {
350
358
  expect(h.count("x-server")).toBe(1);
351
359
 
352
360
  // The SIGKILL alone does not free the display and port; the exit does.
353
- await waitFor(() => h.killed.length === 2, { intervalMs: 1 });
354
- expect(h.killed[1]).toEqual({ child: first, signal: "SIGKILL" });
361
+ await waitFor(
362
+ () => h.killed.some((k) => k.child === first && k.signal === "SIGKILL"),
363
+ { intervalMs: 1 },
364
+ );
365
+ expect(h.killed).toContainEqual({ child: first, signal: "SIGKILL" });
355
366
  expect(h.count("x-server")).toBe(1);
356
367
 
357
368
  first.exit(0);
@@ -368,7 +379,12 @@ describe("DesktopSessionManager process tree", () => {
368
379
  const started = Date.now();
369
380
  await h.manager.ensureDesktopRunning();
370
381
  expect(Date.now() - started).toBeGreaterThanOrEqual(KILL_GRACE_MS * 2 - 4);
371
- expect(h.killed.map((k) => k.signal)).toEqual(["SIGTERM", "SIGKILL"]);
382
+ expect(h.killed.map((k) => k.signal)).toEqual([
383
+ "SIGTERM",
384
+ "SIGTERM",
385
+ "SIGKILL",
386
+ "SIGKILL",
387
+ ]);
372
388
  expect(h.count("x-server")).toBe(2);
373
389
  });
374
390
 
@@ -386,13 +402,14 @@ describe("DesktopSessionManager process tree", () => {
386
402
  .terminated()
387
403
  .map((c) => c.role)
388
404
  .sort(),
389
- ).toEqual(["compositor", "window-manager", "x-server"]);
405
+ ).toEqual(["compositor", "session-bus", "window-manager", "x-server"]);
390
406
  expect(lost).toEqual([{ code: 4011, reason: "Desktop failed to start" }]);
391
407
 
392
408
  failSpawn.length = 0;
393
409
  await h.manager.ensureDesktopRunning();
394
410
  await settle();
395
- expect(h.roles().slice(3)).toEqual([
411
+ expect(h.roles().slice(4)).toEqual([
412
+ "session-bus",
396
413
  "x-server",
397
414
  "window-manager",
398
415
  "compositor",
@@ -400,7 +417,7 @@ describe("DesktopSessionManager process tree", () => {
400
417
  "panel",
401
418
  "browser",
402
419
  ]);
403
- expect(h.terminated()).toHaveLength(3);
420
+ expect(h.terminated()).toHaveLength(4);
404
421
  });
405
422
 
406
423
  test("a dock or compositor that will not spawn leaves the desktop up", async () => {
@@ -414,6 +431,7 @@ describe("DesktopSessionManager process tree", () => {
414
431
  expect(lost).toEqual([]);
415
432
  expect(h.killed).toEqual([]);
416
433
  expect(h.roles()).toEqual([
434
+ "session-bus",
417
435
  "x-server",
418
436
  "window-manager",
419
437
  "clipboard",
@@ -444,6 +462,7 @@ describe("DesktopSessionManager process tree", () => {
444
462
  "clipboard",
445
463
  "compositor",
446
464
  "panel",
465
+ "session-bus",
447
466
  "window-manager",
448
467
  ]);
449
468
  // The slot is free and the next start builds a fresh tree.
@@ -647,7 +666,13 @@ describe("DesktopSessionManager process tree", () => {
647
666
  .terminated()
648
667
  .map((c) => c.role)
649
668
  .sort(),
650
- ).toEqual(["clipboard", "compositor", "window-manager", "x-server"]);
669
+ ).toEqual([
670
+ "clipboard",
671
+ "compositor",
672
+ "session-bus",
673
+ "window-manager",
674
+ "x-server",
675
+ ]);
651
676
  });
652
677
 
653
678
  test("destroy terminates, hard-kills stragglers after the grace, and refuses what comes after", async () => {
@@ -665,8 +690,8 @@ describe("DesktopSessionManager process tree", () => {
665
690
  { code: 1001, reason: "The assistant is shutting down" },
666
691
  ]);
667
692
  expect(h.killed.map((k) => k.signal)).toEqual([
668
- ...Array(6).fill("SIGTERM"),
669
- ...Array(6).fill("SIGKILL"),
693
+ ...Array(7).fill("SIGTERM"),
694
+ ...Array(7).fill("SIGKILL"),
670
695
  ]);
671
696
  expect(h.manager.acquireViewerSlot(newViewer().viewer)).toEqual(
672
697
  SHUTTING_DOWN,
@@ -682,8 +707,9 @@ describe("DesktopSessionManager process tree", () => {
682
707
  await settle();
683
708
 
684
709
  await h.manager.destroy();
685
- expect(h.killed.filter((k) => k.signal === "SIGTERM")).toHaveLength(6);
710
+ expect(h.killed.filter((k) => k.signal === "SIGTERM")).toHaveLength(7);
686
711
  expect(h.killed.filter((k) => k.signal === "SIGKILL")).toEqual([
712
+ { child: h.child("session-bus"), signal: "SIGKILL" },
687
713
  { child: h.child("panel"), signal: "SIGKILL" },
688
714
  ]);
689
715
  });
@@ -716,7 +742,7 @@ describe("DesktopSessionManager viewer slot", () => {
716
742
  expect(h.killed).toEqual([]);
717
743
 
718
744
  await sleep(LINGER_MS * 2);
719
- expect(h.terminated()).toHaveLength(6);
745
+ expect(h.terminated()).toHaveLength(7);
720
746
  await h.manager.ensureDesktopRunning();
721
747
  expect(h.count("x-server")).toBe(2);
722
748
  });
@@ -811,3 +837,25 @@ test.each([undefined, 4321])(
811
837
  }
812
838
  },
813
839
  );
840
+
841
+ test("losing the accessibility session bus tears down apps and the next desktop gets a new bus", async () => {
842
+ const h = newManager({ exitOnTerm: true });
843
+ const { viewer, lost } = newViewer();
844
+ h.manager.acquireViewerSlot(viewer);
845
+ await h.manager.ensureDesktopRunning();
846
+ await settle();
847
+ const firstAddress = h.manager.accessibilityBusAddress;
848
+ for (const role of ["window-manager", "panel", "browser"] as const) {
849
+ expect(h.child(role).request.env.DBUS_SESSION_BUS_ADDRESS).toBe(
850
+ firstAddress,
851
+ );
852
+ }
853
+ h.child("session-bus").exit(1);
854
+ await settle();
855
+ expect(lost).toEqual([
856
+ { code: 4011, reason: "Desktop stopped: session-bus exited" },
857
+ ]);
858
+ expect(h.terminated()).toContain(h.child("browser"));
859
+ await h.manager.ensureDesktopRunning();
860
+ expect(h.manager.accessibilityBusAddress).not.toBe(firstAddress);
861
+ });
@@ -6,16 +6,16 @@
6
6
  * last one leaves so a reconnect is instant.
7
7
  */
8
8
 
9
+ import { randomUUID } from "node:crypto";
9
10
  import { mkdirSync, writeFileSync } from "node:fs";
11
+ import { createConnection } from "node:net";
10
12
  import { join } from "node:path";
11
13
 
12
- import { readAvatarState } from "../avatar/avatar-manifest.js";
13
- import { resolveNotificationAccentHex } from "../avatar/notification-avatar.js";
14
14
  import { getIsContainerized } from "../config/env-registry.js";
15
15
  import { connectCdpWsTransport } from "../tools/browser/cdp-client/cdp-inspect/ws-transport.js";
16
16
  import { terminateProcessTree } from "../util/host-process.js";
17
17
  import { getLogger } from "../util/logger.js";
18
- import { getDataDir } from "../util/platform.js";
18
+ import { getDataDir, getWorkspaceDir } from "../util/platform.js";
19
19
  import { sleep } from "../util/retry.js";
20
20
  import { DesktopBrowserClient } from "./desktop-browser-client.js";
21
21
  import {
@@ -46,7 +46,7 @@ const log = getLogger("desktop-session");
46
46
 
47
47
  export const DESKTOP_VNC_PORT = 5999;
48
48
  const DESKTOP_GEOMETRY = `${DESKTOP_WIDTH}x${DESKTOP_HEIGHT}`;
49
- const DESKTOP_LINGER_MS = 5 * 60_000;
49
+ const DESKTOP_LINGER_MS = 24 * 60 * 60_000;
50
50
  const VNC_READY_DEADLINE_MS = 10_000;
51
51
  const VNC_PROBE_INTERVAL_MS = 100;
52
52
  const KILL_GRACE_MS = 2_000;
@@ -96,6 +96,7 @@ const BUSY_LOSS: DesktopLoss = {
96
96
  };
97
97
 
98
98
  export type DesktopChildRole =
99
+ | "session-bus"
99
100
  | "x-server"
100
101
  | "window-manager"
101
102
  | "compositor"
@@ -172,6 +173,7 @@ interface DesktopSessionManagerOptions {
172
173
  /** PATH lookup for the desktop binaries; `null` when one is missing. */
173
174
  readonly which?: (binary: string) => string | null;
174
175
  /** Whether the VNC server accepts connections on `port`. */
176
+ readonly probeSessionBus?: (address: string) => Promise<boolean>;
175
177
  readonly probeVncPort?: (port: number) => Promise<boolean>;
176
178
  /** Path of the installed Google Chrome binary. */
177
179
  readonly resolveChromePath?: () => Promise<string>;
@@ -218,7 +220,7 @@ export class DesktopSessionManager {
218
220
  chromiumPath: string;
219
221
  env: Record<string, string>;
220
222
  } | null = null;
221
- private readonly retiredPanels = new Set<DesktopChild>();
223
+ private readonly retiredLaunchers = new Map<DesktopChild, DesktopChildRole>();
222
224
  private wallpaperStarting: {
223
225
  generation: number;
224
226
  refreshQueued: boolean;
@@ -288,6 +290,13 @@ export class DesktopSessionManager {
288
290
  }
289
291
  });
290
292
 
293
+ private busName = "";
294
+ private readonly probeSessionBus: (address: string) => Promise<boolean>;
295
+
296
+ get accessibilityBusAddress(): string {
297
+ return `unix:abstract=${this.busName}`;
298
+ }
299
+
291
300
  private readonly spawn: NonNullable<DesktopSessionManagerOptions["spawn"]>;
292
301
  private readonly which: NonNullable<DesktopSessionManagerOptions["which"]>;
293
302
  private readonly probeVncPort: NonNullable<
@@ -315,6 +324,7 @@ export class DesktopSessionManager {
315
324
  this.allocateDebugPort =
316
325
  options.allocateDebugPort ?? allocateDesktopDebugPort;
317
326
  this.spawn = options.spawn ?? spawnDetached;
327
+ this.probeSessionBus = options.probeSessionBus ?? probeSessionBus;
318
328
  this.which = options.which ?? Bun.which;
319
329
  this.probeVncPort = options.probeVncPort ?? probeLoopbackPort;
320
330
  this.resolveChromePath =
@@ -346,11 +356,7 @@ export class DesktopSessionManager {
346
356
  ((configDir) => {
347
357
  let sourcePath: string | undefined;
348
358
  try {
349
- sourcePath = writeDesktopWindowTheme(
350
- configDir,
351
- this.sourceEnv.HOME,
352
- resolveNotificationAccentHex(readAvatarState()),
353
- );
359
+ sourcePath = writeDesktopWindowTheme(configDir, this.sourceEnv.HOME);
354
360
  } catch (err) {
355
361
  log.warn({ err }, "Desktop window theme could not be applied");
356
362
  }
@@ -454,7 +460,28 @@ export class DesktopSessionManager {
454
460
  let env: Record<string, string>;
455
461
  try {
456
462
  this.binaries = resolveDesktopBinaries(this.which);
463
+ this.busName = `vellum-desktop-${randomUUID()}`;
457
464
  env = this.childEnv();
465
+ this.launch(
466
+ "session-bus",
467
+ [
468
+ this.binaries.sessionBus,
469
+ "--session",
470
+ "--nofork",
471
+ `--address=${this.accessibilityBusAddress}`,
472
+ ],
473
+ env,
474
+ );
475
+ const busDeadline = Date.now() + this.readyDeadlineMs;
476
+ while (!(await this.probeSessionBus(this.accessibilityBusAddress))) {
477
+ if (this.generation !== generation || Date.now() >= busDeadline) {
478
+ throw new Error("Desktop session bus did not start");
479
+ }
480
+ await sleep(VNC_PROBE_INTERVAL_MS);
481
+ }
482
+ if (this.generation !== generation) {
483
+ throw new Error("Desktop was torn down while starting");
484
+ }
458
485
  this.launch("x-server", xServerCommand(this.binaries.xServer), env);
459
486
  const ready = await this.waitForVnc(generation);
460
487
  if (this.generation !== generation) {
@@ -618,11 +645,11 @@ export class DesktopSessionManager {
618
645
  chromiumProfileDir: this.profileDir,
619
646
  debugPort: this.debugPort,
620
647
  terminalPath: binaries.terminal,
648
+ fileManagerPath: binaries.fileManager,
649
+ workspaceDir: getWorkspaceDir(),
621
650
  });
622
- this.launch("panel", [binaries.panelSession, "--", binaries.panel], {
651
+ this.launch("panel", [binaries.panel], {
623
652
  ...env,
624
- XDG_CONFIG_HOME: this.panelConfigDir,
625
- XDG_DATA_HOME: this.panelConfigDir,
626
653
  GSETTINGS_BACKEND: "keyfile",
627
654
  });
628
655
  } catch (err) {
@@ -694,11 +721,14 @@ export class DesktopSessionManager {
694
721
  this.children.delete(role);
695
722
  if (role === "panel") {
696
723
  // Keep dock-launched applications alive until desktop teardown.
697
- this.retiredPanels.add(child);
724
+ this.retiredLaunchers.set(child, role);
698
725
  log.warn({ outcome }, "Desktop dock exited, scheduling restart");
699
726
  this.schedulePanelRestart();
700
727
  return;
701
728
  }
729
+ if (role === "session-bus") {
730
+ this.retiredLaunchers.set(child, role);
731
+ }
702
732
  if (role === "wallpaper" && outcome === 0) {
703
733
  return;
704
734
  }
@@ -737,8 +767,8 @@ export class DesktopSessionManager {
737
767
  }
738
768
  this.panelRestartAttempts = 0;
739
769
  this.panelLaunch = null;
740
- const retiredPanels = [...this.retiredPanels];
741
- this.retiredPanels.clear();
770
+ const retiredLaunchers = [...this.retiredLaunchers];
771
+ this.retiredLaunchers.clear();
742
772
  const children = new Map(this.children);
743
773
  this.children.clear();
744
774
  const viewer = this.viewer;
@@ -755,8 +785,8 @@ export class DesktopSessionManager {
755
785
  const done: Promise<void> = Promise.all([
756
786
  this.tearingDown,
757
787
  this.killAll(children),
758
- ...retiredPanels.map((child) =>
759
- this.killAll(new Map([["panel", child]])),
788
+ ...retiredLaunchers.map(([child, role]) =>
789
+ this.killAll(new Map([[role, child]])),
760
790
  ),
761
791
  ])
762
792
  .then(() => undefined)
@@ -787,13 +817,16 @@ export class DesktopSessionManager {
787
817
  return child.exited.catch(() => 0).then(() => alive.delete(role));
788
818
  }),
789
819
  );
790
- const panel = children.get("panel");
820
+ const hasServiceChildren =
821
+ children.has("panel") || children.has("session-bus");
791
822
  await this.waitForExits(
792
- panel ? Promise.all([exits, sleep(this.killGraceMs)]) : exits,
823
+ hasServiceChildren
824
+ ? Promise.all([exits, sleep(this.killGraceMs)])
825
+ : exits,
793
826
  );
794
827
  for (const [role, child] of children) {
795
- // A reaped panel does not prove its application process group is empty.
796
- if (role === "panel" || alive.has(role)) {
828
+ // Launchers can leave descendants after their own exit.
829
+ if (role === "panel" || role === "session-bus" || alive.has(role)) {
797
830
  this.killProcessGroup(child, "SIGKILL");
798
831
  }
799
832
  }
@@ -843,6 +876,12 @@ export class DesktopSessionManager {
843
876
  }
844
877
  }
845
878
  env.DISPLAY = DESKTOP_DISPLAY;
879
+ // D-Bus services share app preferences and launchers.
880
+ env.XDG_CONFIG_HOME = this.panelConfigDir;
881
+ env.XDG_DATA_HOME = this.panelConfigDir;
882
+ env.DBUS_SESSION_BUS_ADDRESS = this.accessibilityBusAddress;
883
+ env.NO_AT_BRIDGE = "0";
884
+ env.GTK_A11Y = "atspi";
846
885
  return env;
847
886
  }
848
887
  }
@@ -949,6 +988,21 @@ async function probeLoopbackPort(port: number): Promise<boolean> {
949
988
  }
950
989
  }
951
990
 
991
+ function probeSessionBus(address: string): Promise<boolean> {
992
+ return new Promise((resolve) => {
993
+ const socket = createConnection({
994
+ path: `\0${address.slice("unix:abstract=".length)}`,
995
+ });
996
+ const finish = (ready: boolean) => {
997
+ socket.destroy();
998
+ resolve(ready);
999
+ };
1000
+ socket.setTimeout(200, () => finish(false));
1001
+ socket.once("connect", () => finish(true));
1002
+ socket.once("error", () => finish(false));
1003
+ });
1004
+ }
1005
+
952
1006
  // ---------------------------------------------------------------------------
953
1007
  // Singleton
954
1008
  // ---------------------------------------------------------------------------
@@ -6,7 +6,6 @@ import { afterAll, describe, expect, mock, spyOn, test } from "bun:test";
6
6
 
7
7
  import { setOverridesForTesting } from "../__tests__/feature-flag-test-helpers.js";
8
8
  import { newFakeDesktop, newViewer, settle } from "./__tests__/fake-desktop.js";
9
- import { DesktopDependencyInstaller } from "./desktop-dependencies.js";
10
9
  import {
11
10
  type DesktopSessionManager,
12
11
  type DesktopTcpHandlers,
@@ -57,7 +56,7 @@ class FakeTcp implements DesktopTcpSocket {
57
56
 
58
57
  function newBridge(
59
58
  manager: DesktopSessionManager,
60
- ensureInstalled: () => Promise<void> = async () => {},
59
+ assertReady: () => void = () => {},
61
60
  isEnabled: () => boolean = () => true,
62
61
  ) {
63
62
  const ws = new FakeWs();
@@ -68,7 +67,7 @@ function newBridge(
68
67
  });
69
68
  const bridge = new DesktopStreamBridge(ws, {
70
69
  manager,
71
- ensureInstalled,
70
+ assertReady,
72
71
  isEnabled,
73
72
  connect: async (_port, handlers) => {
74
73
  tcp.handlers = handlers;
@@ -147,42 +146,23 @@ describe("DesktopStreamBridge", () => {
147
146
  }
148
147
  });
149
148
 
150
- test("refuses a disabled desktop before installation or process startup", async () => {
149
+ test("refuses a disabled desktop before checking components or starting processes", async () => {
151
150
  const h = newFakeDesktop({ profileDir });
152
- let installs = 0;
151
+ let checks = 0;
153
152
  const b = newBridge(
154
153
  h.manager,
155
154
  async () => {
156
- installs++;
155
+ checks++;
157
156
  },
158
157
  () => false,
159
158
  );
160
159
  await b.bridge.start();
161
160
  expect(b.ws.closeCode).toBe(4008);
162
- expect(installs).toBe(0);
161
+ expect(checks).toBe(0);
163
162
  expect(h.spawned).toEqual([]);
164
163
  expect(slotIsFree(h.manager)).toBe(true);
165
164
  });
166
165
 
167
- test("does not start the desktop when disabled during installation", async () => {
168
- const h = newFakeDesktop({ profileDir });
169
- let enabled = true;
170
- const install = Promise.withResolvers<void>();
171
- const b = newBridge(
172
- h.manager,
173
- () => install.promise,
174
- () => enabled,
175
- );
176
- const started = b.bridge.start();
177
- enabled = false;
178
- install.resolve();
179
- await started;
180
- expect(b.ws.closeCode).toBe(4008);
181
- expect(slotIsFree(h.manager)).toBe(true);
182
- expect(b.tcp.handlers).toBeUndefined();
183
- expect(h.spawned).toEqual([]);
184
- });
185
-
186
166
  for (const direction of ["viewer", "desktop"] as const) {
187
167
  test(`ignores ${direction} frames after the stream closes`, async () => {
188
168
  const h = newFakeDesktop({ profileDir });
@@ -205,106 +185,15 @@ describe("DesktopStreamBridge", () => {
205
185
  });
206
186
  }
207
187
 
208
- test("direct viewers share background setup across a timeout and reconnect", async () => {
209
- const h = newFakeDesktop({ profileDir });
210
- let ready = false;
211
- let installs = 0;
212
- const install = Promise.withResolvers<void>();
213
- const installer = new DesktopDependencyInstaller({
214
- supported: () => true,
215
- ready: () => ready,
216
- install: async () => {
217
- installs++;
218
- await install.promise;
219
- ready = true;
220
- },
221
- notify: async () => {},
222
- });
223
- const ensureInstalled = () => installer.ensureReady();
224
- const first = newBridge(h.manager, ensureInstalled);
225
- const firstStart = first.bridge.start();
226
- await settle();
227
- expect(installs).toBe(1);
228
- expect(h.spawned).toEqual([]);
229
-
230
- const busy = newBridge(h.manager, async () => {
231
- throw new Error("A rejected viewer must not start setup");
232
- });
233
- await busy.bridge.start();
234
- expect(busy.ws.closeCode).toBe(4013);
235
-
236
- first.bridge.handleClose();
237
- expect(slotIsFree(h.manager)).toBe(true);
238
- const retry = newBridge(h.manager, ensureInstalled);
239
- const retryStart = retry.bridge.start();
240
- await settle();
241
- expect(installs).toBe(1);
242
- expect(h.spawned).toEqual([]);
243
-
244
- install.resolve();
245
- retry.connectNow();
246
- await Promise.all([firstStart, retryStart]);
247
- expect(retry.ws.closeCode).toBeNull();
248
- expect(h.spawned.length).toBeGreaterThan(0);
249
- retry.tcp.handlers.onData(new Uint8Array([1, 2, 3]));
250
- expect(retry.ws.sent).toEqual([new Uint8Array([1, 2, 3])]);
251
- expect(first.ws.sent).toEqual([]);
252
- expect(first.tcp.handlers).toBeUndefined();
253
- retry.bridge.handleClose();
254
- });
255
-
256
- test("setup continues after the last viewer closes without starting a desktop", async () => {
257
- const h = newFakeDesktop({ profileDir });
258
- const install = Promise.withResolvers<void>();
259
- const b = newBridge(h.manager, () => install.promise);
260
- const started = b.bridge.start();
261
- b.bridge.handleClose();
262
- install.resolve();
263
- await started;
264
- expect(h.spawned).toEqual([]);
265
- expect(slotIsFree(h.manager)).toBe(true);
266
- });
267
-
268
- test("setup failure releases the slot and permits a successful retry", async () => {
188
+ test("missing image components release the viewer slot without starting processes", async () => {
269
189
  const h = newFakeDesktop({ profileDir });
270
- let ready = false;
271
- let attempts = 0;
272
- const installer = new DesktopDependencyInstaller({
273
- supported: () => true,
274
- ready: () => ready,
275
- install: async () => {
276
- if (++attempts === 1) {
277
- throw new Error("Download failed");
278
- }
279
- ready = true;
280
- },
281
- notify: async () => {},
190
+ const b = newBridge(h.manager, () => {
191
+ throw new Error("Missing image components");
282
192
  });
283
- const ensureInstalled = () => installer.ensureReady();
284
- const first = newBridge(h.manager, ensureInstalled);
285
- await first.bridge.start();
286
- expect(first.ws.closeCode).toBe(4011);
193
+ await b.bridge.start();
194
+ expect(b.ws.closeCode).toBe(4011);
287
195
  expect(slotIsFree(h.manager)).toBe(true);
288
196
  expect(h.spawned).toEqual([]);
289
-
290
- const retry = newBridge(h.manager, ensureInstalled);
291
- retry.connectNow();
292
- await retry.bridge.start();
293
- expect(attempts).toBe(2);
294
- expect(retry.ws.closeCode).toBeNull();
295
- retry.bridge.handleClose();
296
- });
297
-
298
- test("shutdown during setup closes the viewer without starting a desktop", async () => {
299
- const h = newFakeDesktop({ profileDir });
300
- const install = Promise.withResolvers<void>();
301
- const b = newBridge(h.manager, () => install.promise);
302
- const started = b.bridge.start();
303
- await h.manager.destroy();
304
- expect(b.ws.closeCode).toBe(1001);
305
- install.resolve();
306
- await started;
307
- expect(h.spawned).toEqual([]);
308
197
  expect(b.tcp.handlers).toBeUndefined();
309
198
  });
310
199
 
@@ -4,7 +4,7 @@
4
4
  * are signaled with close codes only (see `DESKTOP_CLOSE`).
5
5
  */
6
6
 
7
- import { desktopDependencyInstaller } from "./desktop-dependencies.js";
7
+ import { desktopDependencies } from "./desktop-dependencies.js";
8
8
  import {
9
9
  connectLoopback,
10
10
  DESKTOP_CLOSE,
@@ -31,7 +31,7 @@ interface DesktopStreamClientSocket {
31
31
  interface DesktopStreamBridgeOptions {
32
32
  readonly isEnabled: () => boolean;
33
33
  readonly manager?: DesktopSessionManager;
34
- readonly ensureInstalled?: () => Promise<void>;
34
+ readonly assertReady?: () => void;
35
35
  readonly connect?: (
36
36
  port: number,
37
37
  handlers: DesktopTcpHandlers,
@@ -42,7 +42,7 @@ export class DesktopStreamBridge {
42
42
  private readonly ws: DesktopStreamClientSocket;
43
43
  private readonly isEnabled: () => boolean;
44
44
  private readonly manager: DesktopSessionManager;
45
- private readonly ensureInstalled: () => Promise<void>;
45
+ private readonly assertReady: () => void;
46
46
  private readonly connect: NonNullable<DesktopStreamBridgeOptions["connect"]>;
47
47
  private readonly viewer: DesktopViewer;
48
48
 
@@ -60,9 +60,8 @@ export class DesktopStreamBridge {
60
60
  this.ws = ws;
61
61
  this.isEnabled = options.isEnabled;
62
62
  this.manager = options.manager ?? getDesktopSessionManager();
63
- this.ensureInstalled =
64
- options.ensureInstalled ??
65
- (() => desktopDependencyInstaller.ensureReady());
63
+ this.assertReady =
64
+ options.assertReady ?? (() => desktopDependencies.assertReady());
66
65
  this.connect = options.connect ?? connectLoopback;
67
66
  this.viewer = { onDesktopLost: (loss) => this.lose(loss) };
68
67
  }
@@ -80,8 +79,7 @@ export class DesktopStreamBridge {
80
79
  this.ownsSlot = true;
81
80
 
82
81
  try {
83
- // Direct viewers share setup with the modal, including across reconnects.
84
- await this.ensureInstalled();
82
+ this.assertReady();
85
83
  if (!this.checkEnabled()) {
86
84
  return;
87
85
  }