@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.
- package/ARCHITECTURE.md +43 -29
- package/Dockerfile +63 -0
- package/docs/architecture/security.md +10 -5
- package/docs/architecture/turn-actor.md +2 -0
- package/docs/desktop-browser-cli.md +45 -4
- package/docs/live-voice-task-outcomes.md +100 -0
- package/docs/mode-sessions.md +183 -0
- package/docs/voice-input-diagnostics.md +58 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
- package/openapi.yaml +665 -14
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +10 -26
- package/scripts/smoke-desktop-window-theme.ts +2 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
- package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/answered-question-persistence.test.ts +2 -0
- package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
- package/src/__tests__/approval-message-composer.test.ts +1 -118
- package/src/__tests__/approval-routes-http.test.ts +3 -0
- package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
- package/src/__tests__/assistant-event-targeting.test.ts +186 -0
- package/src/__tests__/assistant-stream-state.test.ts +29 -9
- package/src/__tests__/browser-fill-credential.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +4 -1
- package/src/__tests__/build-persisted-content.test.ts +1 -1
- package/src/__tests__/call-conversation-messages.test.ts +5 -0
- package/src/__tests__/call-recovery.test.ts +7 -2
- package/src/__tests__/call-routes-http.test.ts +12 -0
- package/src/__tests__/call-store.test.ts +7 -2
- package/src/__tests__/channel-retry-sweep.test.ts +9 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
- package/src/__tests__/checker.test.ts +11 -3
- package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
- package/src/__tests__/computer-use-tools.test.ts +1 -233
- package/src/__tests__/config-model-image-provider.test.ts +39 -0
- package/src/__tests__/config-schema.test.ts +16 -0
- package/src/__tests__/config-watcher.test.ts +24 -0
- package/src/__tests__/context-search-agent-runner.test.ts +260 -1
- package/src/__tests__/context-search-fanout.test.ts +1 -1
- package/src/__tests__/context-search-types.test.ts +7 -7
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +546 -16
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
- package/src/__tests__/conversation-evictor.test.ts +62 -37
- package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-retry-route.test.ts +22 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
- package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
- package/src/__tests__/conversation-undo.test.ts +61 -0
- package/src/__tests__/credential-execution-client.test.ts +6 -6
- package/src/__tests__/credential-prompt-route.test.ts +35 -3
- package/src/__tests__/cu-unified-flow.test.ts +82 -1
- package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
- package/src/__tests__/dm-backfill.test.ts +91 -1
- package/src/__tests__/document-create-route.test.ts +88 -0
- package/src/__tests__/document-sync-tags.test.ts +27 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
- package/src/__tests__/events-tail-route.test.ts +25 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
- package/src/__tests__/guardian-action-sweep.test.ts +16 -0
- package/src/__tests__/guardian-dispatch.test.ts +13 -0
- package/src/__tests__/headless-browser-interactions.test.ts +64 -20
- package/src/__tests__/heartbeat-service.test.ts +2 -59
- package/src/__tests__/helpers/mock-conversation.ts +21 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
- package/src/__tests__/host-cu-proxy.test.ts +46 -2
- package/src/__tests__/http-user-message-parity.test.ts +2 -0
- package/src/__tests__/image-credentials.test.ts +49 -0
- package/src/__tests__/image-service-dispatcher.test.ts +45 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/interrupt-on-send.test.ts +57 -43
- package/src/__tests__/interrupt-turn-note.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +49 -1
- package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
- package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
- package/src/__tests__/mode-session-history.test.ts +245 -0
- package/src/__tests__/mode-session-recovery.test.ts +222 -0
- package/src/__tests__/mode-session-wire.test.ts +141 -0
- package/src/__tests__/notification-guardian-path.test.ts +7 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
- package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-api-provider.test.ts +5 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
- package/src/__tests__/process-message-display-content.test.ts +56 -0
- package/src/__tests__/reaction-persistence.test.ts +5 -0
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
- package/src/__tests__/secret-ingress-http.test.ts +2 -0
- package/src/__tests__/secure-keys.test.ts +43 -1
- package/src/__tests__/send-endpoint-busy.test.ts +163 -1
- package/src/__tests__/server-history-render.test.ts +91 -1
- package/src/__tests__/settings-routes.test.ts +50 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
- package/src/__tests__/subagent-manager-notify.test.ts +1 -2
- package/src/__tests__/subagent-notify-parent.test.ts +146 -39
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +53 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/twilio-routes.test.ts +1 -0
- package/src/__tests__/web-search-backend-failure.test.ts +1 -1
- package/src/activation/progress-store.test.ts +12 -7
- package/src/agent/loop.ts +99 -41
- package/src/api/events/assistant-turn-start.ts +3 -0
- package/src/api/events/generation-handoff.ts +2 -0
- package/src/api/events/message-complete.ts +2 -0
- package/src/api/events/notification-conversation-created.ts +14 -16
- package/src/api/events/notification-intent.ts +4 -2
- package/src/api/events/tool-result.ts +85 -0
- package/src/api/events/tool-use-start.ts +3 -0
- package/src/api/events/user-message-echo.ts +3 -0
- package/src/api/index.ts +31 -6
- package/src/api/mode-session.test.ts +131 -0
- package/src/api/mode-session.ts +148 -0
- package/src/api/remember-tool.ts +15 -0
- package/src/api/responses/conversation-message.ts +6 -0
- package/src/approvals/AGENTS.md +4 -2
- package/src/browser/__tests__/operations.test.ts +29 -0
- package/src/browser/operation-meta.ts +2 -1
- package/src/browser/operations.ts +29 -0
- package/src/browser/virtual-desktop-target.ts +2 -8
- package/src/bundler/bundle-signer.ts +1 -3
- package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
- package/src/calls/__tests__/call-controller.test.ts +94 -0
- package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
- package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
- package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
- package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
- package/src/calls/barge-in-guard.ts +5 -0
- package/src/calls/call-controller.ts +196 -1
- package/src/calls/call-domain.ts +4 -0
- package/src/calls/call-store.ts +87 -2
- package/src/calls/guardian-action-sweep.ts +22 -8
- package/src/calls/media-stream-audio-transcode.ts +1 -3
- package/src/calls/typesafe-noul.ts +156 -0
- package/src/calls/voice-control-protocol.ts +6 -0
- package/src/calls/voice-escalation-judge.ts +145 -0
- package/src/calls/voice-escalation-target.ts +10 -0
- package/src/calls/voice-leg-coordinator.ts +121 -3
- package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
- package/src/calls/voice-session-bridge.ts +256 -112
- package/src/calls/voice-triage-escalate.ts +7 -0
- package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
- package/src/cli/commands/__tests__/notifications.test.ts +35 -10
- package/src/cli/commands/browser.help.ts +4 -5
- package/src/cli/commands/browser.ts +0 -1
- package/src/cli/commands/clients.help.ts +46 -0
- package/src/cli/commands/clients.ts +116 -0
- package/src/cli/commands/credentials.help.ts +6 -1
- package/src/cli/commands/notifications.help.ts +16 -2
- package/src/cli/commands/notifications.ts +30 -8
- package/src/cli/commands/roadmap.help.ts +18 -21
- package/src/cli/commands/roadmap.ts +4 -21
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
- package/src/cli/lib/bundled-marketplace.json +84 -34
- package/src/cli/lib/plugin-marketplace.ts +8 -0
- package/src/cli/lib/publish-plugin.ts +1 -2
- package/src/config/__tests__/default-provider.test.ts +3 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
- package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
- package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
- package/src/config/call-site-defaults.ts +9 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/profile-text-generation.test.ts +2 -2
- package/src/config/schemas/__tests__/tools.test.ts +31 -0
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -18
- package/src/config/schemas/llm.ts +3 -1
- package/src/config/schemas/mcp.ts +2 -1
- package/src/config/schemas/memory-retrospective.ts +11 -2
- package/src/config/schemas/services.ts +15 -6
- package/src/config/schemas/tools.ts +10 -0
- package/src/config/session-groups-gate.ts +21 -0
- package/src/config/skills.ts +2 -6
- package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
- package/src/conversations/message-consolidation.ts +102 -5
- package/src/credential-execution/ces-connect.test.ts +69 -0
- package/src/credential-execution/ces-connect.ts +115 -0
- package/src/credential-execution/ces-runtime.ts +55 -93
- package/src/credential-execution/executable-discovery.ts +1 -11
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
- package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
- package/src/daemon/browser-mode-session.test.ts +402 -0
- package/src/daemon/browser-mode-session.ts +186 -0
- package/src/daemon/computer-use-mode-session.test.ts +210 -0
- package/src/daemon/computer-use-mode-session.ts +105 -0
- package/src/daemon/config-watcher.ts +10 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
- package/src/daemon/conversation-agent-loop.ts +219 -25
- package/src/daemon/conversation-evictor.ts +5 -24
- package/src/daemon/conversation-history.ts +10 -0
- package/src/daemon/conversation-interrupt-repair.ts +4 -43
- package/src/daemon/conversation-interrupt.ts +7 -11
- package/src/daemon/conversation-messaging.ts +58 -5
- package/src/daemon/conversation-mode-session.test.ts +1667 -0
- package/src/daemon/conversation-mode-session.ts +1008 -0
- package/src/daemon/conversation-process.ts +5 -0
- package/src/daemon/conversation-runtime-assembly.ts +10 -20
- package/src/daemon/conversation-skill-tools.ts +1 -0
- package/src/daemon/conversation-store.ts +4 -4
- package/src/daemon/conversation-surface-snapshots.ts +135 -0
- package/src/daemon/conversation-surfaces.ts +113 -5
- package/src/daemon/conversation-tool-setup.ts +8 -3
- package/src/daemon/conversation.ts +143 -41
- package/src/daemon/handlers/config-model.ts +7 -5
- package/src/daemon/handlers/shared.ts +55 -16
- package/src/daemon/host-cu-proxy.ts +51 -22
- package/src/daemon/lifecycle.ts +30 -13
- package/src/daemon/message-protocol.ts +0 -1
- package/src/daemon/message-provenance.ts +28 -4
- package/src/daemon/mode-session-startup-recovery.ts +23 -0
- package/src/daemon/mode-session-tracking.ts +54 -0
- package/src/daemon/process-message.ts +13 -4
- package/src/daemon/virtual-desktop-context.ts +21 -0
- package/src/desktop/__tests__/fake-desktop.ts +1 -0
- package/src/desktop/desktop-accessibility-script.ts +88 -0
- package/src/desktop/desktop-accessibility.test.ts +160 -0
- package/src/desktop/desktop-accessibility.ts +169 -0
- package/src/desktop/desktop-automation-lease.test.ts +76 -68
- package/src/desktop/desktop-automation-lease.ts +41 -50
- package/src/desktop/desktop-browser-endpoint.ts +4 -0
- package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
- package/src/desktop/desktop-computer-use.test.ts +449 -0
- package/src/desktop/desktop-computer-use.ts +502 -0
- package/src/desktop/desktop-dependencies.ts +47 -293
- package/src/desktop/desktop-display.ts +2 -2
- package/src/desktop/desktop-panel-config.test.ts +13 -3
- package/src/desktop/desktop-panel-config.ts +109 -15
- package/src/desktop/desktop-session-manager.test.ts +69 -21
- package/src/desktop/desktop-session-manager.ts +76 -22
- package/src/desktop/desktop-stream-bridge.test.ts +11 -122
- package/src/desktop/desktop-stream-bridge.ts +6 -8
- package/src/desktop/desktop-window-theme.ts +5 -25
- package/src/desktop/virtual-desktop-feature.ts +52 -2
- package/src/desktop/virtual-desktop-platform.test.ts +5 -9
- package/src/documents/document-store.ts +23 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
- package/src/heartbeat/heartbeat-service.ts +5 -33
- package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
- package/src/ipc/assistant-server.ts +2 -0
- package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
- package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
- package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
- package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
- package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
- package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
- package/src/live-voice/__tests__/session-controls.test.ts +23 -0
- package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
- package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
- package/src/live-voice/camera-mode-session.test.ts +265 -0
- package/src/live-voice/camera-mode-session.ts +277 -0
- package/src/live-voice/continuation-judge.ts +123 -0
- package/src/live-voice/live-activity-reporter.ts +115 -6
- package/src/live-voice/live-voice-manager.ts +36 -1
- package/src/live-voice/live-voice-photo.ts +88 -7
- package/src/live-voice/live-voice-session-manager.ts +15 -0
- package/src/live-voice/live-voice-session.ts +1443 -747
- package/src/live-voice/live-voice-tts.ts +22 -4
- package/src/live-voice/microphone-idle-gate.ts +56 -0
- package/src/live-voice/protocol.ts +171 -4
- package/src/live-voice/session-controls.ts +11 -3
- package/src/live-voice/task-outcomes.ts +131 -0
- package/src/live-voice/voice-input-diagnostics.ts +117 -0
- package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
- package/src/mcp/__tests__/tool-caps.test.ts +28 -1
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/tool-caps.ts +12 -4
- package/src/media/__tests__/image-models.test.ts +58 -0
- package/src/media/image-credentials.ts +5 -0
- package/src/media/image-models.ts +52 -0
- package/src/media/image-service.ts +8 -0
- package/src/media/openrouter-image-service.test.ts +133 -0
- package/src/media/openrouter-image-service.ts +153 -0
- package/src/media/types.ts +1 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
- package/src/messaging/providers/discord/send.ts +4 -17
- package/src/messaging/providers/discord/transport.ts +2 -2
- package/src/messaging/providers/outlook/client.ts +16 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
- package/src/messaging/providers/telegram-bot/send.ts +10 -7
- package/src/messaging/providers/telegram-bot/transport.ts +3 -10
- package/src/messaging/providers/whatsapp/send.test.ts +169 -4
- package/src/monitoring/__tests__/control.test.ts +17 -5
- package/src/monitoring/control.ts +15 -5
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +83 -99
- package/src/notifications/__tests__/broadcaster.test.ts +1 -1
- package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
- package/src/notifications/__tests__/copy-composer.test.ts +1 -1
- package/src/notifications/__tests__/decision-engine.test.ts +79 -2
- package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
- package/src/notifications/__tests__/notification-utils.test.ts +18 -0
- package/src/notifications/adapters/macos.ts +37 -46
- package/src/notifications/broadcaster.ts +5 -6
- package/src/notifications/channel-allowlist.ts +44 -0
- package/src/notifications/copy-composer.ts +0 -23
- package/src/notifications/decision-engine.ts +24 -14
- package/src/notifications/deterministic-checks.ts +1 -1
- package/src/notifications/emit-signal.ts +123 -37
- package/src/notifications/home-feed-side-effect.ts +9 -7
- package/src/notifications/notification-utils.ts +19 -0
- package/src/notifications/signal.ts +4 -19
- package/src/notifications/types.ts +15 -0
- package/src/oauth/connection-resolver.ts +1 -2
- package/src/oauth/oauth-store.ts +1 -3
- package/src/oauth/provider-serializer.ts +7 -0
- package/src/oauth/seed-providers.ts +30 -0
- package/src/onboarding/onboarding-events-store.ts +99 -12
- package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
- package/src/persistence/client-connection-events-store.ts +377 -0
- package/src/persistence/conversation-crud.ts +55 -6
- package/src/persistence/conversation-mode-sessions.test.ts +424 -0
- package/src/persistence/conversation-mode-sessions.ts +460 -0
- package/src/persistence/conversation-queries.ts +6 -19
- package/src/persistence/llm-usage-store.ts +3 -9
- package/src/persistence/message-metadata.ts +25 -0
- package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
- package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
- package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
- package/src/persistence/schema/conversations.ts +67 -0
- package/src/persistence/schema/infrastructure.ts +29 -0
- package/src/persistence/steps.ts +17 -0
- package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
- package/src/plugin-api/effective-context-window.ts +33 -0
- package/src/plugin-api/index.ts +24 -5
- package/src/plugin-api/plugin-name-env.ts +125 -0
- package/src/plugin-api/resolve-credential.ts +79 -20
- package/src/plugin-api/vision-support.test.ts +22 -0
- package/src/plugin-api/vision-support.ts +17 -5
- package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
- package/src/plugins/active-plugin-env.ts +51 -0
- package/src/plugins/defaults/index.ts +24 -0
- package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
- package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
- package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
- package/src/plugins/defaults/memory/context-search/format.ts +51 -7
- package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
- package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
- package/src/plugins/defaults/memory/context-search/search.ts +8 -14
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
- package/src/plugins/defaults/memory/context-search/types.ts +1 -11
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
- package/src/plugins/defaults/memory/graph/tools.ts +35 -64
- package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/tools.test.ts +124 -9
- package/src/plugins/defaults/memory/tools.ts +38 -11
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
- package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
- package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
- package/src/plugins/defaults/memory/worker-control.ts +15 -5
- package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
- package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
- package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
- package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
- package/src/plugins/defaults/task-progress/package.json +14 -0
- package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
- package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
- package/src/providers/anthropic/client.ts +10 -1
- package/src/providers/anthropic-gateway-shared.ts +6 -0
- package/src/providers/fetch-provider-catalog.ts +11 -0
- package/src/providers/gemini/client.ts +25 -8
- package/src/providers/inference/adapter-factory.ts +1 -1
- package/src/providers/jev/client.test.ts +10 -0
- package/src/providers/jev/client.ts +20 -4
- package/src/providers/model-catalog.ts +4 -3
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
- package/src/providers/openai/chat-completions-provider.ts +100 -29
- package/src/providers/openai/google-thought-signature.ts +0 -6
- package/src/providers/openai/responses-provider.ts +23 -1
- package/src/providers/ratelimit.ts +1 -3
- package/src/providers/search-provider-catalog.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
- package/src/providers/types.ts +5 -0
- package/src/routes/control.ts +7 -5
- package/src/runtime/AGENTS.md +4 -2
- package/src/runtime/actor-trust-resolver.ts +3 -3
- package/src/runtime/agent-wake.ts +1 -1
- package/src/runtime/approval-message-composer.ts +3 -69
- package/src/runtime/assistant-event-hub.ts +73 -75
- package/src/runtime/assistant-event-publish-options.ts +7 -0
- package/src/runtime/assistant-event-targeting.ts +79 -0
- package/src/runtime/assistant-stream-state.ts +13 -100
- package/src/runtime/background-job-runner.ts +5 -5
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/guardian-action-message-composer.ts +8 -161
- package/src/runtime/guardian-reply-router.ts +28 -32
- package/src/runtime/http-types.ts +6 -0
- package/src/runtime/message-composer-types.ts +3 -61
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
- package/src/runtime/migrations/vbundle-builder.ts +24 -1
- package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
- package/src/runtime/migrations/vbundle-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-validator.ts +1 -0
- package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
- package/src/runtime/pending-interactions.ts +29 -0
- package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
- package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
- package/src/runtime/routes/browser-context.ts +31 -11
- package/src/runtime/routes/browser-routes.ts +43 -4
- package/src/runtime/routes/btw-routes.ts +9 -4
- package/src/runtime/routes/client-routes.ts +108 -0
- package/src/runtime/routes/conversation-management-routes.ts +4 -1
- package/src/runtime/routes/conversation-routes.ts +113 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -3
- package/src/runtime/routes/credential-routes.ts +1 -2
- package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
- package/src/runtime/routes/desktop-setup-routes.ts +4 -6
- package/src/runtime/routes/documents-routes.ts +51 -0
- package/src/runtime/routes/email-routes.ts +3 -7
- package/src/runtime/routes/events-routes.ts +75 -54
- package/src/runtime/routes/identity-routes.ts +1 -5
- package/src/runtime/routes/image-generation-routes.ts +8 -14
- package/src/runtime/routes/inbound-message-handler.ts +35 -38
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +73 -2
- package/src/runtime/routes/notification-routes.ts +10 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +6 -0
- package/src/runtime/routes/roadmap-routes.ts +47 -8
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/settings-routes.ts +10 -4
- package/src/runtime/routes/watcher-routes.ts +1 -3
- package/src/runtime/trust-class.test.ts +7 -6
- package/src/runtime/trust-class.ts +9 -17
- package/src/schedule/__tests__/worker-control.test.ts +36 -5
- package/src/schedule/worker-control.ts +15 -5
- package/src/security/secure-keys.ts +53 -103
- package/src/skills/clawhub.ts +3 -8
- package/src/skills/managed-store.ts +1 -5
- package/src/stt/types.ts +4 -0
- package/src/subagent/notify.ts +54 -34
- package/src/subagent/parent-notification.ts +6 -0
- package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
- package/src/telemetry/phone-call-funnel.ts +144 -0
- package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
- package/src/tools/ask-question/ask-question-tool.ts +6 -4
- package/src/tools/browser/browser-execution.ts +22 -59
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
- package/src/tools/client-os.ts +15 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
- package/src/tools/computer-use/target.ts +42 -0
- package/src/tools/document/document-tool.ts +13 -18
- package/src/tools/execution-target.ts +14 -16
- package/src/tools/host-terminal/host-shell.ts +4 -1
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
- package/src/tools/network/__tests__/web-search.test.ts +203 -0
- package/src/tools/network/web-fetch.ts +274 -7
- package/src/tools/network/web-search.ts +468 -6
- package/src/tools/permission-checker.ts +3 -3
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/skills/load.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +11 -2
- package/src/tools/skills/skill-script-runner.ts +7 -0
- package/src/tools/skills/skill-tool-factory.ts +15 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/terminal/shell.ts +2 -0
- package/src/tools/tool-approval-handler.ts +5 -1
- package/src/tools/tool-types.ts +3 -3
- package/src/tools/types.ts +8 -2
- package/src/util/__tests__/worker-process-command.test.ts +5 -4
- package/src/util/abort-reasons.ts +18 -26
- package/src/util/canonicalize-identity.test.ts +21 -0
- package/src/util/canonicalize-identity.ts +16 -0
- package/src/util/errors.ts +13 -3
- package/src/util/provider-error-patterns.ts +3 -0
- package/src/util/worker-process.ts +77 -35
- package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
- package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/computer-use-window-schema.test.ts +0 -24
- package/src/api/events/heartbeat-alert.ts +0 -20
- package/src/daemon/message-types/web-activity.ts +0 -65
- package/src/desktop/desktop-dependencies.test.ts +0 -131
- 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("
|
|
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
|
-
"
|
|
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/
|
|
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([
|
|
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
|
|
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(
|
|
354
|
-
|
|
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([
|
|
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(
|
|
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(
|
|
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([
|
|
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(
|
|
669
|
-
...Array(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
741
|
-
this.
|
|
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
|
-
...
|
|
759
|
-
this.killAll(new Map([[
|
|
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
|
|
820
|
+
const hasServiceChildren =
|
|
821
|
+
children.has("panel") || children.has("session-bus");
|
|
791
822
|
await this.waitForExits(
|
|
792
|
-
|
|
823
|
+
hasServiceChildren
|
|
824
|
+
? Promise.all([exits, sleep(this.killGraceMs)])
|
|
825
|
+
: exits,
|
|
793
826
|
);
|
|
794
827
|
for (const [role, child] of children) {
|
|
795
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
149
|
+
test("refuses a disabled desktop before checking components or starting processes", async () => {
|
|
151
150
|
const h = newFakeDesktop({ profileDir });
|
|
152
|
-
let
|
|
151
|
+
let checks = 0;
|
|
153
152
|
const b = newBridge(
|
|
154
153
|
h.manager,
|
|
155
154
|
async () => {
|
|
156
|
-
|
|
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(
|
|
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("
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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.
|
|
64
|
-
options.
|
|
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
|
-
|
|
84
|
-
await this.ensureInstalled();
|
|
82
|
+
this.assertReady();
|
|
85
83
|
if (!this.checkEnabled()) {
|
|
86
84
|
return;
|
|
87
85
|
}
|