@elizaos/app-core 2.0.3-beta.2 → 2.0.3-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/agent-bridge.d.ts +5 -5
  2. package/api/auth/audit.d.ts +1 -1
  3. package/api/auth/auth-context.d.ts +1 -1
  4. package/api/auth/bootstrap-token.d.ts +1 -1
  5. package/api/auth/sessions.d.ts +1 -1
  6. package/api/auth-bootstrap-routes.d.ts +1 -1
  7. package/api/auth-pairing-routes.d.ts +1 -1
  8. package/api/auth-pairing-routes.d.ts.map +1 -1
  9. package/api/auth-pairing-routes.js +2 -3
  10. package/api/auth-session-routes.d.ts +2 -2
  11. package/api/auth.d.ts +1 -1
  12. package/api/automations-compat-routes.d.ts +1 -1
  13. package/api/background-tasks-routes.d.ts +1 -1
  14. package/api/catalog-routes.d.ts +1 -1
  15. package/api/cloud-voice-routes.d.ts +1 -1
  16. package/api/compat-route-shared.d.ts +12 -5
  17. package/api/compat-route-shared.d.ts.map +1 -1
  18. package/api/compat-route-shared.js +22 -187
  19. package/api/database-rows-compat-routes.d.ts +1 -1
  20. package/api/dev-compat-routes.d.ts +1 -1
  21. package/api/dev-route-catalog.d.ts.map +1 -1
  22. package/api/dev-route-catalog.js +20 -0
  23. package/api/drop-status-compat-route.d.ts +3 -0
  24. package/api/drop-status-compat-route.d.ts.map +1 -0
  25. package/api/drop-status-compat-route.js +12 -0
  26. package/api/first-run-routes.d.ts +1 -1
  27. package/api/internal-routes.d.ts +1 -1
  28. package/api/runtime-mode-routes.d.ts +1 -1
  29. package/api/secrets-manager-routes.d.ts.map +1 -1
  30. package/api/sensitive-request-routes.d.ts +2 -2
  31. package/api/server-config-filter.d.ts.map +1 -1
  32. package/api/server-config-filter.js +0 -1
  33. package/api/server-first-run-helpers.d.ts.map +1 -1
  34. package/api/server-first-run-helpers.js +0 -3
  35. package/api/server.d.ts +8 -8
  36. package/api/server.d.ts.map +1 -1
  37. package/api/server.js +13 -59
  38. package/api/training-benchmarks.d.ts +1 -1
  39. package/api/training-benchmarks.d.ts.map +1 -1
  40. package/api/training-benchmarks.js +31 -9
  41. package/api/workbench-compat-routes.d.ts +1 -1
  42. package/benchmark/server-utils.d.ts +2 -2
  43. package/browser.d.ts +4 -4
  44. package/cli/plugins-cli.d.ts.map +1 -1
  45. package/cli/plugins-cli.js +5 -0
  46. package/cli/program/register.auth.d.ts +27 -1
  47. package/cli/program/register.auth.d.ts.map +1 -1
  48. package/cli/program/register.auth.js +187 -0
  49. package/cli/program/register.dashboard.d.ts.map +1 -1
  50. package/cli/program/register.dashboard.js +11 -1
  51. package/cli/program.d.ts +1 -1
  52. package/first-run/first-run-config.d.ts +1 -1
  53. package/index.d.ts +46 -47
  54. package/index.d.ts.map +1 -1
  55. package/index.js +3 -7
  56. package/package.json +42 -36
  57. package/packaging/PUBLISHING_GUIDE.md +20 -20
  58. package/platform/elizaos-agent-browser-stub.d.ts +0 -1
  59. package/platform/elizaos-agent-browser-stub.d.ts.map +1 -1
  60. package/platform/elizaos-agent-browser-stub.js +0 -1
  61. package/platform/ios-runtime-bridge.d.ts.map +1 -1
  62. package/platform/ios-runtime-bridge.js +0 -1
  63. package/platform/native-plugin-entrypoints.d.ts +2 -2
  64. package/platforms/android/app/build.gradle +30 -7
  65. package/platforms/android/app/src/main/elizavoice-jni/elizavoice-jni.cpp +179 -35
  66. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAgentService.java +60 -32
  67. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBionicInferenceServer.java +405 -65
  68. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceNative.java +35 -0
  69. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoicePlugin.java +13 -6
  70. package/platforms/android/app/src/main/res/drawable/splash.png +0 -0
  71. package/platforms/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  72. package/platforms/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  73. package/platforms/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  74. package/platforms/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  75. package/platforms/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  76. package/platforms/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  77. package/platforms/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  78. package/platforms/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  79. package/platforms/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  80. package/platforms/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  81. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  82. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  83. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
  84. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  85. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  86. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  87. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
  88. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  89. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  90. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  91. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
  92. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  93. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  94. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  95. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
  96. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  97. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  98. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  99. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
  100. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  101. package/platforms/android/app/src/main/res/values/colors.xml +5 -1
  102. package/platforms/android/app/src/main/res/values/ic_launcher_background.xml +1 -1
  103. package/platforms/android/app/src/main/res/values/styles.xml +4 -1
  104. package/platforms/electrobun/electrobun.config.ts +0 -1
  105. package/platforms/electrobun/native/macos/window-effects.mm +4 -0
  106. package/platforms/electrobun/package.json +11 -9
  107. package/platforms/electrobun/remotes/local-model/src/bun/eliza1-catalog.ts +1 -1
  108. package/platforms/electrobun/remotes/runtime/src/dev/phase2-smoke.ts +10 -5
  109. package/platforms/electrobun/remotes/runtime/src/dev/phase3-smoke.ts +11 -15
  110. package/platforms/electrobun/src/application-menu.ts +0 -7
  111. package/platforms/electrobun/src/desktop-http-request.test.ts +30 -1
  112. package/platforms/electrobun/src/desktop-http-request.ts +14 -2
  113. package/platforms/electrobun/src/desktop-test-bridge-server.ts +28 -0
  114. package/platforms/electrobun/src/fatal-shutdown.test.ts +3 -7
  115. package/platforms/electrobun/src/index.ts +64 -13
  116. package/platforms/electrobun/src/lifecycle/api-base-owner.test.ts +83 -0
  117. package/platforms/electrobun/src/lifecycle/api-base-owner.ts +37 -16
  118. package/platforms/electrobun/src/native/agent.ts +28 -17
  119. package/platforms/electrobun/src/native/credentials.ts +6 -1
  120. package/platforms/electrobun/src/native/desktop-window.test.ts +142 -26
  121. package/platforms/electrobun/src/native/desktop.ts +8 -4
  122. package/platforms/electrobun/src/native/index.ts +6 -0
  123. package/platforms/electrobun/src/native/remote-plugin-host.ts +18 -8
  124. package/platforms/electrobun/src/preload.js +1 -1
  125. package/platforms/electrobun/src/voice/voice-live-validation.test.ts +10 -5
  126. package/platforms/electrobun/src/voice/voice-runtime-adapter.ts +3 -2
  127. package/platforms/electrobun/src/voice/voice-service.test.ts +26 -0
  128. package/platforms/electrobun/src/voice/voice-service.ts +57 -17
  129. package/platforms/electrobun/tsconfig.json +17 -1
  130. package/platforms/ios/App/App/Base.lproj/LaunchScreen.storyboard +4 -6
  131. package/registry/index.d.ts +1 -6
  132. package/registry/index.d.ts.map +1 -1
  133. package/registry/index.js +9 -101
  134. package/runtime/android-avf-microdroid-bridge.d.ts +1 -1
  135. package/runtime/bundled-fused-lib.d.ts +36 -0
  136. package/runtime/bundled-fused-lib.d.ts.map +1 -0
  137. package/runtime/bundled-fused-lib.js +77 -0
  138. package/runtime/channel-plugin-map.d.ts +1 -5
  139. package/runtime/channel-plugin-map.d.ts.map +1 -1
  140. package/runtime/channel-plugin-map.js +11 -31
  141. package/runtime/desktop/index.d.ts +5 -5
  142. package/runtime/dev-server.js +11 -2
  143. package/runtime/eliza.d.ts.map +1 -1
  144. package/runtime/eliza.js +18 -8
  145. package/runtime/mode/route-mode-guard.d.ts +1 -1
  146. package/runtime/mode/route-mode-matrix.d.ts +1 -1
  147. package/runtime/voice-warmup.d.ts +4 -19
  148. package/runtime/voice-warmup.d.ts.map +1 -1
  149. package/runtime/voice-warmup.js +4 -14
  150. package/scripts/aosp/README.md +10 -1
  151. package/scripts/aosp/compile-libllama-paths.mjs +110 -0
  152. package/scripts/aosp/compile-libllama-zig-pin.test.mjs +147 -0
  153. package/scripts/aosp/compile-libllama.mjs +377 -81
  154. package/scripts/aosp/compile-libllama.test.mjs +133 -0
  155. package/scripts/aosp/compile-shim.mjs +21 -1
  156. package/scripts/aosp/deploy-pixel.mjs +1 -1
  157. package/scripts/aosp/stage-default-models.mjs +24 -31
  158. package/scripts/aosp/stage-default-models.test.mjs +23 -0
  159. package/scripts/assert-required-bundled-packages.test.ts +22 -1
  160. package/scripts/build-helpers/verify-fused-symbols.mjs +33 -0
  161. package/scripts/build-helpers/verify-fused-symbols.test.mjs +0 -0
  162. package/scripts/build-image.sh +1 -3
  163. package/scripts/build-llama-cpp-mtp.mjs +17 -16
  164. package/scripts/check-i18n.mjs +0 -1
  165. package/scripts/copy-runtime-node-modules.ts +147 -0
  166. package/scripts/desktop-build.mjs +317 -4
  167. package/scripts/docker-ci-smoke.sh +33 -8
  168. package/scripts/ensure-type-package-aliases.mjs +1 -1
  169. package/scripts/ios-xcframework/run-physical-device-smoke.mjs +1 -1
  170. package/scripts/lib/agent-source-watcher.mjs +0 -1
  171. package/scripts/lib/artifact-staleness.d.mts +33 -0
  172. package/scripts/lib/artifact-staleness.mjs +122 -0
  173. package/scripts/lib/artifact-staleness.test.mts +98 -0
  174. package/scripts/lib/duet-bridge.mjs +1 -1
  175. package/scripts/lib/ios-plist-url-scheme.mjs +80 -0
  176. package/scripts/lib/mobile-build-decisions.mjs +97 -0
  177. package/scripts/lib/renderer-build-manifest.d.mts +65 -0
  178. package/scripts/lib/renderer-build-manifest.mjs +257 -0
  179. package/scripts/lib/renderer-build-manifest.test.mts +277 -0
  180. package/scripts/lib/stage-android-agent.mjs +13 -4
  181. package/scripts/lib/verify-ondevice-artifact.d.mts +16 -0
  182. package/scripts/lib/verify-ondevice-artifact.mjs +61 -0
  183. package/scripts/lib/verify-ondevice-artifact.test.mts +98 -0
  184. package/scripts/lib/websocket-pending-queue.ts +92 -0
  185. package/scripts/link-docker-local-app-packages.mjs +6 -0
  186. package/scripts/playwright-ui-live-stack.ts +430 -144
  187. package/scripts/playwright-ui-smoke-api-stub.mjs +160 -168
  188. package/scripts/run-local-plugin-live-smoke.mjs +1 -6
  189. package/scripts/run-mobile-build-brand-separation.test.mts +33 -0
  190. package/scripts/run-mobile-build-ios-engine-gate.test.mjs +85 -0
  191. package/scripts/run-mobile-build-mtp-staleness.test.mts +198 -0
  192. package/scripts/run-mobile-build.mjs +408 -86
  193. package/scripts/serve-real-local-agent.ts +72 -0
  194. package/scripts/stage-desktop-fused-lib.mjs +43 -14
  195. package/scripts/verify-ondevice-artifact.mjs +111 -0
  196. package/scripts/voice-attribution-smoke.ts +144 -38
  197. package/scripts/voice-duet.mjs +6 -6
  198. package/scripts/voice-e2e-hardware.ts +22 -16
  199. package/scripts/voice-interactive.mjs +16 -45
  200. package/security/agent-vault-id.d.ts +1 -1
  201. package/security/platform-secure-store-node.d.ts +1 -1
  202. package/services/ambient-audio/consent.d.ts +1 -1
  203. package/services/ambient-audio/index.d.ts +6 -6
  204. package/services/ambient-audio/replay-buffer.d.ts +1 -1
  205. package/services/ambient-audio/response-gate.d.ts +1 -1
  206. package/services/ambient-audio/service.d.ts +3 -3
  207. package/services/connector-target-catalog.d.ts +1 -1
  208. package/services/sensitive-requests/index.d.ts +6 -6
  209. package/services/steward-sidecar/wallet-setup.d.ts +1 -1
  210. package/services/steward-sidecar.d.ts +3 -3
  211. package/services/tunnel-to-mobile/index.d.ts +1 -1
  212. package/services/vault-bootstrap.d.ts.map +1 -1
  213. package/services/vault-bootstrap.js +14 -10
  214. package/services/voice-profiles/diarization-pipeline.d.ts +1 -1
  215. package/services/voice-profiles/index.d.ts +11 -11
  216. package/services/voice-profiles/owner-confidence.d.ts +1 -1
  217. package/services/voice-profiles/private-challenge.d.ts +1 -1
  218. package/services/voice-profiles/store.d.ts +1 -1
  219. package/test/helpers/live-provider.test.ts +91 -1
  220. package/test/helpers/live-provider.ts +195 -51
  221. package/test/scripts/websocket-pending-queue.test.ts +119 -0
  222. package/test-support/test-helpers.d.ts +0 -33
  223. package/test-support/test-helpers.d.ts.map +1 -1
  224. package/test-support/test-helpers.js +81 -189
  225. package/ui-compat.d.ts +1 -0
  226. package/ui-compat.d.ts.map +1 -1
  227. package/ui-compat.js +4 -0
  228. package/platforms/electrobun/docs/convergence-audit.json +0 -3505
  229. package/platforms/electrobun/docs/convergence-audit.md +0 -694
  230. package/platforms/electrobun/scripts/ensure-whisper-gguf.sh +0 -55
  231. package/platforms/electrobun/scripts/ensure-whisper-model.sh +0 -35
  232. package/platforms/electrobun/scripts/generate-convergence-audit.ts +0 -1203
  233. package/platforms/electrobun/src/native/whisper-env.test.ts +0 -71
  234. package/platforms/electrobun/src/native/whisper-env.ts +0 -68
  235. package/registry/app-registry.d.ts +0 -14
  236. package/registry/app-registry.d.ts.map +0 -1
  237. package/registry/app-registry.js +0 -29
  238. package/registry/entries/apps/app-polymarket.json +0 -31
  239. package/registry/entries/apps/clawville.json +0 -27
  240. package/registry/entries/apps/companion.json +0 -28
  241. package/registry/entries/apps/database-viewer.json +0 -27
  242. package/registry/entries/apps/defense-of-the-agents.json +0 -27
  243. package/registry/entries/apps/documents.json +0 -30
  244. package/registry/entries/apps/feed.json +0 -27
  245. package/registry/entries/apps/hyperliquid.json +0 -31
  246. package/registry/entries/apps/log-viewer.json +0 -27
  247. package/registry/entries/apps/memory-viewer.json +0 -27
  248. package/registry/entries/apps/model-tester.json +0 -31
  249. package/registry/entries/apps/plugin-viewer.json +0 -27
  250. package/registry/entries/apps/relationship-viewer.json +0 -27
  251. package/registry/entries/apps/runtime-debugger.json +0 -27
  252. package/registry/entries/apps/shopify.json +0 -31
  253. package/registry/entries/apps/skills-viewer.json +0 -27
  254. package/registry/entries/apps/steward.json +0 -31
  255. package/registry/entries/apps/training.json +0 -54
  256. package/registry/entries/apps/trajectory-viewer.json +0 -27
  257. package/registry/entries/apps/vincent.json +0 -31
  258. package/registry/entries/connectors/bluebubbles.json +0 -99
  259. package/registry/entries/connectors/bluesky.json +0 -173
  260. package/registry/entries/connectors/discord.json +0 -119
  261. package/registry/entries/connectors/farcaster.json +0 -174
  262. package/registry/entries/connectors/feishu.json +0 -79
  263. package/registry/entries/connectors/google-chat.json +0 -120
  264. package/registry/entries/connectors/google.json +0 -82
  265. package/registry/entries/connectors/imessage.json +0 -96
  266. package/registry/entries/connectors/instagram.json +0 -64
  267. package/registry/entries/connectors/line.json +0 -86
  268. package/registry/entries/connectors/matrix.json +0 -94
  269. package/registry/entries/connectors/mattermost.json +0 -110
  270. package/registry/entries/connectors/msteams.json +0 -104
  271. package/registry/entries/connectors/nextcloud-talk.json +0 -104
  272. package/registry/entries/connectors/nostr.json +0 -70
  273. package/registry/entries/connectors/signal.json +0 -81
  274. package/registry/entries/connectors/slack.json +0 -102
  275. package/registry/entries/connectors/telegram.json +0 -71
  276. package/registry/entries/connectors/tlon.json +0 -94
  277. package/registry/entries/connectors/twitch.json +0 -110
  278. package/registry/entries/connectors/whatsapp.json +0 -113
  279. package/registry/entries/connectors/x.json +0 -231
  280. package/registry/entries/connectors/zalo.json +0 -112
  281. package/registry/entries/connectors/zalouser.json +0 -122
  282. package/registry/entries/plugins/agent-orchestrator.json +0 -33
  283. package/registry/entries/plugins/agent-skills.json +0 -72
  284. package/registry/entries/plugins/anthropic.json +0 -73
  285. package/registry/entries/plugins/app-control.json +0 -23
  286. package/registry/entries/plugins/auto-trader.json +0 -203
  287. package/registry/entries/plugins/background-runner.json +0 -26
  288. package/registry/entries/plugins/blooio.json +0 -102
  289. package/registry/entries/plugins/browser.json +0 -75
  290. package/registry/entries/plugins/cli.json +0 -40
  291. package/registry/entries/plugins/clipboard.json +0 -44
  292. package/registry/entries/plugins/coding-tools.json +0 -71
  293. package/registry/entries/plugins/commands.json +0 -63
  294. package/registry/entries/plugins/computeruse.json +0 -74
  295. package/registry/entries/plugins/copilot-proxy.json +0 -93
  296. package/registry/entries/plugins/directives.json +0 -63
  297. package/registry/entries/plugins/edge-tts.json +0 -97
  298. package/registry/entries/plugins/elevenlabs.json +0 -169
  299. package/registry/entries/plugins/elizacloud.json +0 -208
  300. package/registry/entries/plugins/evm.json +0 -134
  301. package/registry/entries/plugins/experience.json +0 -34
  302. package/registry/entries/plugins/facewear.json +0 -131
  303. package/registry/entries/plugins/form.json +0 -26
  304. package/registry/entries/plugins/github.json +0 -93
  305. package/registry/entries/plugins/gmail-watch.json +0 -25
  306. package/registry/entries/plugins/goals.json +0 -77
  307. package/registry/entries/plugins/google-genai.json +0 -106
  308. package/registry/entries/plugins/groq.json +0 -93
  309. package/registry/entries/plugins/hedera.json +0 -48
  310. package/registry/entries/plugins/inmemorydb.json +0 -25
  311. package/registry/entries/plugins/linear.json +0 -51
  312. package/registry/entries/plugins/local-inference.json +0 -137
  313. package/registry/entries/plugins/local-storage.json +0 -36
  314. package/registry/entries/plugins/localdb.json +0 -25
  315. package/registry/entries/plugins/mcp.json +0 -44
  316. package/registry/entries/plugins/memory.json +0 -124
  317. package/registry/entries/plugins/minecraft.json +0 -79
  318. package/registry/entries/plugins/moltbook.json +0 -83
  319. package/registry/entries/plugins/music.json +0 -155
  320. package/registry/entries/plugins/mysticism.json +0 -48
  321. package/registry/entries/plugins/nearai.json +0 -82
  322. package/registry/entries/plugins/ngrok.json +0 -69
  323. package/registry/entries/plugins/ollama.json +0 -96
  324. package/registry/entries/plugins/openai.json +0 -189
  325. package/registry/entries/plugins/openrouter.json +0 -188
  326. package/registry/entries/plugins/pdf.json +0 -26
  327. package/registry/entries/plugins/plugin-manager.json +0 -23
  328. package/registry/entries/plugins/prose.json +0 -48
  329. package/registry/entries/plugins/rlm.json +0 -26
  330. package/registry/entries/plugins/roblox.json +0 -88
  331. package/registry/entries/plugins/rss.json +0 -64
  332. package/registry/entries/plugins/s3-storage.json +0 -91
  333. package/registry/entries/plugins/scheduling.json +0 -35
  334. package/registry/entries/plugins/shell.json +0 -94
  335. package/registry/entries/plugins/social-alpha.json +0 -72
  336. package/registry/entries/plugins/tailscale.json +0 -81
  337. package/registry/entries/plugins/tee.json +0 -53
  338. package/registry/entries/plugins/todos.json +0 -26
  339. package/registry/entries/plugins/trajectory-logger.json +0 -33
  340. package/registry/entries/plugins/trust.json +0 -39
  341. package/registry/entries/plugins/tts.json +0 -71
  342. package/registry/entries/plugins/tunnel.json +0 -45
  343. package/registry/entries/plugins/twilio.json +0 -168
  344. package/registry/entries/plugins/vercel-ai-gateway.json +0 -128
  345. package/registry/entries/plugins/video.json +0 -23
  346. package/registry/entries/plugins/vision.json +0 -43
  347. package/registry/entries/plugins/webhooks.json +0 -23
  348. package/registry/entries/plugins/workflow.json +0 -25
  349. package/registry/entries/plugins/xai.json +0 -75
  350. package/registry/loader.d.ts +0 -28
  351. package/registry/loader.d.ts.map +0 -1
  352. package/registry/loader.js +0 -158
  353. package/registry/schema.d.ts +0 -1199
  354. package/registry/schema.d.ts.map +0 -1
  355. package/registry/schema.js +0 -300
  356. package/scripts/patches/llama-mobile-kokoro-tts.patch +0 -480
  357. package/scripts/release-workflow-drift.test.ts +0 -57
  358. package/test/helpers/i18n.ts +0 -5
  359. package/test/helpers/loopback.ts +0 -33
  360. package/test/helpers/react-test.ts +0 -40
  361. 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 { resolveApiToken, resolveDesktopApiPort } from "@elizaos/shared";
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
- Utils.quit();
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
- rendererUrl = `file://${path.join(resolveRendererAssetDir(import.meta.dir), "index.html")}`;
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
- logger.info(`[Main] App quitting (${reason}), disposing native modules...`);
2176
- isQuitting = true;
2177
- sendToActiveRenderer("desktopShutdownStarted", { reason });
2178
- for (const cleanupFn of cleanupFns) {
2179
- await Promise.resolve(cleanupFn());
2198
+ if (shutdownCleanupPromise) {
2199
+ return shutdownCleanupPromise;
2180
2200
  }
2181
- await disposeNativeModules();
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
- if (!current.base) return html;
82
- const baseLiteral = safeJsonForHtml(current.base);
83
- const tokenLiteral = current.token ? safeJsonForHtml(current.token) : "";
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
- const script = `<script>window.__ELIZA_API_BASE__=${baseLiteral};${runtimeModeInject}${tokenInject}${bootConfigInject}</script>`;
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 lsofResult = Bun.spawnSync(["lsof", "-ti", `tcp:${port}`]);
1281
- const pids = new TextDecoder()
1282
- .decode(lsofResult.stdout)
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
- // lsof missing — ignore
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
- const proc = Bun.spawn(["which", name], {
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: vi.fn(),
116
+ GlobalShortcut: electrobunMock.GlobalShortcut,
16
117
  Screen: {
17
118
  getAllDisplays: vi.fn(() => []),
18
119
  },
19
120
  Session: {
20
121
  defaultSession: {},
21
122
  },
22
- Tray: vi.fn(),
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
- Utils.quit();
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
- void this.destroyTray();
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()],