@elizaos/app-core 2.0.3-beta.2 → 2.0.3-beta.3
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/agent-bridge.d.ts +5 -5
- package/api/auth/audit.d.ts +1 -1
- package/api/auth/auth-context.d.ts +1 -1
- package/api/auth/bootstrap-token.d.ts +1 -1
- package/api/auth/sessions.d.ts +1 -1
- package/api/auth-bootstrap-routes.d.ts +1 -1
- package/api/auth-pairing-routes.d.ts +1 -1
- package/api/auth-pairing-routes.d.ts.map +1 -1
- package/api/auth-pairing-routes.js +2 -3
- package/api/auth-session-routes.d.ts +2 -2
- package/api/auth.d.ts +1 -1
- package/api/automations-compat-routes.d.ts +1 -1
- package/api/background-tasks-routes.d.ts +1 -1
- package/api/catalog-routes.d.ts +1 -1
- package/api/cloud-voice-routes.d.ts +1 -1
- package/api/compat-route-shared.d.ts +12 -5
- package/api/compat-route-shared.d.ts.map +1 -1
- package/api/compat-route-shared.js +22 -187
- package/api/database-rows-compat-routes.d.ts +1 -1
- package/api/dev-compat-routes.d.ts +1 -1
- package/api/dev-route-catalog.d.ts.map +1 -1
- package/api/dev-route-catalog.js +20 -0
- package/api/drop-status-compat-route.d.ts +3 -0
- package/api/drop-status-compat-route.d.ts.map +1 -0
- package/api/drop-status-compat-route.js +12 -0
- package/api/first-run-routes.d.ts +1 -1
- package/api/internal-routes.d.ts +1 -1
- package/api/runtime-mode-routes.d.ts +1 -1
- package/api/secrets-manager-routes.d.ts.map +1 -1
- package/api/sensitive-request-routes.d.ts +2 -2
- package/api/server-config-filter.d.ts.map +1 -1
- package/api/server-config-filter.js +0 -1
- package/api/server-first-run-helpers.d.ts.map +1 -1
- package/api/server-first-run-helpers.js +0 -3
- package/api/server.d.ts +8 -8
- package/api/server.d.ts.map +1 -1
- package/api/server.js +13 -59
- package/api/training-benchmarks.d.ts +1 -1
- package/api/training-benchmarks.d.ts.map +1 -1
- package/api/training-benchmarks.js +31 -9
- package/api/workbench-compat-routes.d.ts +1 -1
- package/benchmark/server-utils.d.ts +2 -2
- package/browser.d.ts +4 -4
- package/cli/plugins-cli.d.ts.map +1 -1
- package/cli/plugins-cli.js +5 -0
- package/cli/program/register.auth.d.ts +27 -1
- package/cli/program/register.auth.d.ts.map +1 -1
- package/cli/program/register.auth.js +187 -0
- package/cli/program/register.dashboard.d.ts.map +1 -1
- package/cli/program/register.dashboard.js +11 -1
- package/cli/program.d.ts +1 -1
- package/first-run/first-run-config.d.ts +1 -1
- package/index.d.ts +46 -47
- package/index.d.ts.map +1 -1
- package/index.js +3 -7
- package/package.json +42 -36
- package/packaging/PUBLISHING_GUIDE.md +20 -20
- package/platform/elizaos-agent-browser-stub.d.ts +0 -1
- package/platform/elizaos-agent-browser-stub.d.ts.map +1 -1
- package/platform/elizaos-agent-browser-stub.js +0 -1
- package/platform/ios-runtime-bridge.d.ts.map +1 -1
- package/platform/ios-runtime-bridge.js +0 -1
- package/platform/native-plugin-entrypoints.d.ts +2 -2
- package/platforms/android/app/build.gradle +30 -7
- package/platforms/android/app/src/main/elizavoice-jni/elizavoice-jni.cpp +179 -35
- package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAgentService.java +60 -32
- package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBionicInferenceServer.java +405 -65
- package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceNative.java +35 -0
- package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoicePlugin.java +13 -6
- package/platforms/android/app/src/main/res/drawable/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/values/colors.xml +5 -1
- package/platforms/android/app/src/main/res/values/ic_launcher_background.xml +1 -1
- package/platforms/android/app/src/main/res/values/styles.xml +4 -1
- package/platforms/electrobun/electrobun.config.ts +0 -1
- package/platforms/electrobun/native/macos/window-effects.mm +4 -0
- package/platforms/electrobun/package.json +11 -9
- package/platforms/electrobun/remotes/local-model/src/bun/eliza1-catalog.ts +1 -1
- package/platforms/electrobun/remotes/runtime/src/dev/phase2-smoke.ts +10 -5
- package/platforms/electrobun/remotes/runtime/src/dev/phase3-smoke.ts +11 -15
- package/platforms/electrobun/src/application-menu.ts +0 -7
- package/platforms/electrobun/src/desktop-http-request.test.ts +30 -1
- package/platforms/electrobun/src/desktop-http-request.ts +14 -2
- package/platforms/electrobun/src/desktop-test-bridge-server.ts +28 -0
- package/platforms/electrobun/src/fatal-shutdown.test.ts +3 -7
- package/platforms/electrobun/src/index.ts +64 -13
- package/platforms/electrobun/src/lifecycle/api-base-owner.test.ts +83 -0
- package/platforms/electrobun/src/lifecycle/api-base-owner.ts +37 -16
- package/platforms/electrobun/src/native/agent.ts +28 -17
- package/platforms/electrobun/src/native/credentials.ts +6 -1
- package/platforms/electrobun/src/native/desktop-window.test.ts +142 -26
- package/platforms/electrobun/src/native/desktop.ts +8 -4
- package/platforms/electrobun/src/native/index.ts +6 -0
- package/platforms/electrobun/src/native/remote-plugin-host.ts +18 -8
- package/platforms/electrobun/src/preload.js +1 -1
- package/platforms/electrobun/src/voice/voice-live-validation.test.ts +10 -5
- package/platforms/electrobun/src/voice/voice-runtime-adapter.ts +3 -2
- package/platforms/electrobun/src/voice/voice-service.test.ts +26 -0
- package/platforms/electrobun/src/voice/voice-service.ts +57 -17
- package/platforms/electrobun/tsconfig.json +17 -1
- package/platforms/ios/App/App/Base.lproj/LaunchScreen.storyboard +4 -6
- package/registry/index.d.ts +1 -6
- package/registry/index.d.ts.map +1 -1
- package/registry/index.js +9 -101
- package/runtime/android-avf-microdroid-bridge.d.ts +1 -1
- package/runtime/bundled-fused-lib.d.ts +36 -0
- package/runtime/bundled-fused-lib.d.ts.map +1 -0
- package/runtime/bundled-fused-lib.js +77 -0
- package/runtime/channel-plugin-map.d.ts +1 -5
- package/runtime/channel-plugin-map.d.ts.map +1 -1
- package/runtime/channel-plugin-map.js +11 -31
- package/runtime/desktop/index.d.ts +5 -5
- package/runtime/dev-server.js +11 -2
- package/runtime/eliza.d.ts.map +1 -1
- package/runtime/eliza.js +18 -8
- package/runtime/mode/route-mode-guard.d.ts +1 -1
- package/runtime/mode/route-mode-matrix.d.ts +1 -1
- package/runtime/voice-warmup.d.ts +4 -19
- package/runtime/voice-warmup.d.ts.map +1 -1
- package/runtime/voice-warmup.js +4 -14
- package/scripts/aosp/README.md +10 -1
- package/scripts/aosp/compile-libllama-zig-pin.test.mjs +147 -0
- package/scripts/aosp/compile-libllama.mjs +446 -59
- package/scripts/aosp/compile-libllama.test.mjs +133 -0
- package/scripts/aosp/compile-shim.mjs +21 -1
- package/scripts/aosp/deploy-pixel.mjs +1 -1
- package/scripts/aosp/stage-default-models.mjs +24 -31
- package/scripts/aosp/stage-default-models.test.mjs +23 -0
- package/scripts/assert-required-bundled-packages.test.ts +22 -1
- package/scripts/build-helpers/verify-fused-symbols.mjs +33 -0
- package/scripts/build-helpers/verify-fused-symbols.test.mjs +0 -0
- package/scripts/build-image.sh +1 -3
- package/scripts/build-llama-cpp-mtp.mjs +17 -16
- package/scripts/check-i18n.mjs +0 -1
- package/scripts/copy-runtime-node-modules.ts +147 -0
- package/scripts/desktop-build.mjs +317 -4
- package/scripts/docker-ci-smoke.sh +33 -8
- package/scripts/ensure-type-package-aliases.mjs +1 -1
- package/scripts/ios-xcframework/run-physical-device-smoke.mjs +1 -1
- package/scripts/lib/agent-source-watcher.mjs +0 -1
- package/scripts/lib/artifact-staleness.d.mts +33 -0
- package/scripts/lib/artifact-staleness.mjs +122 -0
- package/scripts/lib/artifact-staleness.test.mts +98 -0
- package/scripts/lib/duet-bridge.mjs +1 -1
- package/scripts/lib/ios-plist-url-scheme.mjs +80 -0
- package/scripts/lib/mobile-build-decisions.mjs +97 -0
- package/scripts/lib/renderer-build-manifest.d.mts +65 -0
- package/scripts/lib/renderer-build-manifest.mjs +257 -0
- package/scripts/lib/renderer-build-manifest.test.mts +277 -0
- package/scripts/lib/stage-android-agent.mjs +13 -4
- package/scripts/lib/verify-ondevice-artifact.d.mts +16 -0
- package/scripts/lib/verify-ondevice-artifact.mjs +61 -0
- package/scripts/lib/verify-ondevice-artifact.test.mts +98 -0
- package/scripts/lib/websocket-pending-queue.ts +92 -0
- package/scripts/link-docker-local-app-packages.mjs +6 -0
- package/scripts/playwright-ui-live-stack.ts +430 -144
- package/scripts/playwright-ui-smoke-api-stub.mjs +160 -168
- package/scripts/run-local-plugin-live-smoke.mjs +1 -6
- package/scripts/run-mobile-build-brand-separation.test.mts +33 -0
- package/scripts/run-mobile-build-ios-engine-gate.test.mjs +85 -0
- package/scripts/run-mobile-build-mtp-staleness.test.mts +198 -0
- package/scripts/run-mobile-build.mjs +408 -86
- package/scripts/serve-real-local-agent.ts +72 -0
- package/scripts/stage-desktop-fused-lib.mjs +43 -14
- package/scripts/verify-ondevice-artifact.mjs +111 -0
- package/scripts/voice-attribution-smoke.ts +144 -38
- package/scripts/voice-duet.mjs +6 -6
- package/scripts/voice-e2e-hardware.ts +22 -16
- package/scripts/voice-interactive.mjs +16 -45
- package/security/agent-vault-id.d.ts +1 -1
- package/security/platform-secure-store-node.d.ts +1 -1
- package/services/ambient-audio/consent.d.ts +1 -1
- package/services/ambient-audio/index.d.ts +6 -6
- package/services/ambient-audio/replay-buffer.d.ts +1 -1
- package/services/ambient-audio/response-gate.d.ts +1 -1
- package/services/ambient-audio/service.d.ts +3 -3
- package/services/connector-target-catalog.d.ts +1 -1
- package/services/sensitive-requests/index.d.ts +6 -6
- package/services/steward-sidecar/wallet-setup.d.ts +1 -1
- package/services/steward-sidecar.d.ts +3 -3
- package/services/tunnel-to-mobile/index.d.ts +1 -1
- package/services/vault-bootstrap.d.ts.map +1 -1
- package/services/vault-bootstrap.js +14 -10
- package/services/voice-profiles/diarization-pipeline.d.ts +1 -1
- package/services/voice-profiles/index.d.ts +11 -11
- package/services/voice-profiles/owner-confidence.d.ts +1 -1
- package/services/voice-profiles/private-challenge.d.ts +1 -1
- package/services/voice-profiles/store.d.ts +1 -1
- package/test/helpers/live-provider.test.ts +91 -1
- package/test/helpers/live-provider.ts +195 -51
- package/test/scripts/websocket-pending-queue.test.ts +119 -0
- package/test-support/test-helpers.d.ts +0 -33
- package/test-support/test-helpers.d.ts.map +1 -1
- package/test-support/test-helpers.js +81 -189
- package/ui-compat.d.ts +1 -0
- package/ui-compat.d.ts.map +1 -1
- package/ui-compat.js +4 -0
- package/platforms/electrobun/docs/convergence-audit.json +0 -3505
- package/platforms/electrobun/docs/convergence-audit.md +0 -694
- package/platforms/electrobun/scripts/ensure-whisper-gguf.sh +0 -55
- package/platforms/electrobun/scripts/ensure-whisper-model.sh +0 -35
- package/platforms/electrobun/scripts/generate-convergence-audit.ts +0 -1203
- package/platforms/electrobun/src/native/whisper-env.test.ts +0 -71
- package/platforms/electrobun/src/native/whisper-env.ts +0 -68
- package/registry/app-registry.d.ts +0 -14
- package/registry/app-registry.d.ts.map +0 -1
- package/registry/app-registry.js +0 -29
- package/registry/entries/apps/app-polymarket.json +0 -31
- package/registry/entries/apps/clawville.json +0 -27
- package/registry/entries/apps/companion.json +0 -28
- package/registry/entries/apps/database-viewer.json +0 -27
- package/registry/entries/apps/defense-of-the-agents.json +0 -27
- package/registry/entries/apps/documents.json +0 -30
- package/registry/entries/apps/feed.json +0 -27
- package/registry/entries/apps/hyperliquid.json +0 -31
- package/registry/entries/apps/log-viewer.json +0 -27
- package/registry/entries/apps/memory-viewer.json +0 -27
- package/registry/entries/apps/model-tester.json +0 -31
- package/registry/entries/apps/plugin-viewer.json +0 -27
- package/registry/entries/apps/relationship-viewer.json +0 -27
- package/registry/entries/apps/runtime-debugger.json +0 -27
- package/registry/entries/apps/shopify.json +0 -31
- package/registry/entries/apps/skills-viewer.json +0 -27
- package/registry/entries/apps/steward.json +0 -31
- package/registry/entries/apps/training.json +0 -54
- package/registry/entries/apps/trajectory-viewer.json +0 -27
- package/registry/entries/apps/vincent.json +0 -31
- package/registry/entries/connectors/bluebubbles.json +0 -99
- package/registry/entries/connectors/bluesky.json +0 -173
- package/registry/entries/connectors/discord.json +0 -119
- package/registry/entries/connectors/farcaster.json +0 -174
- package/registry/entries/connectors/feishu.json +0 -79
- package/registry/entries/connectors/google-chat.json +0 -120
- package/registry/entries/connectors/google.json +0 -82
- package/registry/entries/connectors/imessage.json +0 -96
- package/registry/entries/connectors/instagram.json +0 -64
- package/registry/entries/connectors/line.json +0 -86
- package/registry/entries/connectors/matrix.json +0 -94
- package/registry/entries/connectors/mattermost.json +0 -110
- package/registry/entries/connectors/msteams.json +0 -104
- package/registry/entries/connectors/nextcloud-talk.json +0 -104
- package/registry/entries/connectors/nostr.json +0 -70
- package/registry/entries/connectors/signal.json +0 -81
- package/registry/entries/connectors/slack.json +0 -102
- package/registry/entries/connectors/telegram.json +0 -71
- package/registry/entries/connectors/tlon.json +0 -94
- package/registry/entries/connectors/twitch.json +0 -110
- package/registry/entries/connectors/whatsapp.json +0 -113
- package/registry/entries/connectors/x.json +0 -231
- package/registry/entries/connectors/zalo.json +0 -112
- package/registry/entries/connectors/zalouser.json +0 -122
- package/registry/entries/plugins/agent-orchestrator.json +0 -33
- package/registry/entries/plugins/agent-skills.json +0 -72
- package/registry/entries/plugins/anthropic.json +0 -73
- package/registry/entries/plugins/app-control.json +0 -23
- package/registry/entries/plugins/auto-trader.json +0 -203
- package/registry/entries/plugins/background-runner.json +0 -26
- package/registry/entries/plugins/blooio.json +0 -102
- package/registry/entries/plugins/browser.json +0 -75
- package/registry/entries/plugins/cli.json +0 -40
- package/registry/entries/plugins/clipboard.json +0 -44
- package/registry/entries/plugins/coding-tools.json +0 -71
- package/registry/entries/plugins/commands.json +0 -63
- package/registry/entries/plugins/computeruse.json +0 -74
- package/registry/entries/plugins/copilot-proxy.json +0 -93
- package/registry/entries/plugins/directives.json +0 -63
- package/registry/entries/plugins/edge-tts.json +0 -97
- package/registry/entries/plugins/elevenlabs.json +0 -169
- package/registry/entries/plugins/elizacloud.json +0 -208
- package/registry/entries/plugins/evm.json +0 -134
- package/registry/entries/plugins/experience.json +0 -34
- package/registry/entries/plugins/facewear.json +0 -131
- package/registry/entries/plugins/form.json +0 -26
- package/registry/entries/plugins/github.json +0 -93
- package/registry/entries/plugins/gmail-watch.json +0 -25
- package/registry/entries/plugins/goals.json +0 -77
- package/registry/entries/plugins/google-genai.json +0 -106
- package/registry/entries/plugins/groq.json +0 -93
- package/registry/entries/plugins/hedera.json +0 -48
- package/registry/entries/plugins/inmemorydb.json +0 -25
- package/registry/entries/plugins/linear.json +0 -51
- package/registry/entries/plugins/local-inference.json +0 -137
- package/registry/entries/plugins/local-storage.json +0 -36
- package/registry/entries/plugins/localdb.json +0 -25
- package/registry/entries/plugins/mcp.json +0 -44
- package/registry/entries/plugins/memory.json +0 -124
- package/registry/entries/plugins/minecraft.json +0 -79
- package/registry/entries/plugins/moltbook.json +0 -83
- package/registry/entries/plugins/music.json +0 -155
- package/registry/entries/plugins/mysticism.json +0 -48
- package/registry/entries/plugins/nearai.json +0 -82
- package/registry/entries/plugins/ngrok.json +0 -69
- package/registry/entries/plugins/ollama.json +0 -96
- package/registry/entries/plugins/openai.json +0 -189
- package/registry/entries/plugins/openrouter.json +0 -188
- package/registry/entries/plugins/pdf.json +0 -26
- package/registry/entries/plugins/plugin-manager.json +0 -23
- package/registry/entries/plugins/prose.json +0 -48
- package/registry/entries/plugins/rlm.json +0 -26
- package/registry/entries/plugins/roblox.json +0 -88
- package/registry/entries/plugins/rss.json +0 -64
- package/registry/entries/plugins/s3-storage.json +0 -91
- package/registry/entries/plugins/scheduling.json +0 -35
- package/registry/entries/plugins/shell.json +0 -94
- package/registry/entries/plugins/social-alpha.json +0 -72
- package/registry/entries/plugins/tailscale.json +0 -81
- package/registry/entries/plugins/tee.json +0 -53
- package/registry/entries/plugins/todos.json +0 -26
- package/registry/entries/plugins/trajectory-logger.json +0 -33
- package/registry/entries/plugins/trust.json +0 -39
- package/registry/entries/plugins/tts.json +0 -71
- package/registry/entries/plugins/tunnel.json +0 -45
- package/registry/entries/plugins/twilio.json +0 -168
- package/registry/entries/plugins/vercel-ai-gateway.json +0 -128
- package/registry/entries/plugins/video.json +0 -23
- package/registry/entries/plugins/vision.json +0 -43
- package/registry/entries/plugins/webhooks.json +0 -23
- package/registry/entries/plugins/workflow.json +0 -25
- package/registry/entries/plugins/xai.json +0 -75
- package/registry/loader.d.ts +0 -28
- package/registry/loader.d.ts.map +0 -1
- package/registry/loader.js +0 -158
- package/registry/schema.d.ts +0 -1199
- package/registry/schema.d.ts.map +0 -1
- package/registry/schema.js +0 -300
- package/scripts/patches/llama-mobile-kokoro-tts.patch +0 -480
- package/scripts/release-workflow-drift.test.ts +0 -57
- package/test/helpers/i18n.ts +0 -5
- package/test/helpers/loopback.ts +0 -33
- package/test/helpers/react-test.ts +0 -40
- package/test/helpers/skip-without.ts +0 -53
|
@@ -2,7 +2,12 @@ import fs from "node:fs";
|
|
|
2
2
|
import { createServer as createNetServer } from "node:net";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import {
|
|
7
|
+
formatError,
|
|
8
|
+
resolveApiToken,
|
|
9
|
+
resolveDesktopApiPort,
|
|
10
|
+
} from "@elizaos/shared";
|
|
6
11
|
import type { BrowserWindow } from "electrobun/bun";
|
|
7
12
|
import Electrobun, {
|
|
8
13
|
ApplicationMenu,
|
|
@@ -629,10 +634,23 @@ let surfaceWindowManager: SurfaceWindowManager | null = null;
|
|
|
629
634
|
let rendererUrlPromise: Promise<string> | null = null;
|
|
630
635
|
let backgroundWindowPromise: Promise<void> | null = null;
|
|
631
636
|
let isQuitting = false;
|
|
637
|
+
let quitRequestPromise: Promise<void> | null = null;
|
|
638
|
+
|
|
639
|
+
function requestAppQuit(): Promise<void> {
|
|
640
|
+
if (quitRequestPromise) {
|
|
641
|
+
return quitRequestPromise;
|
|
642
|
+
}
|
|
632
643
|
|
|
633
|
-
function requestAppQuit(): void {
|
|
634
644
|
isQuitting = true;
|
|
635
|
-
|
|
645
|
+
quitRequestPromise = (async () => {
|
|
646
|
+
await runShutdownCleanup("explicit-quit").catch((err) => {
|
|
647
|
+
logger.warn(
|
|
648
|
+
`[Main] Shutdown cleanup failed before explicit quit: ${formatError(err)}`,
|
|
649
|
+
);
|
|
650
|
+
});
|
|
651
|
+
Utils.quit();
|
|
652
|
+
})();
|
|
653
|
+
return quitRequestPromise;
|
|
636
654
|
}
|
|
637
655
|
|
|
638
656
|
/**
|
|
@@ -647,6 +665,7 @@ function isPackagedDesktopBuild(): boolean {
|
|
|
647
665
|
}
|
|
648
666
|
|
|
649
667
|
const cleanupFns: Array<() => void | Promise<void>> = [];
|
|
668
|
+
let shutdownCleanupPromise: Promise<void> | null = null;
|
|
650
669
|
let lastFocusedWindow: ManagedWindowLike | null = null;
|
|
651
670
|
const macOpenedDevtoolsWindowIds = new Set<number>();
|
|
652
671
|
|
|
@@ -911,8 +930,12 @@ async function resolveRendererUrl(): Promise<string> {
|
|
|
911
930
|
}
|
|
912
931
|
|
|
913
932
|
if (!rendererUrl) {
|
|
914
|
-
// Last resort: file:// (may have CORS issues with crossorigin module scripts)
|
|
915
|
-
|
|
933
|
+
// Last resort: file:// (may have CORS issues with crossorigin module scripts).
|
|
934
|
+
// pathToFileURL builds a valid file:///C:/… URL on Windows; `file://${winPath}`
|
|
935
|
+
// would be malformed (backslashes, drive letter parsed as host) and not load.
|
|
936
|
+
rendererUrl = pathToFileURL(
|
|
937
|
+
path.join(resolveRendererAssetDir(import.meta.dir), "index.html"),
|
|
938
|
+
).href;
|
|
916
939
|
logger.warn(
|
|
917
940
|
"[Main] Falling back to file:// renderer URL — CORS issues possible",
|
|
918
941
|
);
|
|
@@ -1160,7 +1183,7 @@ function attachMainWindow(
|
|
|
1160
1183
|
logger.info(
|
|
1161
1184
|
"[Main] Window close on Linux with no tray — quitting (no surface to restore from)",
|
|
1162
1185
|
);
|
|
1163
|
-
requestAppQuit();
|
|
1186
|
+
void requestAppQuit();
|
|
1164
1187
|
return;
|
|
1165
1188
|
}
|
|
1166
1189
|
|
|
@@ -2172,13 +2195,41 @@ function setupDockReopen(): void {
|
|
|
2172
2195
|
}
|
|
2173
2196
|
|
|
2174
2197
|
async function runShutdownCleanup(reason: string): Promise<void> {
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
sendToActiveRenderer("desktopShutdownStarted", { reason });
|
|
2178
|
-
for (const cleanupFn of cleanupFns) {
|
|
2179
|
-
await Promise.resolve(cleanupFn());
|
|
2198
|
+
if (shutdownCleanupPromise) {
|
|
2199
|
+
return shutdownCleanupPromise;
|
|
2180
2200
|
}
|
|
2181
|
-
|
|
2201
|
+
|
|
2202
|
+
shutdownCleanupPromise = (async () => {
|
|
2203
|
+
logger.info(`[Main] App quitting (${reason}), disposing native modules...`);
|
|
2204
|
+
isQuitting = true;
|
|
2205
|
+
sendToActiveRenderer("desktopShutdownStarted", { reason });
|
|
2206
|
+
const cleanupFnsToRun = cleanupFns.splice(0);
|
|
2207
|
+
const cleanupResults = await Promise.allSettled(
|
|
2208
|
+
cleanupFnsToRun.map((cleanupFn) => Promise.resolve().then(cleanupFn)),
|
|
2209
|
+
);
|
|
2210
|
+
for (const result of cleanupResults) {
|
|
2211
|
+
if (result.status === "rejected") {
|
|
2212
|
+
logger.warn(
|
|
2213
|
+
`[Main] Shutdown cleanup callback failed: ${
|
|
2214
|
+
result.reason instanceof Error
|
|
2215
|
+
? result.reason.message
|
|
2216
|
+
: String(result.reason)
|
|
2217
|
+
}`,
|
|
2218
|
+
);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
try {
|
|
2222
|
+
await disposeNativeModules();
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
logger.warn(
|
|
2225
|
+
`[Main] Native module disposal failed during shutdown: ${
|
|
2226
|
+
error instanceof Error ? error.message : String(error)
|
|
2227
|
+
}`,
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
})();
|
|
2231
|
+
|
|
2232
|
+
return shutdownCleanupPromise;
|
|
2182
2233
|
}
|
|
2183
2234
|
|
|
2184
2235
|
function setupShutdown(): void {
|
|
@@ -2632,7 +2683,7 @@ async function main(): Promise<void> {
|
|
|
2632
2683
|
});
|
|
2633
2684
|
getDesktopManager().setRestoreMainWindowCallback(() => restoreWindow());
|
|
2634
2685
|
getDesktopManager().setRequestQuitCallback(() => {
|
|
2635
|
-
requestAppQuit();
|
|
2686
|
+
void requestAppQuit();
|
|
2636
2687
|
});
|
|
2637
2688
|
getDesktopManager().setOpenSurfaceWindowCallback(
|
|
2638
2689
|
(surface, browse, alwaysOnTop) => {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { getCurrent, injectIntoHtml, setCurrent } from "./api-base-owner";
|
|
6
|
+
|
|
7
|
+
const ENV_KEYS = [
|
|
8
|
+
"ELIZA_STARTUP_SESSION_ID",
|
|
9
|
+
"ELIZA_STARTUP_STATE_FILE",
|
|
10
|
+
"ELIZA_STARTUP_EVENTS_FILE",
|
|
11
|
+
"HOME",
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
const originalEnv = new Map<string, string | undefined>();
|
|
15
|
+
let tempHome: string | null = null;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
for (const key of ENV_KEYS) {
|
|
19
|
+
originalEnv.set(key, process.env[key]);
|
|
20
|
+
}
|
|
21
|
+
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "eliza-api-base-owner-"));
|
|
22
|
+
process.env.HOME = tempHome;
|
|
23
|
+
delete process.env.ELIZA_STARTUP_SESSION_ID;
|
|
24
|
+
delete process.env.ELIZA_STARTUP_STATE_FILE;
|
|
25
|
+
delete process.env.ELIZA_STARTUP_EVENTS_FILE;
|
|
26
|
+
setCurrent(null);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
setCurrent(null);
|
|
31
|
+
for (const key of ENV_KEYS) {
|
|
32
|
+
const value = originalEnv.get(key);
|
|
33
|
+
if (value === undefined) {
|
|
34
|
+
delete process.env[key];
|
|
35
|
+
} else {
|
|
36
|
+
process.env[key] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
originalEnv.clear();
|
|
40
|
+
if (tempHome) {
|
|
41
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
42
|
+
tempHome = null;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("api-base-owner", () => {
|
|
47
|
+
it("leaves HTML unchanged without an API base or startup trace id", () => {
|
|
48
|
+
const html = "<html><head></head><body></body></html>";
|
|
49
|
+
expect(injectIntoHtml(html)).toBe(html);
|
|
50
|
+
expect(getCurrent().base).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("injects the host startup trace id before renderer JavaScript runs", () => {
|
|
54
|
+
process.env.ELIZA_STARTUP_SESSION_ID = "desktop-session-123";
|
|
55
|
+
const html = "<html><head></head><body></body></html>";
|
|
56
|
+
|
|
57
|
+
const injected = injectIntoHtml(html);
|
|
58
|
+
|
|
59
|
+
expect(injected).toContain(
|
|
60
|
+
'window.__ELIZA_STARTUP_TRACE_ID__="desktop-session-123";',
|
|
61
|
+
);
|
|
62
|
+
expect(injected).not.toContain("window.__ELIZA_API_BASE__=");
|
|
63
|
+
expect(injected.indexOf("<script>")).toBeLessThan(
|
|
64
|
+
injected.indexOf("</head>"),
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("injects startup trace id alongside the current API base snapshot", () => {
|
|
69
|
+
process.env.ELIZA_STARTUP_SESSION_ID = "desktop-session-456";
|
|
70
|
+
setCurrent("http://127.0.0.1:31337", "dev-token");
|
|
71
|
+
|
|
72
|
+
const injected = injectIntoHtml("<html><head></head><body></body></html>");
|
|
73
|
+
|
|
74
|
+
expect(injected).toContain(
|
|
75
|
+
'window.__ELIZA_STARTUP_TRACE_ID__="desktop-session-456";',
|
|
76
|
+
);
|
|
77
|
+
expect(injected).toContain(
|
|
78
|
+
'window.__ELIZA_API_BASE__="http://127.0.0.1:31337";',
|
|
79
|
+
);
|
|
80
|
+
expect(injected).toContain('"__ELIZA_API_TOKEN__"');
|
|
81
|
+
expect(injected).toContain("elizaos.app.boot-config");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -31,8 +31,10 @@
|
|
|
31
31
|
|
|
32
32
|
import {
|
|
33
33
|
pushApiBaseToRenderer,
|
|
34
|
+
resolveDesktopRuntimeMode,
|
|
34
35
|
resolveDesktopRuntimeModeSignal,
|
|
35
36
|
} from "../api-base";
|
|
37
|
+
import { getStartupTraceConfig } from "../startup-trace";
|
|
36
38
|
|
|
37
39
|
interface ApiBaseSnapshot {
|
|
38
40
|
base: string | null;
|
|
@@ -45,6 +47,10 @@ function safeJsonForHtml(value: unknown): string {
|
|
|
45
47
|
return JSON.stringify(value).replace(/<\//g, "<\\/");
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
function resolveStartupTraceId(): string | null {
|
|
51
|
+
return getStartupTraceConfig().sessionId;
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
/**
|
|
49
55
|
* Update the singleton with the latest known API base + token. Subsequent
|
|
50
56
|
* `injectIntoHtml(...)` and `pushToWindow(...)` calls read this state.
|
|
@@ -78,23 +84,38 @@ export function getCurrent(): Readonly<ApiBaseSnapshot> {
|
|
|
78
84
|
* apiBase and every `/api/*` call returns SPA HTML.
|
|
79
85
|
*/
|
|
80
86
|
export function injectIntoHtml(html: string): string {
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
const tokenInject = tokenLiteral
|
|
85
|
-
? `Object.defineProperty(window,"__ELIZA_API_TOKEN__",{value:${tokenLiteral},configurable:true,writable:true,enumerable:false});`
|
|
86
|
-
: "";
|
|
87
|
-
const bootConfigInject = `(function(){var k=Symbol.for("elizaos.app.boot-config"),w=window,prev=w.__ELIZAOS_APP_BOOT_CONFIG__||w.__ELIZA_APP_BOOT_CONFIG__||(w[k]&&w[k].current)||{},next=Object.assign({},prev,{apiBase:${baseLiteral}${tokenLiteral ? `,apiToken:${tokenLiteral}` : ""}});w.__ELIZAOS_APP_BOOT_CONFIG__=next;w.__ELIZA_APP_BOOT_CONFIG__=next;w[k]={current:next};})();`;
|
|
88
|
-
// Desktop cloud-only opt-in: expose the runtime-mode signal as a window global
|
|
89
|
-
// before any renderer JS runs, so the renderer's cloud-only branding
|
|
90
|
-
// (shouldUseCloudOnlyBranding) resolves correctly at module-eval time. Only
|
|
91
|
-
// injected when explicitly cloud, so the default desktop/web behavior is
|
|
92
|
-
// unchanged.
|
|
93
|
-
const runtimeModeSignal = resolveDesktopRuntimeModeSignal(process.env);
|
|
94
|
-
const runtimeModeInject = runtimeModeSignal
|
|
95
|
-
? `window.__ELIZA_DESKTOP_RUNTIME_MODE__=${safeJsonForHtml(runtimeModeSignal)};`
|
|
87
|
+
const startupTraceId = resolveStartupTraceId();
|
|
88
|
+
const startupTraceInject = startupTraceId
|
|
89
|
+
? `window.__ELIZA_STARTUP_TRACE_ID__=${safeJsonForHtml(startupTraceId)};`
|
|
96
90
|
: "";
|
|
97
|
-
|
|
91
|
+
if (!current.base && !startupTraceInject) return html;
|
|
92
|
+
|
|
93
|
+
let apiBaseInject = "";
|
|
94
|
+
if (current.base) {
|
|
95
|
+
const baseLiteral = safeJsonForHtml(current.base);
|
|
96
|
+
const tokenLiteral = current.token ? safeJsonForHtml(current.token) : "";
|
|
97
|
+
const tokenInject = tokenLiteral
|
|
98
|
+
? `Object.defineProperty(window,"__ELIZA_API_TOKEN__",{value:${tokenLiteral},configurable:true,writable:true,enumerable:false});`
|
|
99
|
+
: "";
|
|
100
|
+
const bootConfigInject = `(function(){var k=Symbol.for("elizaos.app.boot-config"),w=window,prev=w.__ELIZAOS_APP_BOOT_CONFIG__||w.__ELIZA_APP_BOOT_CONFIG__||(w[k]&&w[k].current)||{},next=Object.assign({},prev,{apiBase:${baseLiteral}${tokenLiteral ? `,apiToken:${tokenLiteral}` : ""}});w.__ELIZAOS_APP_BOOT_CONFIG__=next;w.__ELIZA_APP_BOOT_CONFIG__=next;w[k]={current:next};})();`;
|
|
101
|
+
// Desktop cloud-only opt-in: expose the runtime-mode signal as a window global
|
|
102
|
+
// before any renderer JS runs, so the renderer's cloud-only branding
|
|
103
|
+
// (shouldUseCloudOnlyBranding) resolves correctly at module-eval time. Only
|
|
104
|
+
// injected when explicitly cloud, so the default desktop/web behavior is
|
|
105
|
+
// unchanged.
|
|
106
|
+
const runtimeModeSignal = resolveDesktopRuntimeModeSignal(process.env);
|
|
107
|
+
const runtimeModeInject = runtimeModeSignal
|
|
108
|
+
? `window.__ELIZA_DESKTOP_RUNTIME_MODE__=${safeJsonForHtml(runtimeModeSignal)};`
|
|
109
|
+
: "";
|
|
110
|
+
const runtime = resolveDesktopRuntimeMode(process.env);
|
|
111
|
+
const externalApiBaseInject =
|
|
112
|
+
runtime.mode === "external" && runtime.externalApi.base
|
|
113
|
+
? `window.__ELIZA_DESKTOP_EXTERNAL_API_BASE__=${safeJsonForHtml(runtime.externalApi.base)};`
|
|
114
|
+
: "";
|
|
115
|
+
apiBaseInject = `window.__ELIZA_API_BASE__=${baseLiteral};${runtimeModeInject}${externalApiBaseInject}${tokenInject}${bootConfigInject}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const script = `<script>${startupTraceInject}${apiBaseInject}</script>`;
|
|
98
119
|
if (html.includes("</head>")) {
|
|
99
120
|
return html.replace("</head>", `${script}</head>`);
|
|
100
121
|
}
|
|
@@ -56,7 +56,6 @@ import { logger } from "../logger";
|
|
|
56
56
|
import { recordStartupPhase, resolveStartupBundlePath } from "../startup-trace";
|
|
57
57
|
import type { SendToWebview } from "../types.js";
|
|
58
58
|
import { findFirstAvailableLoopbackPort } from "./loopback-port";
|
|
59
|
-
import { applyBundledWhisperEnv } from "./whisper-env";
|
|
60
59
|
|
|
61
60
|
// ---------------------------------------------------------------------------
|
|
62
61
|
// Types
|
|
@@ -1271,21 +1270,39 @@ function shouldAutoRecoverPgliteFailure(line: string): boolean {
|
|
|
1271
1270
|
* Set ELIZA_AGENT_RECLAIM_STALE_PORT=1 to restore the old "take over default
|
|
1272
1271
|
* port" behavior.
|
|
1273
1272
|
*/
|
|
1273
|
+
/**
|
|
1274
|
+
* PIDs listening on `port`. POSIX uses `lsof`; Windows uses
|
|
1275
|
+
* `Get-NetTCPConnection` (locale-independent and returns the owning PID
|
|
1276
|
+
* directly — `lsof` does not exist on Windows, so the reclaim was a silent
|
|
1277
|
+
* no-op there before).
|
|
1278
|
+
*/
|
|
1279
|
+
function listListeningPids(port: number): number[] {
|
|
1280
|
+
const result =
|
|
1281
|
+
process.platform === "win32"
|
|
1282
|
+
? Bun.spawnSync([
|
|
1283
|
+
"powershell.exe",
|
|
1284
|
+
"-NoProfile",
|
|
1285
|
+
"-Command",
|
|
1286
|
+
`(Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue).OwningProcess`,
|
|
1287
|
+
])
|
|
1288
|
+
: Bun.spawnSync(["lsof", "-ti", `tcp:${port}`]);
|
|
1289
|
+
return new TextDecoder()
|
|
1290
|
+
.decode(result.stdout)
|
|
1291
|
+
.trim()
|
|
1292
|
+
.split(/\r?\n/)
|
|
1293
|
+
.map((p) => parseInt(p.trim(), 10))
|
|
1294
|
+
.filter((n) => !Number.isNaN(n));
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1274
1297
|
async function maybeReclaimPortWithSigkill(port: number): Promise<void> {
|
|
1275
1298
|
const raw = process.env.ELIZA_AGENT_RECLAIM_STALE_PORT?.trim().toLowerCase();
|
|
1276
1299
|
if (raw !== "1" && raw !== "true" && raw !== "yes") {
|
|
1277
1300
|
return;
|
|
1278
1301
|
}
|
|
1279
1302
|
try {
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
.trim()
|
|
1284
|
-
.split("\n")
|
|
1285
|
-
.filter(Boolean);
|
|
1286
|
-
for (const pid of pids) {
|
|
1287
|
-
const numPid = parseInt(pid, 10);
|
|
1288
|
-
if (!Number.isNaN(numPid) && numPid !== process.pid) {
|
|
1303
|
+
const pids = listListeningPids(port);
|
|
1304
|
+
for (const numPid of pids) {
|
|
1305
|
+
if (numPid !== process.pid) {
|
|
1289
1306
|
diagnosticLog(
|
|
1290
1307
|
`[Agent] Reclaim: killing process ${numPid} on port ${port} (ELIZA_AGENT_RECLAIM_STALE_PORT)`,
|
|
1291
1308
|
);
|
|
@@ -1300,7 +1317,7 @@ async function maybeReclaimPortWithSigkill(port: number): Promise<void> {
|
|
|
1300
1317
|
await new Promise((r) => setTimeout(r, 500));
|
|
1301
1318
|
}
|
|
1302
1319
|
} catch {
|
|
1303
|
-
//
|
|
1320
|
+
// port-listing tool missing — ignore
|
|
1304
1321
|
}
|
|
1305
1322
|
}
|
|
1306
1323
|
|
|
@@ -1550,12 +1567,6 @@ export class AgentManager {
|
|
|
1550
1567
|
cwd: runtimeDistPath,
|
|
1551
1568
|
});
|
|
1552
1569
|
applyDatabaseResolutionToEnv(childEnv, databaseResolution);
|
|
1553
|
-
const bundledWhisper = applyBundledWhisperEnv(childEnv, runtimeDistPath);
|
|
1554
|
-
if (bundledWhisper) {
|
|
1555
|
-
diagnosticLog(
|
|
1556
|
-
`[Agent] Bundled Whisper ASR: ${bundledWhisper.libraryPath}`,
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
1570
|
const effectiveTarget =
|
|
1560
1571
|
databaseResolution.mode === "postgres"
|
|
1561
1572
|
? redactDatabaseTarget(databaseResolution.postgresUrl)
|
|
@@ -82,7 +82,12 @@ function isTruthyFlag(value: string | undefined): boolean {
|
|
|
82
82
|
|
|
83
83
|
async function isCliInstalled(name: string): Promise<boolean> {
|
|
84
84
|
try {
|
|
85
|
-
|
|
85
|
+
// `which` does not exist on Windows — use `where`. Without this, every CLI
|
|
86
|
+
// probe (claude, gh, ollama, codex, gemini, gcloud …) spawns a missing
|
|
87
|
+
// binary, returns false, and the desktop app reports installed CLIs as
|
|
88
|
+
// absent on Windows.
|
|
89
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
90
|
+
const proc = Bun.spawn([probe, name], {
|
|
86
91
|
stdout: "pipe",
|
|
87
92
|
stderr: "ignore",
|
|
88
93
|
});
|
|
@@ -1,9 +1,110 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { DesktopManager, resetDesktopManagerForTesting } from "./desktop";
|
|
3
3
|
|
|
4
|
+
const electrobunMock = vi.hoisted(() => {
|
|
5
|
+
type Handler = (event?: unknown) => void;
|
|
6
|
+
const handlers = new Map<string, Handler[]>();
|
|
7
|
+
const trayInstances: Array<{
|
|
8
|
+
on: ReturnType<typeof vi.fn>;
|
|
9
|
+
off: ReturnType<typeof vi.fn>;
|
|
10
|
+
remove: ReturnType<typeof vi.fn>;
|
|
11
|
+
setImage: ReturnType<typeof vi.fn>;
|
|
12
|
+
setMenu: ReturnType<typeof vi.fn>;
|
|
13
|
+
setTitle: ReturnType<typeof vi.fn>;
|
|
14
|
+
}> = [];
|
|
15
|
+
const events = {
|
|
16
|
+
on: vi.fn((event: string, handler: Handler) => {
|
|
17
|
+
const list = handlers.get(event) ?? [];
|
|
18
|
+
list.push(handler);
|
|
19
|
+
handlers.set(event, list);
|
|
20
|
+
}),
|
|
21
|
+
off: vi.fn((event: string, handler: Handler) => {
|
|
22
|
+
handlers.set(
|
|
23
|
+
event,
|
|
24
|
+
(handlers.get(event) ?? []).filter((item) => item !== handler),
|
|
25
|
+
);
|
|
26
|
+
}),
|
|
27
|
+
emit(event: string, payload?: unknown) {
|
|
28
|
+
for (const handler of handlers.get(event) ?? []) {
|
|
29
|
+
handler(payload);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const Tray = vi.fn(function FakeTray(this: {
|
|
34
|
+
on: ReturnType<typeof vi.fn>;
|
|
35
|
+
off: ReturnType<typeof vi.fn>;
|
|
36
|
+
remove: ReturnType<typeof vi.fn>;
|
|
37
|
+
setImage: ReturnType<typeof vi.fn>;
|
|
38
|
+
setMenu: ReturnType<typeof vi.fn>;
|
|
39
|
+
setTitle: ReturnType<typeof vi.fn>;
|
|
40
|
+
}) {
|
|
41
|
+
this.on = vi.fn();
|
|
42
|
+
this.off = vi.fn();
|
|
43
|
+
this.remove = vi.fn();
|
|
44
|
+
this.setImage = vi.fn();
|
|
45
|
+
this.setMenu = vi.fn();
|
|
46
|
+
this.setTitle = vi.fn();
|
|
47
|
+
trayInstances.push(this);
|
|
48
|
+
});
|
|
49
|
+
const Utils = {
|
|
50
|
+
clipboard: {},
|
|
51
|
+
openExternal: vi.fn(),
|
|
52
|
+
paths: {
|
|
53
|
+
home: "/tmp",
|
|
54
|
+
appData: "/tmp",
|
|
55
|
+
userData: "/tmp",
|
|
56
|
+
userCache: "/tmp",
|
|
57
|
+
userLogs: "/tmp",
|
|
58
|
+
temp: "/tmp",
|
|
59
|
+
cache: "/tmp",
|
|
60
|
+
logs: "/tmp",
|
|
61
|
+
config: "/tmp",
|
|
62
|
+
documents: "/tmp",
|
|
63
|
+
downloads: "/tmp",
|
|
64
|
+
desktop: "/tmp",
|
|
65
|
+
pictures: "/tmp",
|
|
66
|
+
music: "/tmp",
|
|
67
|
+
videos: "/tmp",
|
|
68
|
+
},
|
|
69
|
+
quit: vi.fn(),
|
|
70
|
+
showNotification: vi.fn(),
|
|
71
|
+
showItemInFolder: vi.fn(),
|
|
72
|
+
};
|
|
73
|
+
const GlobalShortcut = {
|
|
74
|
+
isRegistered: vi.fn(() => false),
|
|
75
|
+
register: vi.fn(),
|
|
76
|
+
unregister: vi.fn(),
|
|
77
|
+
unregisterAll: vi.fn(),
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
events,
|
|
81
|
+
handlers,
|
|
82
|
+
trayInstances,
|
|
83
|
+
GlobalShortcut,
|
|
84
|
+
Tray,
|
|
85
|
+
Utils,
|
|
86
|
+
reset() {
|
|
87
|
+
handlers.clear();
|
|
88
|
+
trayInstances.splice(0);
|
|
89
|
+
events.on.mockClear();
|
|
90
|
+
events.off.mockClear();
|
|
91
|
+
GlobalShortcut.isRegistered.mockClear();
|
|
92
|
+
GlobalShortcut.register.mockClear();
|
|
93
|
+
GlobalShortcut.unregister.mockClear();
|
|
94
|
+
GlobalShortcut.unregisterAll.mockClear();
|
|
95
|
+
Tray.mockClear();
|
|
96
|
+
for (const value of Object.values(Utils)) {
|
|
97
|
+
if (typeof value === "function" && "mockClear" in value) {
|
|
98
|
+
value.mockClear();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
|
|
4
105
|
vi.mock("electrobun/bun", () => {
|
|
5
106
|
return {
|
|
6
|
-
default: {},
|
|
107
|
+
default: { events: electrobunMock.events },
|
|
7
108
|
BrowserView: vi.fn(),
|
|
8
109
|
BuildConfig: {
|
|
9
110
|
appIdentifier: "test.eliza",
|
|
@@ -12,38 +113,16 @@ vi.mock("electrobun/bun", () => {
|
|
|
12
113
|
ContextMenu: {
|
|
13
114
|
on: vi.fn(),
|
|
14
115
|
},
|
|
15
|
-
GlobalShortcut:
|
|
116
|
+
GlobalShortcut: electrobunMock.GlobalShortcut,
|
|
16
117
|
Screen: {
|
|
17
118
|
getAllDisplays: vi.fn(() => []),
|
|
18
119
|
},
|
|
19
120
|
Session: {
|
|
20
121
|
defaultSession: {},
|
|
21
122
|
},
|
|
22
|
-
Tray:
|
|
123
|
+
Tray: electrobunMock.Tray,
|
|
23
124
|
Updater: {},
|
|
24
|
-
Utils:
|
|
25
|
-
clipboard: {},
|
|
26
|
-
openExternal: vi.fn(),
|
|
27
|
-
paths: {
|
|
28
|
-
home: "/tmp",
|
|
29
|
-
appData: "/tmp",
|
|
30
|
-
userData: "/tmp",
|
|
31
|
-
userCache: "/tmp",
|
|
32
|
-
userLogs: "/tmp",
|
|
33
|
-
temp: "/tmp",
|
|
34
|
-
cache: "/tmp",
|
|
35
|
-
logs: "/tmp",
|
|
36
|
-
config: "/tmp",
|
|
37
|
-
documents: "/tmp",
|
|
38
|
-
downloads: "/tmp",
|
|
39
|
-
desktop: "/tmp",
|
|
40
|
-
pictures: "/tmp",
|
|
41
|
-
music: "/tmp",
|
|
42
|
-
videos: "/tmp",
|
|
43
|
-
},
|
|
44
|
-
showNotification: vi.fn(),
|
|
45
|
-
showItemInFolder: vi.fn(),
|
|
46
|
-
},
|
|
125
|
+
Utils: electrobunMock.Utils,
|
|
47
126
|
};
|
|
48
127
|
});
|
|
49
128
|
|
|
@@ -124,6 +203,7 @@ describe("DesktopManager main window controls", () => {
|
|
|
124
203
|
|
|
125
204
|
beforeEach(() => {
|
|
126
205
|
resetDesktopManagerForTesting();
|
|
206
|
+
electrobunMock.reset();
|
|
127
207
|
delete process.env.ELIZAOS_CLOSE_MINIMIZES_TO_TRAY;
|
|
128
208
|
});
|
|
129
209
|
|
|
@@ -297,4 +377,40 @@ describe("DesktopManager main window controls", () => {
|
|
|
297
377
|
expect(second.on).toHaveBeenCalledWith("focus", expect.any(Function));
|
|
298
378
|
expect(second.on).toHaveBeenCalledWith("blur", expect.any(Function));
|
|
299
379
|
});
|
|
380
|
+
|
|
381
|
+
it("routes tray quit through the app quit callback", async () => {
|
|
382
|
+
const manager = new DesktopManager();
|
|
383
|
+
const requestQuit = vi.fn(async () => {});
|
|
384
|
+
manager.setRequestQuitCallback(requestQuit);
|
|
385
|
+
|
|
386
|
+
await manager.createTray({
|
|
387
|
+
icon: "/tmp/appIcon.png",
|
|
388
|
+
menu: [{ id: "quit", label: "Quit" }],
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
electrobunMock.events.emit("tray-clicked", {
|
|
392
|
+
data: { action: "quit" },
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
await vi.waitFor(() => expect(requestQuit).toHaveBeenCalledTimes(1));
|
|
396
|
+
expect(electrobunMock.Utils.quit).not.toHaveBeenCalled();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("awaits tray teardown during dispose", async () => {
|
|
400
|
+
const manager = new DesktopManager();
|
|
401
|
+
await manager.createTray({
|
|
402
|
+
icon: "/tmp/appIcon.png",
|
|
403
|
+
menu: [{ id: "quit", label: "Quit" }],
|
|
404
|
+
});
|
|
405
|
+
const tray = electrobunMock.trayInstances[0];
|
|
406
|
+
|
|
407
|
+
await manager.dispose();
|
|
408
|
+
|
|
409
|
+
expect(tray.off).toHaveBeenCalledWith("tray-clicked", expect.any(Function));
|
|
410
|
+
expect(electrobunMock.events.off).toHaveBeenCalledWith(
|
|
411
|
+
"tray-clicked",
|
|
412
|
+
expect.any(Function),
|
|
413
|
+
);
|
|
414
|
+
expect(tray.remove).toHaveBeenCalledTimes(1);
|
|
415
|
+
});
|
|
300
416
|
});
|
|
@@ -730,7 +730,11 @@ export class DesktopManager {
|
|
|
730
730
|
} else if (action === "restart-agent" || action === "tray-restart") {
|
|
731
731
|
triggerAgentRestart();
|
|
732
732
|
} else if (action === "quit") {
|
|
733
|
-
|
|
733
|
+
void this.quit().catch((err: unknown) => {
|
|
734
|
+
logger.warn(
|
|
735
|
+
`[Desktop] Failed to quit from tray menu: ${err instanceof Error ? err.message : String(err)}`,
|
|
736
|
+
);
|
|
737
|
+
});
|
|
734
738
|
} else if (action === "open-settings") {
|
|
735
739
|
this.openSettingsCallback?.();
|
|
736
740
|
}
|
|
@@ -2326,7 +2330,7 @@ X-GNOME-Autostart-enabled=true
|
|
|
2326
2330
|
/**
|
|
2327
2331
|
* Clean up all resources.
|
|
2328
2332
|
*/
|
|
2329
|
-
dispose(): void {
|
|
2333
|
+
async dispose(): Promise<void> {
|
|
2330
2334
|
if (this._focusPoller) {
|
|
2331
2335
|
clearInterval(this._focusPoller);
|
|
2332
2336
|
this._focusPoller = null;
|
|
@@ -2336,8 +2340,8 @@ X-GNOME-Autostart-enabled=true
|
|
|
2336
2340
|
this.releaseNotesView?.remove();
|
|
2337
2341
|
this.releaseNotesView = null;
|
|
2338
2342
|
this.releaseNotesWindow = null;
|
|
2339
|
-
this.unregisterAllShortcuts();
|
|
2340
|
-
|
|
2343
|
+
await this.unregisterAllShortcuts();
|
|
2344
|
+
await this.destroyTray();
|
|
2341
2345
|
this.trayMenuItems.clear();
|
|
2342
2346
|
this.sendToWebview = null;
|
|
2343
2347
|
}
|
|
@@ -18,6 +18,7 @@ import { getSwabbleManager } from "./swabble";
|
|
|
18
18
|
import { getTalkModeManager } from "./talkmode";
|
|
19
19
|
|
|
20
20
|
const NATIVE_DISPOSE_TIMEOUT_MS = 10_000;
|
|
21
|
+
let nativeDisposePromise: Promise<void> | null = null;
|
|
21
22
|
|
|
22
23
|
export function initializeNativeModules(
|
|
23
24
|
mainWindow: BrowserWindow,
|
|
@@ -43,6 +44,11 @@ export function initializeNativeModules(
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export async function disposeNativeModules(): Promise<void> {
|
|
47
|
+
nativeDisposePromise ??= disposeNativeModulesOnce();
|
|
48
|
+
return nativeDisposePromise;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function disposeNativeModulesOnce(): Promise<void> {
|
|
46
52
|
const managers = [
|
|
47
53
|
["agent", getAgentManager()],
|
|
48
54
|
["browser-workspace", getBrowserWorkspaceManager()],
|