@elizaos/app-core 2.0.0-beta.3 → 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.
Files changed (1466) hide show
  1. package/agent-bridge.d.ts +27 -0
  2. package/agent-bridge.d.ts.map +1 -0
  3. package/agent-bridge.js +26 -0
  4. package/api/__tests__/sandbox-test-helpers.js +1 -1
  5. package/api/auth/audit.d.ts +1 -1
  6. package/api/auth/audit.js +1 -1
  7. package/api/auth/auth-context.d.ts +1 -1
  8. package/api/auth/auth-context.js +1 -1
  9. package/api/auth/bootstrap-token.d.ts +1 -1
  10. package/api/auth/bootstrap-token.js +2 -2
  11. package/api/auth/index.d.ts +9 -10
  12. package/api/auth/index.d.ts.map +1 -1
  13. package/api/auth/index.js +9 -10
  14. package/api/auth/passwords.js +2 -2
  15. package/api/auth/sensitive-rate-limit.d.ts +1 -4
  16. package/api/auth/sensitive-rate-limit.d.ts.map +1 -1
  17. package/api/auth/sensitive-rate-limit.js +6 -6
  18. package/api/auth/sessions.d.ts +1 -1
  19. package/api/auth/sessions.js +2 -2
  20. package/api/auth-bootstrap-routes.d.ts +7 -14
  21. package/api/auth-bootstrap-routes.d.ts.map +1 -1
  22. package/api/auth-bootstrap-routes.js +14 -27
  23. package/api/auth-pairing-routes.d.ts +17 -0
  24. package/api/auth-pairing-routes.d.ts.map +1 -0
  25. package/api/auth-pairing-routes.js +299 -0
  26. package/api/auth-session-routes.d.ts +2 -2
  27. package/api/auth-session-routes.d.ts.map +1 -1
  28. package/api/auth-session-routes.js +36 -15
  29. package/api/auth.d.ts +13 -20
  30. package/api/auth.d.ts.map +1 -1
  31. package/api/auth.js +32 -27
  32. package/api/automations-compat-routes.d.ts +1 -1
  33. package/api/automations-compat-routes.d.ts.map +1 -1
  34. package/api/automations-compat-routes.js +5 -5
  35. package/api/background-tasks-routes.d.ts +4 -0
  36. package/api/background-tasks-routes.d.ts.map +1 -0
  37. package/api/background-tasks-routes.js +63 -0
  38. package/api/catalog-routes.d.ts +1 -1
  39. package/api/catalog-routes.js +3 -3
  40. package/api/cloud-pair-route.d.ts +26 -0
  41. package/api/cloud-pair-route.d.ts.map +1 -0
  42. package/api/cloud-pair-route.js +222 -0
  43. package/api/cloud-voice-routes.d.ts +52 -0
  44. package/api/cloud-voice-routes.d.ts.map +1 -0
  45. package/api/cloud-voice-routes.js +50 -0
  46. package/api/compat-route-shared.d.ts +14 -7
  47. package/api/compat-route-shared.d.ts.map +1 -1
  48. package/api/compat-route-shared.js +27 -188
  49. package/api/credential-resolver.d.ts +2 -2
  50. package/api/credential-resolver.d.ts.map +1 -1
  51. package/api/credential-resolver.js +8 -2
  52. package/api/database-rows-compat-routes.d.ts +1 -1
  53. package/api/database-rows-compat-routes.d.ts.map +1 -1
  54. package/api/database-rows-compat-routes.js +69 -31
  55. package/api/dev-boot-history.d.ts +26 -0
  56. package/api/dev-boot-history.d.ts.map +1 -0
  57. package/api/dev-boot-history.js +69 -0
  58. package/api/dev-compat-routes.d.ts +6 -1
  59. package/api/dev-compat-routes.d.ts.map +1 -1
  60. package/api/dev-compat-routes.js +127 -4
  61. package/api/dev-console-log.d.ts +2 -2
  62. package/api/dev-console-log.d.ts.map +1 -1
  63. package/api/dev-console-log.js +8 -5
  64. package/api/dev-route-catalog.d.ts +58 -0
  65. package/api/dev-route-catalog.d.ts.map +1 -0
  66. package/api/dev-route-catalog.js +477 -0
  67. package/api/dev-stack.d.ts.map +1 -1
  68. package/api/dev-stack.js +6 -9
  69. package/api/drop-status-compat-route.d.ts +3 -0
  70. package/api/drop-status-compat-route.d.ts.map +1 -0
  71. package/api/drop-status-compat-route.js +12 -0
  72. package/api/first-run-routes.d.ts +4 -0
  73. package/api/first-run-routes.d.ts.map +1 -0
  74. package/api/first-run-routes.js +208 -0
  75. package/api/first-run-tts-route.d.ts +19 -0
  76. package/api/first-run-tts-route.d.ts.map +1 -0
  77. package/api/first-run-tts-route.js +59 -0
  78. package/api/internal-routes.d.ts +23 -0
  79. package/api/internal-routes.d.ts.map +1 -0
  80. package/api/internal-routes.js +203 -0
  81. package/api/ios-local-agent-transport.d.ts +36 -0
  82. package/api/ios-local-agent-transport.d.ts.map +1 -0
  83. package/api/ios-local-agent-transport.js +566 -0
  84. package/api/onboarding-voice-lines.d.ts +23 -0
  85. package/api/onboarding-voice-lines.d.ts.map +1 -0
  86. package/api/onboarding-voice-lines.js +8 -0
  87. package/api/perf-instrument.d.ts +43 -0
  88. package/api/perf-instrument.d.ts.map +1 -0
  89. package/api/perf-instrument.js +113 -0
  90. package/api/response.d.ts.map +1 -1
  91. package/api/response.js +14 -14
  92. package/api/runtime-mode-routes.d.ts +1 -1
  93. package/api/runtime-mode-routes.d.ts.map +1 -1
  94. package/api/runtime-mode-routes.js +2 -2
  95. package/api/secrets-inventory-routes.js +2 -2
  96. package/api/secrets-manager-routes.d.ts +1 -1
  97. package/api/secrets-manager-routes.d.ts.map +1 -1
  98. package/api/secrets-manager-routes.js +9 -10
  99. package/api/sensitive-request-routes.d.ts +2 -2
  100. package/api/sensitive-request-routes.js +5 -5
  101. package/api/server-config-filter.d.ts.map +1 -1
  102. package/api/server-config-filter.js +0 -1
  103. package/api/server-cors.d.ts.map +1 -1
  104. package/api/server-cors.js +13 -2
  105. package/api/server-first-run-helpers.d.ts +26 -0
  106. package/api/server-first-run-helpers.d.ts.map +1 -0
  107. package/api/server-first-run-helpers.js +268 -0
  108. package/api/server-security.js +1 -1
  109. package/api/server-startup.d.ts.map +1 -1
  110. package/api/server-startup.js +3 -4
  111. package/api/server-wallet-trade.js +1 -1
  112. package/api/server.d.ts +11 -11
  113. package/api/server.d.ts.map +1 -1
  114. package/api/server.js +227 -139
  115. package/api/setup-contract.d.ts +63 -0
  116. package/api/setup-contract.d.ts.map +1 -0
  117. package/api/setup-contract.js +39 -0
  118. package/api/training-benchmarks.d.ts +97 -0
  119. package/api/training-benchmarks.d.ts.map +1 -0
  120. package/api/training-benchmarks.js +329 -0
  121. package/api/workbench-compat-routes.d.ts +1 -1
  122. package/api/workbench-compat-routes.js +2 -2
  123. package/benchmark/cerebras-autowire.d.ts +28 -0
  124. package/benchmark/cerebras-autowire.d.ts.map +1 -0
  125. package/benchmark/cerebras-autowire.js +62 -0
  126. package/benchmark/lifeops-bench-handler.d.ts +36 -0
  127. package/benchmark/lifeops-bench-handler.d.ts.map +1 -1
  128. package/benchmark/lifeops-bench-handler.js +63 -1
  129. package/benchmark/lifeops-fake-backend.d.ts +39 -0
  130. package/benchmark/lifeops-fake-backend.d.ts.map +1 -1
  131. package/benchmark/lifeops-fake-backend.js +993 -21
  132. package/benchmark/mock-plugin.d.ts.map +1 -1
  133. package/benchmark/mock-plugin.js +0 -24
  134. package/benchmark/plugin.d.ts +2 -1
  135. package/benchmark/plugin.d.ts.map +1 -1
  136. package/benchmark/plugin.js +989 -68
  137. package/benchmark/replay-capture.d.ts +2 -2
  138. package/benchmark/replay-capture.d.ts.map +1 -1
  139. package/benchmark/replay-capture.js +3 -3
  140. package/benchmark/server-utils.d.ts +164 -11
  141. package/benchmark/server-utils.d.ts.map +1 -1
  142. package/benchmark/server-utils.js +625 -62
  143. package/benchmark/server.d.ts.map +1 -1
  144. package/benchmark/server.js +1962 -118
  145. package/boot-profile.d.ts +3 -0
  146. package/boot-profile.d.ts.map +1 -0
  147. package/boot-profile.js +30 -0
  148. package/browser.d.ts +23 -1
  149. package/browser.d.ts.map +1 -1
  150. package/browser.js +20 -1
  151. package/cli/argv.js +1 -1
  152. package/cli/banner.js +1 -1
  153. package/cli/command-format.js +2 -2
  154. package/cli/doctor/checks.d.ts.map +1 -1
  155. package/cli/doctor/checks.js +6 -6
  156. package/cli/plugins-cli.d.ts.map +1 -1
  157. package/cli/plugins-cli.js +82 -32
  158. package/cli/profile.d.ts.map +1 -1
  159. package/cli/profile.js +5 -4
  160. package/cli/program/build-program.js +4 -4
  161. package/cli/program/command-registry.d.ts.map +1 -1
  162. package/cli/program/command-registry.js +13 -11
  163. package/cli/program/help.js +5 -5
  164. package/cli/program/preaction.js +5 -5
  165. package/cli/program/register.auth.d.ts +27 -1
  166. package/cli/program/register.auth.d.ts.map +1 -1
  167. package/cli/program/register.auth.js +193 -12
  168. package/cli/program/register.capability-router.d.ts +29 -0
  169. package/cli/program/register.capability-router.d.ts.map +1 -0
  170. package/cli/program/register.capability-router.js +568 -0
  171. package/cli/program/register.config.js +1 -1
  172. package/cli/program/register.configure.d.ts.map +1 -1
  173. package/cli/program/register.configure.js +1 -1
  174. package/cli/program/register.dashboard.d.ts.map +1 -1
  175. package/cli/program/register.dashboard.js +17 -8
  176. package/cli/program/register.db.d.ts.map +1 -1
  177. package/cli/program/register.db.js +3 -4
  178. package/cli/program/register.doctor.js +7 -7
  179. package/cli/program/register.setup.d.ts.map +1 -1
  180. package/cli/program/register.setup.js +14 -10
  181. package/cli/program/register.start.d.ts.map +1 -1
  182. package/cli/program/register.start.js +5 -3
  183. package/cli/program/register.subclis.js +3 -3
  184. package/cli/program/register.update.d.ts +6 -0
  185. package/cli/program/register.update.d.ts.map +1 -1
  186. package/cli/program/register.update.js +58 -6
  187. package/cli/program.d.ts +1 -1
  188. package/cli/program.js +1 -1
  189. package/cli/run-main.js +4 -4
  190. package/config/app-config.d.ts +2 -0
  191. package/config/app-config.d.ts.map +1 -0
  192. package/config/app-config.js +1 -0
  193. package/connectors/capacitor-jsc.d.ts.map +1 -1
  194. package/connectors/capacitor-jsc.js +16 -10
  195. package/connectors/capacitor-quickjs.d.ts.map +1 -1
  196. package/connectors/capacitor-quickjs.js +18 -13
  197. package/connectors/capacitor-sqlite.d.ts.map +1 -1
  198. package/connectors/capacitor-sqlite.js +27 -12
  199. package/dispatch/approval-queue.d.ts +37 -0
  200. package/dispatch/approval-queue.d.ts.map +1 -0
  201. package/dispatch/approval-queue.js +25 -0
  202. package/dispatch/channel-registry.d.ts +30 -0
  203. package/dispatch/channel-registry.d.ts.map +1 -0
  204. package/dispatch/channel-registry.js +22 -0
  205. package/dispatch/connector-registry.d.ts +39 -0
  206. package/dispatch/connector-registry.d.ts.map +1 -0
  207. package/dispatch/connector-registry.js +24 -0
  208. package/dispatch/index.d.ts +14 -0
  209. package/dispatch/index.d.ts.map +1 -0
  210. package/dispatch/index.js +13 -0
  211. package/dispatch/send-policy.d.ts +36 -0
  212. package/dispatch/send-policy.d.ts.map +1 -0
  213. package/dispatch/send-policy.js +16 -0
  214. package/entry.js +28 -11
  215. package/first-run/first-run-config.d.ts +55 -0
  216. package/first-run/first-run-config.d.ts.map +1 -0
  217. package/first-run/first-run-config.js +178 -0
  218. package/first-run/runtime-target.d.ts +4 -0
  219. package/first-run/runtime-target.d.ts.map +1 -0
  220. package/first-run/runtime-target.js +13 -0
  221. package/index.d.ts +46 -34
  222. package/index.d.ts.map +1 -1
  223. package/index.js +53 -33
  224. package/package.json +166 -51
  225. package/packaging/PUBLISHING_GUIDE.md +20 -20
  226. package/packaging/debian/apt-repo-config/README.md +18 -0
  227. package/packaging/debian/apt-repo-config/conf/distributions +11 -0
  228. package/packaging/flatpak/README.md +26 -16
  229. package/packaging/flatpak/ai.elizaos.App.metainfo.xml +17 -12
  230. package/packaging/flatpak/ai.elizaos.App.store.yml +5 -5
  231. package/packaging/flatpak/ai.elizaos.App.yml +10 -24
  232. package/packaging/flatpak/elizaos-app-wrapper.store.sh +2 -2
  233. package/packaging/flatpak/generate-sources.sh +74 -0
  234. package/packaging/flatpak/node-sources.json +7930 -0
  235. package/packaging/inno/build-inno.ps1 +34 -9
  236. package/packaging/msix/AppxManifest.store.xml +1 -1
  237. package/packaging/msix/README.md +39 -19
  238. package/packaging/msix/build-msix.ps1 +44 -14
  239. package/packaging/snap/snapcraft.yaml +22 -21
  240. package/packaging/test-packaging.sh +2 -2
  241. package/permissions/types.d.ts +1 -1
  242. package/permissions/types.js +1 -1
  243. package/platform/elizaos-agent-browser-stub.d.ts +143 -0
  244. package/platform/elizaos-agent-browser-stub.d.ts.map +1 -0
  245. package/platform/elizaos-agent-browser-stub.js +157 -0
  246. package/platform/elizaos-plugin-elizacloud-browser-stub.d.ts +34 -0
  247. package/platform/elizaos-plugin-elizacloud-browser-stub.d.ts.map +1 -0
  248. package/platform/elizaos-plugin-elizacloud-browser-stub.js +51 -0
  249. package/platform/empty-node-module.d.ts +148 -0
  250. package/platform/empty-node-module.d.ts.map +1 -1
  251. package/platform/empty-node-module.js +140 -3
  252. package/platform/ios-runtime-backends.d.ts +83 -0
  253. package/platform/ios-runtime-backends.d.ts.map +1 -0
  254. package/platform/ios-runtime-backends.js +133 -0
  255. package/platform/ios-runtime-bridge.d.ts +15 -0
  256. package/platform/ios-runtime-bridge.d.ts.map +1 -0
  257. package/platform/ios-runtime-bridge.js +526 -0
  258. package/platform/native-library-policy.d.ts +23 -0
  259. package/platform/native-library-policy.d.ts.map +1 -0
  260. package/platform/native-library-policy.js +112 -0
  261. package/platform/native-plugin-entrypoints.d.ts +19 -0
  262. package/platform/native-plugin-entrypoints.d.ts.map +1 -0
  263. package/platform/native-plugin-entrypoints.js +29 -0
  264. package/platforms/android/README.md +68 -10
  265. package/platforms/android/app/build.gradle +291 -3
  266. package/platforms/android/app/capacitor.build.gradle +18 -1
  267. package/platforms/android/app/proguard-rules.pro +17 -2
  268. package/platforms/android/app/src/androidTest/java/ai/elizaos/app/ElizaOsInstrumentedTest.java +1 -1
  269. package/platforms/android/app/src/main/AndroidManifest.xml +335 -18
  270. package/platforms/android/app/src/main/assets/runners/eliza-tasks.js +177 -0
  271. package/platforms/android/app/src/main/elizavoice-jni/CMakeLists.txt +100 -0
  272. package/platforms/android/app/src/main/elizavoice-jni/elizavoice-jni.cpp +1629 -0
  273. package/platforms/android/app/src/main/java/ai/elizaos/app/AgentPlugin.java +111 -171
  274. package/platforms/android/app/src/main/java/ai/elizaos/app/AndroidVirtualizationBridge.java +284 -0
  275. package/platforms/android/app/src/main/java/ai/elizaos/app/BatteryOptimizationPlugin.java +95 -0
  276. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAccessibilityService.java +55 -0
  277. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAgentService.java +1283 -141
  278. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAndroidSystemBridge.java +83 -0
  279. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAssistActivity.java +50 -1
  280. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBionicInferenceServer.java +740 -0
  281. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBootReceiver.java +90 -8
  282. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBrowserActivity.java +2 -2
  283. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaCalendarActivity.java +1 -1
  284. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaCameraActivity.java +1 -1
  285. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaClockActivity.java +2 -2
  286. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaContactsActivity.java +1 -1
  287. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaDialActivity.java +1 -1
  288. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaInCallService.java +1 -1
  289. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaMmsReceiver.java +1 -1
  290. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaNativeBridge.java +22 -0
  291. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaNotificationListenerService.java +45 -0
  292. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaQuickActionsWidgetProvider.java +68 -0
  293. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaShareActivity.java +132 -0
  294. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsComposeActivity.java +1 -1
  295. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsGatewayService.java +268 -0
  296. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsReceiver.java +12 -1
  297. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaTasksWorker.java +194 -0
  298. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceCaptureService.java +198 -0
  299. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceNative.java +255 -0
  300. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoicePlugin.java +505 -0
  301. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceTileService.java +39 -0
  302. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaWorkScheduler.java +60 -0
  303. package/platforms/android/app/src/main/java/ai/elizaos/app/GatewayConnectionService.java +53 -19
  304. package/platforms/android/app/src/main/java/ai/elizaos/app/MainActivity.java +160 -33
  305. package/platforms/android/app/src/main/java/ai/elizaos/app/ResourceProbePlugin.java +169 -0
  306. package/platforms/android/app/src/main/java/ai/elizaos/app/VoiceCapturePlugin.java +119 -0
  307. package/platforms/android/app/src/main/res/drawable/eliza_widget_background.xml +10 -0
  308. package/platforms/android/app/src/main/res/drawable/eliza_widget_button_background.xml +13 -0
  309. package/platforms/android/app/src/main/res/drawable/splash.png +0 -0
  310. package/platforms/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  311. package/platforms/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  312. package/platforms/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  313. package/platforms/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  314. package/platforms/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  315. package/platforms/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  316. package/platforms/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  317. package/platforms/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  318. package/platforms/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  319. package/platforms/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  320. package/platforms/android/app/src/main/res/layout/eliza_quick_actions_widget.xml +86 -0
  321. package/platforms/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +2 -1
  322. package/platforms/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +2 -1
  323. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  324. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  325. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
  326. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  327. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  328. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  329. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
  330. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  331. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  332. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  333. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
  334. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  335. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  336. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  337. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
  338. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  339. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  340. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  341. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
  342. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  343. package/platforms/android/app/src/main/res/values/android_app_actions.xml +48 -0
  344. package/platforms/android/app/src/main/res/values/colors.xml +12 -0
  345. package/platforms/android/app/src/main/res/values/ic_launcher_background.xml +2 -2
  346. package/platforms/android/app/src/main/res/values/strings.xml +2 -2
  347. package/platforms/android/app/src/main/res/values/styles.xml +28 -1
  348. package/platforms/android/app/src/main/res/xml/eliza_accessibility_service.xml +9 -0
  349. package/platforms/android/app/src/main/res/xml/eliza_quick_actions_widget.xml +13 -0
  350. package/platforms/android/app/src/main/res/xml/shortcuts.xml +121 -0
  351. package/platforms/android/build.gradle +2 -2
  352. package/platforms/android/capacitor-cordova-android-plugins/build.gradle +9 -3
  353. package/platforms/android/capacitor-cordova-android-plugins/cordova.variables.gradle +6 -2
  354. package/platforms/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml +7 -2
  355. package/platforms/android/capacitor-cordova-android-plugins/src/main/java/.gitkeep +0 -1
  356. package/platforms/android/capacitor.settings.gradle +66 -16
  357. package/platforms/android/gradle.properties +1 -0
  358. package/platforms/android/update-manifest/generate-manifest.mjs +97 -0
  359. package/platforms/android/update-manifest/schema.json +26 -0
  360. package/platforms/apple-store-entitlements.reviewed.json +155 -0
  361. package/platforms/electrobun/.generated/brand-config.json +3 -2
  362. package/platforms/electrobun/LICENSE +21 -0
  363. package/platforms/electrobun/README.md +15 -1
  364. package/platforms/electrobun/assets/appIcon.icns +0 -0
  365. package/platforms/electrobun/assets/appIcon.ico +0 -0
  366. package/platforms/electrobun/assets/appIcon.iconset/icon_128x128.png +0 -0
  367. package/platforms/electrobun/assets/appIcon.iconset/icon_128x128@2x.png +0 -0
  368. package/platforms/electrobun/assets/appIcon.iconset/icon_16x16.png +0 -0
  369. package/platforms/electrobun/assets/appIcon.iconset/icon_16x16@2x.png +0 -0
  370. package/platforms/electrobun/assets/appIcon.iconset/icon_256x256.png +0 -0
  371. package/platforms/electrobun/assets/appIcon.iconset/icon_256x256@2x.png +0 -0
  372. package/platforms/electrobun/assets/appIcon.iconset/icon_32x32.png +0 -0
  373. package/platforms/electrobun/assets/appIcon.iconset/icon_32x32@2x.png +0 -0
  374. package/platforms/electrobun/assets/appIcon.iconset/icon_512x512.png +0 -0
  375. package/platforms/electrobun/assets/brand-config.json +6 -6
  376. package/platforms/electrobun/biome.json +9 -9
  377. package/platforms/electrobun/docs/capability-collapse-matrix.json +318 -0
  378. package/platforms/electrobun/docs/capability-collapse-matrix.md +129 -0
  379. package/platforms/electrobun/docs/capability-routing.md +86 -0
  380. package/platforms/electrobun/docs/database-boot-policy.md +90 -0
  381. package/platforms/electrobun/docs/riscv64-port.md +175 -0
  382. package/platforms/electrobun/docs/startup-first-run-cleanup.md +18 -0
  383. package/platforms/electrobun/docs/trace-first-annotations.md +52 -0
  384. package/platforms/electrobun/docs/ui-boundary-audit.json +580 -0
  385. package/platforms/electrobun/docs/ui-boundary-audit.md +257 -0
  386. package/platforms/electrobun/electrobun.config.ts +591 -364
  387. package/platforms/electrobun/entitlements/JUSTIFICATIONS.md +141 -0
  388. package/platforms/electrobun/entitlements/README.md +34 -6
  389. package/platforms/electrobun/entitlements/mas-bun.entitlements +15 -0
  390. package/platforms/electrobun/entitlements/mas.entitlements +6 -4
  391. package/platforms/electrobun/native/macos/window-effects.mm +1526 -0
  392. package/platforms/electrobun/package.json +21 -13
  393. package/platforms/electrobun/remotes/fs/README.md +70 -0
  394. package/platforms/electrobun/remotes/fs/electrobun.config.ts +38 -0
  395. package/platforms/electrobun/remotes/fs/package.json +12 -0
  396. package/platforms/electrobun/remotes/fs/plugin.json +25 -0
  397. package/platforms/electrobun/remotes/fs/src/bun/errors.ts +57 -0
  398. package/platforms/electrobun/remotes/fs/src/bun/file-limits.ts +50 -0
  399. package/platforms/electrobun/remotes/fs/src/bun/fs-service.ts +389 -0
  400. package/platforms/electrobun/remotes/fs/src/bun/path-guard.ts +270 -0
  401. package/platforms/electrobun/remotes/fs/src/bun/protocol.ts +149 -0
  402. package/platforms/electrobun/remotes/fs/src/bun/worker.ts +174 -0
  403. package/platforms/electrobun/remotes/fs/src/dev/phase5-smoke.ts +171 -0
  404. package/platforms/electrobun/remotes/fs/src/web/index.html +8 -0
  405. package/platforms/electrobun/remotes/git/README.md +75 -0
  406. package/platforms/electrobun/remotes/git/electrobun.config.ts +44 -0
  407. package/platforms/electrobun/remotes/git/package.json +12 -0
  408. package/platforms/electrobun/remotes/git/plugin.json +31 -0
  409. package/platforms/electrobun/remotes/git/src/bun/errors.ts +69 -0
  410. package/platforms/electrobun/remotes/git/src/bun/git-command.ts +156 -0
  411. package/platforms/electrobun/remotes/git/src/bun/git-service.ts +446 -0
  412. package/platforms/electrobun/remotes/git/src/bun/operation-history.ts +124 -0
  413. package/platforms/electrobun/remotes/git/src/bun/protocol.ts +252 -0
  414. package/platforms/electrobun/remotes/git/src/bun/worker.ts +316 -0
  415. package/platforms/electrobun/remotes/git/src/dev/phase7-smoke.ts +141 -0
  416. package/platforms/electrobun/remotes/git/src/web/index.html +8 -0
  417. package/platforms/electrobun/remotes/local-model/README.md +138 -0
  418. package/platforms/electrobun/remotes/local-model/electrobun.config.ts +46 -0
  419. package/platforms/electrobun/remotes/local-model/package.json +12 -0
  420. package/platforms/electrobun/remotes/local-model/plugin.json +33 -0
  421. package/platforms/electrobun/remotes/local-model/src/bun/download-state.ts +115 -0
  422. package/platforms/electrobun/remotes/local-model/src/bun/eliza1-catalog.ts +405 -0
  423. package/platforms/electrobun/remotes/local-model/src/bun/errors.ts +74 -0
  424. package/platforms/electrobun/remotes/local-model/src/bun/hf-eliza1-client.ts +169 -0
  425. package/platforms/electrobun/remotes/local-model/src/bun/local-inference-api-client.ts +245 -0
  426. package/platforms/electrobun/remotes/local-model/src/bun/model-service.ts +490 -0
  427. package/platforms/electrobun/remotes/local-model/src/bun/protocol.ts +301 -0
  428. package/platforms/electrobun/remotes/local-model/src/bun/worker.ts +248 -0
  429. package/platforms/electrobun/remotes/local-model/src/dev/phase8-smoke.ts +116 -0
  430. package/platforms/electrobun/remotes/local-model/src/web/index.html +13 -0
  431. package/platforms/electrobun/remotes/pty/README.md +65 -0
  432. package/platforms/electrobun/remotes/pty/electrobun.config.ts +47 -0
  433. package/platforms/electrobun/remotes/pty/package.json +12 -0
  434. package/platforms/electrobun/remotes/pty/plugin.json +34 -0
  435. package/platforms/electrobun/remotes/pty/src/bun/errors.ts +57 -0
  436. package/platforms/electrobun/remotes/pty/src/bun/output-buffer.ts +127 -0
  437. package/platforms/electrobun/remotes/pty/src/bun/protocol.ts +192 -0
  438. package/platforms/electrobun/remotes/pty/src/bun/pty-service.ts +562 -0
  439. package/platforms/electrobun/remotes/pty/src/bun/worker.ts +218 -0
  440. package/platforms/electrobun/remotes/pty/src/dev/phase6-smoke.ts +127 -0
  441. package/platforms/electrobun/remotes/pty/src/web/index.html +8 -0
  442. package/platforms/electrobun/remotes/runtime/README.md +370 -0
  443. package/platforms/electrobun/remotes/runtime/electrobun.config.ts +48 -0
  444. package/platforms/electrobun/remotes/runtime/package.json +14 -0
  445. package/platforms/electrobun/remotes/runtime/plugin.json +30 -0
  446. package/platforms/electrobun/remotes/runtime/src/bun/api-client.ts +620 -0
  447. package/platforms/electrobun/remotes/runtime/src/bun/errors.ts +45 -0
  448. package/platforms/electrobun/remotes/runtime/src/bun/log-buffer.ts +33 -0
  449. package/platforms/electrobun/remotes/runtime/src/bun/protocol.ts +366 -0
  450. package/platforms/electrobun/remotes/runtime/src/bun/route-discovery.ts +419 -0
  451. package/platforms/electrobun/remotes/runtime/src/bun/runtime-manager.ts +423 -0
  452. package/platforms/electrobun/remotes/runtime/src/bun/sse-parser.ts +99 -0
  453. package/platforms/electrobun/remotes/runtime/src/bun/stream-manager.ts +887 -0
  454. package/platforms/electrobun/remotes/runtime/src/bun/worker.ts +1231 -0
  455. package/platforms/electrobun/remotes/runtime/src/dev/phase1-smoke.ts +34 -0
  456. package/platforms/electrobun/remotes/runtime/src/dev/phase2-smoke.ts +91 -0
  457. package/platforms/electrobun/remotes/runtime/src/dev/phase3-smoke.ts +137 -0
  458. package/platforms/electrobun/remotes/runtime/src/web/index.css +187 -0
  459. package/platforms/electrobun/remotes/runtime/src/web/index.html +76 -0
  460. package/platforms/electrobun/remotes/runtime/src/web/index.ts +192 -0
  461. package/platforms/electrobun/remotes/surface/README.md +201 -0
  462. package/platforms/electrobun/remotes/surface/electrobun.config.ts +38 -0
  463. package/platforms/electrobun/remotes/surface/package.json +12 -0
  464. package/platforms/electrobun/remotes/surface/plugin.json +28 -0
  465. package/platforms/electrobun/remotes/surface/src/bun/worker.ts +132 -0
  466. package/platforms/electrobun/remotes/surface/src/dev/phase4-smoke.ts +566 -0
  467. package/platforms/electrobun/remotes/surface/src/protocol/event-types.ts +84 -0
  468. package/platforms/electrobun/remotes/surface/src/protocol/runtime-client.ts +673 -0
  469. package/platforms/electrobun/remotes/surface/src/web/app.ts +595 -0
  470. package/platforms/electrobun/remotes/surface/src/web/index.css +460 -0
  471. package/platforms/electrobun/remotes/surface/src/web/index.html +466 -0
  472. package/platforms/electrobun/remotes/surface/src/web/index.ts +5 -0
  473. package/platforms/electrobun/remotes/surface/src/web/render.ts +455 -0
  474. package/platforms/electrobun/remotes/surface/src/web/state.ts +427 -0
  475. package/platforms/electrobun/scripts/build-macos-effects.sh +4 -0
  476. package/platforms/electrobun/scripts/ensure-build-folder.ts +28 -0
  477. package/platforms/electrobun/scripts/local-adhoc-sign-macos.ts +159 -159
  478. package/platforms/electrobun/scripts/postwrap-diagnostics.ts +424 -339
  479. package/platforms/electrobun/scripts/postwrap-sign-runtime-macos.ts +302 -271
  480. package/platforms/electrobun/scripts/smoke-test-windows.ps1 +17 -16
  481. package/platforms/electrobun/scripts/smoke-test.sh +5 -7
  482. package/platforms/electrobun/scripts/sync-web-assets.mjs +13 -13
  483. package/platforms/electrobun/scripts/verify-rpc-handlers.ts +109 -110
  484. package/platforms/electrobun/scripts/verify-windows-installer-proof.ps1 +3 -8
  485. package/platforms/electrobun/src/__stubs__/bun-ffi.ts +31 -31
  486. package/platforms/electrobun/src/__stubs__/electrobun-bun.ts +1 -1
  487. package/platforms/electrobun/src/agent-ready-state.ts +8 -8
  488. package/platforms/electrobun/src/agent-reset-from-main.test.ts +162 -0
  489. package/platforms/electrobun/src/agent-reset-from-main.ts +62 -62
  490. package/platforms/electrobun/src/agent-status-rpc.test.ts +95 -0
  491. package/platforms/electrobun/src/agent-status-rpc.ts +156 -0
  492. package/platforms/electrobun/src/api-base.test.ts +247 -0
  493. package/platforms/electrobun/src/api-base.ts +202 -93
  494. package/platforms/electrobun/src/application-menu-action-registry.ts +9 -9
  495. package/platforms/electrobun/src/application-menu.ts +341 -348
  496. package/platforms/electrobun/src/background-notice.ts +36 -36
  497. package/platforms/electrobun/src/boot-progress.test.ts +188 -0
  498. package/platforms/electrobun/src/boot-progress.ts +111 -0
  499. package/platforms/electrobun/src/brand-config.test.ts +39 -0
  500. package/platforms/electrobun/src/brand-config.ts +141 -129
  501. package/platforms/electrobun/src/bridge/browser-tabs-renderer-registry.ts +28 -28
  502. package/platforms/electrobun/src/bridge/electrobun-boot-config.ts +42 -0
  503. package/platforms/electrobun/src/bridge/electrobun-crypto-ready.ts +120 -0
  504. package/platforms/electrobun/src/bridge/electrobun-direct-rpc.ts +342 -357
  505. package/platforms/electrobun/src/bridge/electrobun-stub.ts +13 -13
  506. package/platforms/electrobun/src/browser-workspace-bridge-server.ts +285 -243
  507. package/platforms/electrobun/src/cloud-auth-window.ts +136 -136
  508. package/platforms/electrobun/src/cloud-disconnect-from-main.ts +90 -90
  509. package/platforms/electrobun/src/config-and-auth-rpc.test.ts +256 -0
  510. package/platforms/electrobun/src/config-and-auth-rpc.ts +302 -0
  511. package/platforms/electrobun/src/conversations-and-character-rpc.test.ts +185 -0
  512. package/platforms/electrobun/src/conversations-and-character-rpc.ts +131 -0
  513. package/platforms/electrobun/src/dashboard-rpc.test.ts +200 -0
  514. package/platforms/electrobun/src/dashboard-rpc.ts +344 -0
  515. package/platforms/electrobun/src/database/database-lock.ts +141 -0
  516. package/platforms/electrobun/src/database/database-mode.ts +149 -0
  517. package/platforms/electrobun/src/database/database-recovery.ts +72 -0
  518. package/platforms/electrobun/src/database/database-snapshot.ts +190 -0
  519. package/platforms/electrobun/src/database/database.test.ts +196 -0
  520. package/platforms/electrobun/src/database/index.ts +5 -0
  521. package/platforms/electrobun/src/database/pglite-paths.ts +100 -0
  522. package/platforms/electrobun/src/desktop-deep-link-events.test.ts +30 -0
  523. package/platforms/electrobun/src/desktop-deep-link-events.ts +17 -0
  524. package/platforms/electrobun/src/desktop-http-request.test.ts +102 -73
  525. package/platforms/electrobun/src/desktop-http-request.ts +97 -85
  526. package/platforms/electrobun/src/desktop-pill-config.test.ts +27 -0
  527. package/platforms/electrobun/src/desktop-pill-config.ts +40 -0
  528. package/platforms/electrobun/src/desktop-test-bridge-server.ts +232 -204
  529. package/platforms/electrobun/src/desktop-tray-config.test.ts +87 -0
  530. package/platforms/electrobun/src/desktop-tray-config.ts +84 -0
  531. package/platforms/electrobun/src/devtools-layout.ts +41 -41
  532. package/platforms/electrobun/src/diagnostic-format.test.ts +71 -0
  533. package/platforms/electrobun/src/diagnostic-format.ts +75 -36
  534. package/platforms/electrobun/src/dynamic-view-rpc-schema.test.ts +37 -0
  535. package/platforms/electrobun/src/dynamic-views/README.md +44 -0
  536. package/platforms/electrobun/src/dynamic-views/demo/agent-run-trace.html +135 -0
  537. package/platforms/electrobun/src/dynamic-views/errors.ts +29 -0
  538. package/platforms/electrobun/src/dynamic-views/host.test.ts +353 -0
  539. package/platforms/electrobun/src/dynamic-views/host.ts +332 -0
  540. package/platforms/electrobun/src/dynamic-views/index.ts +57 -0
  541. package/platforms/electrobun/src/dynamic-views/kiosk-canvas.ts +89 -0
  542. package/platforms/electrobun/src/dynamic-views/registry.test.ts +139 -0
  543. package/platforms/electrobun/src/dynamic-views/registry.ts +196 -0
  544. package/platforms/electrobun/src/dynamic-views/session-manager.test.ts +355 -0
  545. package/platforms/electrobun/src/dynamic-views/session-manager.ts +348 -0
  546. package/platforms/electrobun/src/dynamic-views/types.ts +105 -0
  547. package/platforms/electrobun/src/electrobun-boot-config.test.ts +50 -0
  548. package/platforms/electrobun/src/electrobun-config.test.ts +62 -0
  549. package/platforms/electrobun/src/electrobun-crypto-ready.test.ts +65 -0
  550. package/platforms/electrobun/src/electrobun-window-options.ts +25 -0
  551. package/platforms/electrobun/src/extension-rpc.test.ts +88 -0
  552. package/platforms/electrobun/src/extension-rpc.ts +102 -0
  553. package/platforms/electrobun/src/fatal-shutdown.test.ts +12 -16
  554. package/platforms/electrobun/src/fatal-shutdown.ts +1 -1
  555. package/platforms/electrobun/src/first-party-remotes.test.ts +169 -0
  556. package/platforms/electrobun/src/first-party-remotes.ts +297 -0
  557. package/platforms/electrobun/src/first-run-rpc.test.ts +192 -0
  558. package/platforms/electrobun/src/first-run-rpc.ts +146 -0
  559. package/platforms/electrobun/src/floating-chat-window.ts +181 -181
  560. package/platforms/electrobun/src/inbox-rpc.test.ts +123 -0
  561. package/platforms/electrobun/src/inbox-rpc.ts +158 -0
  562. package/platforms/electrobun/src/index.ts +2608 -2098
  563. package/platforms/electrobun/src/kiosk-mode.ts +50 -0
  564. package/platforms/electrobun/src/launch/index.ts +4 -0
  565. package/platforms/electrobun/src/launch/launch-dynamic-view.ts +37 -0
  566. package/platforms/electrobun/src/launch/launch-orchestrator.test.ts +224 -0
  567. package/platforms/electrobun/src/launch/launch-orchestrator.ts +456 -0
  568. package/platforms/electrobun/src/launch/launch-store.test.ts +97 -0
  569. package/platforms/electrobun/src/launch/launch-store.ts +134 -0
  570. package/platforms/electrobun/src/launch/types.ts +103 -0
  571. package/platforms/electrobun/src/launch/views/launch-diagnostics.html +205 -0
  572. package/platforms/electrobun/src/lifecycle/agent-ready-publish.test.ts +50 -0
  573. package/platforms/electrobun/src/lifecycle/agent-ready-publish.ts +27 -0
  574. package/platforms/electrobun/src/lifecycle/api-base-owner.test.ts +83 -0
  575. package/platforms/electrobun/src/lifecycle/api-base-owner.ts +63 -31
  576. package/platforms/electrobun/src/lifecycle/desktop-session-prime.ts +44 -44
  577. package/platforms/electrobun/src/logger.ts +14 -14
  578. package/platforms/electrobun/src/main-window-runtime.ts +83 -83
  579. package/platforms/electrobun/src/main-window-session.test.ts +109 -0
  580. package/platforms/electrobun/src/main-window-session.ts +87 -51
  581. package/platforms/electrobun/src/menu-reset-from-main.ts +158 -158
  582. package/platforms/electrobun/src/native/agent-env.test.ts +52 -0
  583. package/platforms/electrobun/src/native/agent-runtime-layout.test.ts +42 -0
  584. package/platforms/electrobun/src/native/agent-state-dir.test.ts +91 -0
  585. package/platforms/electrobun/src/native/agent.ts +2133 -1682
  586. package/platforms/electrobun/src/native/auth-bridge.test.ts +67 -0
  587. package/platforms/electrobun/src/native/auth-bridge.ts +464 -360
  588. package/platforms/electrobun/src/native/browser-workspace.ts +723 -471
  589. package/platforms/electrobun/src/native/camera.ts +50 -50
  590. package/platforms/electrobun/src/native/canvas.ts +444 -445
  591. package/platforms/electrobun/src/native/credentials.ts +678 -616
  592. package/platforms/electrobun/src/native/desktop-window.test.ts +416 -0
  593. package/platforms/electrobun/src/native/desktop.ts +2200 -2156
  594. package/platforms/electrobun/src/native/editor-bridge.ts +201 -201
  595. package/platforms/electrobun/src/native/file-watcher.ts +154 -154
  596. package/platforms/electrobun/src/native/gateway.ts +179 -180
  597. package/platforms/electrobun/src/native/gpu-window.ts +256 -256
  598. package/platforms/electrobun/src/native/index.ts +82 -74
  599. package/platforms/electrobun/src/native/location.test.ts +44 -0
  600. package/platforms/electrobun/src/native/location.ts +90 -80
  601. package/platforms/electrobun/src/native/loopback-port.ts +60 -60
  602. package/platforms/electrobun/src/native/mac-window-effects.ts +166 -104
  603. package/platforms/electrobun/src/native/music-player.ts +38 -38
  604. package/platforms/electrobun/src/native/permissions-shared.ts +249 -150
  605. package/platforms/electrobun/src/native/permissions.ts +301 -208
  606. package/platforms/electrobun/src/native/power-state.ts +129 -129
  607. package/platforms/electrobun/src/native/remote-plugin-host.test.ts +1394 -0
  608. package/platforms/electrobun/src/native/remote-plugin-host.ts +1541 -0
  609. package/platforms/electrobun/src/native/screencapture.ts +667 -573
  610. package/platforms/electrobun/src/native/steward.ts +207 -204
  611. package/platforms/electrobun/src/native/swabble.ts +68 -324
  612. package/platforms/electrobun/src/native/talkmode.ts +253 -422
  613. package/platforms/electrobun/src/native/webgpu-browser-support.test.ts +18 -0
  614. package/platforms/electrobun/src/native/webgpu-browser-support.ts +165 -147
  615. package/platforms/electrobun/src/native-onboarding.ts +270 -0
  616. package/platforms/electrobun/src/onboarding-overlay-window.ts +141 -0
  617. package/platforms/electrobun/src/persisted-deployment.ts +91 -0
  618. package/platforms/electrobun/src/pill-window.test.ts +91 -0
  619. package/platforms/electrobun/src/pill-window.ts +99 -0
  620. package/platforms/electrobun/src/preload-validation.ts +44 -44
  621. package/platforms/electrobun/src/preload.js +1 -1
  622. package/platforms/electrobun/src/print-electrobun-dev-settings-banner.ts +120 -120
  623. package/platforms/electrobun/src/renderer-api-proxy.test.ts +73 -0
  624. package/platforms/electrobun/src/renderer-api-proxy.ts +86 -0
  625. package/platforms/electrobun/src/renderer-static.test.ts +53 -0
  626. package/platforms/electrobun/src/renderer-static.ts +144 -57
  627. package/platforms/electrobun/src/rpc-handler-slices.ts +121 -0
  628. package/platforms/electrobun/src/rpc-handlers.test.ts +267 -0
  629. package/platforms/electrobun/src/rpc-handlers.ts +1306 -913
  630. package/platforms/electrobun/src/rpc-parse-utils.ts +57 -0
  631. package/platforms/electrobun/src/rpc-port-resolver.test.ts +45 -0
  632. package/platforms/electrobun/src/rpc-port-resolver.ts +31 -0
  633. package/platforms/electrobun/src/rpc-schema.ts +2556 -1619
  634. package/platforms/electrobun/src/runtime-layout.ts +105 -105
  635. package/platforms/electrobun/src/runtime-permissions.ts +95 -95
  636. package/platforms/electrobun/src/runtime-rpc.test.ts +126 -0
  637. package/platforms/electrobun/src/runtime-rpc.ts +237 -0
  638. package/platforms/electrobun/src/screenshot-dev-server.ts +87 -87
  639. package/platforms/electrobun/src/settings-mutations-rpc.test.ts +193 -0
  640. package/platforms/electrobun/src/settings-mutations-rpc.ts +220 -0
  641. package/platforms/electrobun/src/startup-trace.ts +274 -270
  642. package/platforms/electrobun/src/subscription-rpc.test.ts +89 -0
  643. package/platforms/electrobun/src/subscription-rpc.ts +192 -0
  644. package/platforms/electrobun/src/surface-windows.test.ts +355 -0
  645. package/platforms/electrobun/src/surface-windows.ts +410 -410
  646. package/platforms/electrobun/src/trace/README.md +73 -0
  647. package/platforms/electrobun/src/trace/errors.ts +21 -0
  648. package/platforms/electrobun/src/trace/index.ts +40 -0
  649. package/platforms/electrobun/src/trace/trace-dynamic-view.ts +40 -0
  650. package/platforms/electrobun/src/trace/trace-host-requests.ts +473 -0
  651. package/platforms/electrobun/src/trace/trace-service.test.ts +186 -0
  652. package/platforms/electrobun/src/trace/trace-service.ts +324 -0
  653. package/platforms/electrobun/src/trace/trace-store.test.ts +141 -0
  654. package/platforms/electrobun/src/trace/trace-store.ts +551 -0
  655. package/platforms/electrobun/src/trace/types.ts +250 -0
  656. package/platforms/electrobun/src/trace/views/agent-run-trace.html +311 -0
  657. package/platforms/electrobun/src/types/web-speech.d.ts +28 -28
  658. package/platforms/electrobun/src/types.ts +5 -5
  659. package/platforms/electrobun/src/update-availability.test.ts +72 -0
  660. package/platforms/electrobun/src/update-availability.ts +90 -0
  661. package/platforms/electrobun/src/update-rpc.test.ts +83 -0
  662. package/platforms/electrobun/src/update-rpc.ts +123 -0
  663. package/platforms/electrobun/src/voice/README.md +184 -0
  664. package/platforms/electrobun/src/voice/errors.ts +42 -0
  665. package/platforms/electrobun/src/voice/index.ts +78 -0
  666. package/platforms/electrobun/src/voice/types.ts +316 -0
  667. package/platforms/electrobun/src/voice/voice-host-requests.ts +259 -0
  668. package/platforms/electrobun/src/voice/voice-latency-budget.test.ts +66 -0
  669. package/platforms/electrobun/src/voice/voice-latency-budget.ts +243 -0
  670. package/platforms/electrobun/src/voice/voice-live-validation.test.ts +357 -0
  671. package/platforms/electrobun/src/voice/voice-live-validation.ts +838 -0
  672. package/platforms/electrobun/src/voice/voice-pipeline.ts +250 -0
  673. package/platforms/electrobun/src/voice/voice-playback-adapter.ts +31 -0
  674. package/platforms/electrobun/src/voice/voice-runtime-adapter.test.ts +213 -0
  675. package/platforms/electrobun/src/voice/voice-runtime-adapter.ts +687 -0
  676. package/platforms/electrobun/src/voice/voice-service.test.ts +587 -0
  677. package/platforms/electrobun/src/voice/voice-service.ts +1067 -0
  678. package/platforms/electrobun/src/voice/voice-stream-coordinator.test.ts +115 -0
  679. package/platforms/electrobun/src/voice/voice-stream-coordinator.ts +270 -0
  680. package/platforms/electrobun/src/voice/voice-trace.ts +97 -0
  681. package/platforms/electrobun/src/voice/voice-tts-chunker.test.ts +91 -0
  682. package/platforms/electrobun/src/voice/voice-tts-chunker.ts +194 -0
  683. package/platforms/electrobun/src/windows-cef-profile.ts +88 -88
  684. package/platforms/electrobun/tsconfig.json +88 -12
  685. package/platforms/electrobun/update-channels.json +22 -0
  686. package/platforms/electrobun/vitest.electrobun.config.ts +72 -42
  687. package/platforms/ios/App/App/App.entitlements +4 -0
  688. package/platforms/ios/App/App/AppDelegate.swift +80 -18
  689. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ios-marketing-1024.png +0 -0
  690. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-20x20@1x.png +0 -0
  691. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-20x20@2x.png +0 -0
  692. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-29x29@1x.png +0 -0
  693. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-29x29@2x.png +0 -0
  694. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-40x40@1x.png +0 -0
  695. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-40x40@2x.png +0 -0
  696. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-76x76@1x.png +0 -0
  697. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-76x76@2x.png +0 -0
  698. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-83_5x83_5@2x.png +0 -0
  699. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-20x20@2x.png +0 -0
  700. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-20x20@3x.png +0 -0
  701. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-29x29@2x.png +0 -0
  702. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-29x29@3x.png +0 -0
  703. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-40x40@2x.png +0 -0
  704. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-40x40@3x.png +0 -0
  705. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-60x60@2x.png +0 -0
  706. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-60x60@3x.png +0 -0
  707. package/platforms/ios/App/App/Base.lproj/LaunchScreen.storyboard +4 -9
  708. package/platforms/ios/App/App/ComputerUseBridge.swift +589 -0
  709. package/platforms/ios/App/App/DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.entitlements +12 -0
  710. package/platforms/ios/App/App/DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.swift +34 -0
  711. package/platforms/ios/App/App/DeviceActivityMonitorExtension/Info.plist +29 -0
  712. package/platforms/ios/App/App/DeviceActivityReportExtension/DeviceActivityReportExtension.entitlements +12 -0
  713. package/platforms/ios/App/App/DeviceActivityReportExtension/DeviceActivityReportExtension.swift +53 -0
  714. package/platforms/ios/App/App/DeviceActivityReportExtension/Info.plist +27 -0
  715. package/platforms/ios/App/App/ElizaAppIntents.swift +183 -0
  716. package/platforms/ios/App/App/ElizaIntentPlugin.swift +342 -5
  717. package/platforms/ios/App/App/Info.plist +17 -1
  718. package/platforms/ios/App/App/runners/eliza-tasks.js +177 -0
  719. package/platforms/ios/App/App.xcodeproj/project.pbxproj +262 -6
  720. package/platforms/ios/App/BroadcastExtension/SampleHandler.swift +100 -0
  721. package/platforms/ios/App/Podfile +5 -0
  722. package/platforms/ios/App/Podfile.lock +83 -59
  723. package/register-runtime-hooks.js +11 -5
  724. package/registry/index.d.ts +1 -5
  725. package/registry/index.d.ts.map +1 -1
  726. package/registry/index.js +10 -68
  727. package/runtime/android-avf-microdroid-bridge.d.ts +29 -0
  728. package/runtime/android-avf-microdroid-bridge.d.ts.map +1 -0
  729. package/runtime/android-avf-microdroid-bridge.js +149 -0
  730. package/runtime/api-dev-settings-banner.d.ts.map +1 -1
  731. package/runtime/api-dev-settings-banner.js +5 -13
  732. package/runtime/app-core-runtime-hooks.d.ts +21 -0
  733. package/runtime/app-core-runtime-hooks.d.ts.map +1 -0
  734. package/runtime/app-core-runtime-hooks.js +10 -0
  735. package/runtime/autonomy-policy.d.ts +2 -0
  736. package/runtime/autonomy-policy.d.ts.map +1 -0
  737. package/runtime/autonomy-policy.js +4 -0
  738. package/runtime/bundled-fused-lib.d.ts +36 -0
  739. package/runtime/bundled-fused-lib.d.ts.map +1 -0
  740. package/runtime/bundled-fused-lib.js +77 -0
  741. package/runtime/channel-plugin-map.d.ts +1 -5
  742. package/runtime/channel-plugin-map.d.ts.map +1 -1
  743. package/runtime/channel-plugin-map.js +11 -31
  744. package/runtime/desktop/AppWindowRenderer.d.ts +17 -0
  745. package/runtime/desktop/AppWindowRenderer.d.ts.map +1 -0
  746. package/runtime/desktop/AppWindowRenderer.js +360 -0
  747. package/runtime/desktop/DesktopSurfaceNavigationRuntime.d.ts +2 -0
  748. package/runtime/desktop/DesktopSurfaceNavigationRuntime.d.ts.map +1 -0
  749. package/runtime/desktop/DesktopSurfaceNavigationRuntime.js +41 -0
  750. package/runtime/desktop/DesktopTrayRuntime.d.ts +2 -0
  751. package/runtime/desktop/DesktopTrayRuntime.d.ts.map +1 -0
  752. package/runtime/desktop/DesktopTrayRuntime.js +174 -0
  753. package/runtime/desktop/DetachedShellRoot.d.ts +10 -0
  754. package/runtime/desktop/DetachedShellRoot.d.ts.map +1 -0
  755. package/runtime/desktop/DetachedShellRoot.js +111 -0
  756. package/runtime/desktop/index.d.ts +6 -0
  757. package/runtime/desktop/index.d.ts.map +1 -0
  758. package/runtime/desktop/index.js +5 -0
  759. package/runtime/desktop/tray-menu.d.ts +20 -0
  760. package/runtime/desktop/tray-menu.d.ts.map +1 -0
  761. package/runtime/desktop/tray-menu.js +143 -0
  762. package/runtime/dev-server.d.ts +1 -1
  763. package/runtime/dev-server.d.ts.map +1 -1
  764. package/runtime/dev-server.js +104 -19
  765. package/runtime/eliza.d.ts +75 -1
  766. package/runtime/eliza.d.ts.map +1 -1
  767. package/runtime/eliza.js +606 -122
  768. package/runtime/ensure-text-to-speech-handler.d.ts.map +1 -1
  769. package/runtime/ensure-text-to-speech-handler.js +10 -3
  770. package/runtime/mobile-safe-runtime.d.ts +181 -2
  771. package/runtime/mobile-safe-runtime.d.ts.map +1 -1
  772. package/runtime/mobile-safe-runtime.js +1019 -12
  773. package/runtime/mode/remote-forwarder.d.ts.map +1 -1
  774. package/runtime/mode/remote-forwarder.js +2 -2
  775. package/runtime/mode/route-mode-guard.d.ts +2 -3
  776. package/runtime/mode/route-mode-guard.d.ts.map +1 -1
  777. package/runtime/mode/route-mode-guard.js +4 -5
  778. package/runtime/mode/route-mode-matrix.d.ts +1 -1
  779. package/runtime/mode/route-mode-matrix.d.ts.map +1 -1
  780. package/runtime/mode/route-mode-matrix.js +14 -1
  781. package/runtime/mode/runtime-mode.d.ts +1 -1
  782. package/runtime/mode/runtime-mode.js +1 -1
  783. package/runtime/runtime-bootstrap-policy.d.ts.map +1 -1
  784. package/runtime/runtime-bootstrap-policy.js +14 -2
  785. package/runtime/telegram-standalone-handler.d.ts.map +1 -1
  786. package/runtime/telegram-standalone-handler.js +10 -9
  787. package/runtime/tts-cache-wiring.d.ts +29 -0
  788. package/runtime/tts-cache-wiring.d.ts.map +1 -0
  789. package/runtime/tts-cache-wiring.js +114 -0
  790. package/runtime/voice-warmup.d.ts +66 -0
  791. package/runtime/voice-warmup.d.ts.map +1 -0
  792. package/runtime/voice-warmup.js +101 -0
  793. package/scripts/android-sms-gateway-template.test.mjs +1014 -0
  794. package/scripts/aosp/README.md +29 -16
  795. package/scripts/aosp/compile-libllama-zig-pin.test.mjs +147 -0
  796. package/scripts/aosp/compile-libllama.mjs +1750 -267
  797. package/scripts/aosp/compile-libllama.test.mjs +133 -0
  798. package/scripts/aosp/compile-shim.mjs +68 -19
  799. package/scripts/aosp/deploy-pixel.mjs +405 -0
  800. package/scripts/aosp/lib/load-variant-config.mjs +3 -3
  801. package/scripts/aosp/llama-cpp-patches/README.md +8 -8
  802. package/scripts/aosp/llama-cpp-patches/apply-patches.mjs +23 -6
  803. package/scripts/aosp/llama-cpp-patches/polarquant/README.md +37 -0
  804. package/scripts/aosp/llama-cpp-patches/qjl/README.md +37 -0
  805. package/scripts/aosp/seccomp-shim/sigsys-handler-arm64.c +169 -0
  806. package/scripts/aosp/seccomp-shim/sigsys-handler-riscv64.c +217 -0
  807. package/scripts/aosp/smoke-cuttlefish.mjs +34 -4
  808. package/scripts/aosp/stage-default-models.mjs +59 -35
  809. package/scripts/aosp/stage-default-models.test.mjs +23 -0
  810. package/scripts/aosp/variant-config-schema.ts +2 -2
  811. package/scripts/assert-required-bundled-packages.test.ts +555 -0
  812. package/scripts/audit-apple-store-sandbox.mjs +146 -0
  813. package/scripts/audit-live-test-surface.mjs +5 -2
  814. package/scripts/build-capacitor-app.mjs +21 -0
  815. package/scripts/build-flatpak.mjs +5 -5
  816. package/scripts/build-helpers/arm64-simd.mjs +72 -0
  817. package/scripts/build-helpers/omnivoice-merged.mjs +87 -0
  818. package/scripts/build-helpers/verify-fused-symbols.mjs +600 -0
  819. package/scripts/build-helpers/verify-fused-symbols.test.mjs +0 -0
  820. package/scripts/build-image.sh +1 -1
  821. package/scripts/build-llama-cpp-mtp.mjs +488 -0
  822. package/scripts/build-native-plugins.mjs +230 -18
  823. package/scripts/build-patched-electrobun-cli.mjs +68 -10
  824. package/scripts/build-win.mjs +1 -1
  825. package/scripts/bun-riscv64/Dockerfile +418 -0
  826. package/scripts/bun-riscv64/README.md +316 -0
  827. package/scripts/bun-riscv64/build.sh +469 -0
  828. package/scripts/bun-riscv64/bun-patches/0001-config-add-riscv64-arch.patch +74 -0
  829. package/scripts/bun-riscv64/bun-patches/0002-flags-add-riscv64-march-mabi.patch +16 -0
  830. package/scripts/bun-riscv64/bun-patches/0003-zig-add-riscv64-target-triple-and-cpu.patch +26 -0
  831. package/scripts/bun-riscv64/bun-patches/0004-webkit-force-local-mode-on-riscv64.patch +33 -0
  832. package/scripts/bun-riscv64/bun-patches/0005-tinycc-disable-on-riscv64.patch +16 -0
  833. package/scripts/bun-riscv64/bun-patches/0006-build-add-riscv64-cli-validation.patch +15 -0
  834. package/scripts/bun-riscv64/bun-patches/0007-deps-per-dep-riscv64-checks.patch +24 -0
  835. package/scripts/bun-riscv64/bun-patches/0008-source-stabilize-riscv64-musl-build.patch +226 -0
  836. package/scripts/bun-riscv64/bun-patches/0009-disable-wasm-streaming-hooks-for-c-loop.patch +162 -0
  837. package/scripts/bun-riscv64/bun-patches/0010-disable-inspector-profiler-for-riscv64-c-loop.patch +80 -0
  838. package/scripts/bun-riscv64/bun-patches/0011-process-arch-add-riscv64.patch +23 -0
  839. package/scripts/bun-riscv64/bun-patches/0012-cpu-features-add-riscv64-fallback.patch +13 -0
  840. package/scripts/bun-riscv64/bun-patches/0013-disable-console-inspector-hooks-for-riscv64-c-loop.patch +43 -0
  841. package/scripts/bun-riscv64/bun-patches/0014-disable-custom-inspector-dispatchers-on-riscv64.patch +127 -0
  842. package/scripts/bun-riscv64/bun-patches/0015-disable-jsc-profiler-builtins-on-riscv64.patch +75 -0
  843. package/scripts/bun-riscv64/bun-patches/0016-node-vm-disable-jit-cached-data-on-riscv64-c-loop.patch +96 -0
  844. package/scripts/bun-riscv64/bun-patches/0017-disable-performance-domjit-signature-on-riscv64-c-loop.patch +34 -0
  845. package/scripts/bun-riscv64/bun-patches/0018-fix-serialized-script-identifier-big-endian-path.patch +19 -0
  846. package/scripts/bun-riscv64/bun-patches/0019-add-wtf-timer-fire-bridge-for-c-loop.patch +24 -0
  847. package/scripts/bun-riscv64/bun-patches/0020-run-riscv64-smoke-test-under-qemu.patch +13 -0
  848. package/scripts/bun-riscv64/bun-patches/0021-fix-riscv64-linux-open-flags.patch +25 -0
  849. package/scripts/bun-riscv64/bun-patches/0022-zlib-riscv64-generic-kernels.patch +25 -0
  850. package/scripts/bun-riscv64/bun-patches/README.md +127 -0
  851. package/scripts/bun-riscv64/bun-version.json +202 -0
  852. package/scripts/bun-riscv64/run-build.sh +162 -0
  853. package/scripts/bun-riscv64/rust-core/0001-riscv64-rust-core-port.patch +868 -0
  854. package/scripts/bun-riscv64/rust-core/0002-second-wave-riscv64-source-gaps.patch +130 -0
  855. package/scripts/bun-riscv64/rust-core/0003-third-wave-riscv64-crash-handler-gaps.patch +78 -0
  856. package/scripts/bun-riscv64/rust-core/0004-rust-target-cpu-riscv64.patch +39 -0
  857. package/scripts/bun-riscv64/rust-core/0005-fifth-wave-riscv64-source-gaps.patch +96 -0
  858. package/scripts/bun-riscv64/rust-core/0006-cpp-wasm-and-inspector-guards-riscv64.patch +91 -0
  859. package/scripts/bun-riscv64/rust-core/0007-bun-alloc-max-align-t-riscv64.patch +36 -0
  860. package/scripts/bun-riscv64/rust-core/0008-workspace-lints-warn-not-deny-riscv64.patch +75 -0
  861. package/scripts/bun-riscv64/rust-core/0009-zigglobalobject-wasm-streaming-guards-riscv64.patch +109 -0
  862. package/scripts/bun-riscv64/rust-core/0010-tcc-externs-stub-on-riscv64.patch +62 -0
  863. package/scripts/bun-riscv64/rust-core/0011-clippy-ptr-cast-lints-warn-riscv64.patch +61 -0
  864. package/scripts/bun-riscv64/rust-core/README.md +80 -0
  865. package/scripts/bun-riscv64/rust-core/webkit-patches/0003-disable-dfg-ftl-on-riscv64.patch +60 -0
  866. package/scripts/bun-riscv64/rust-core/webkit-patches/0004-riscv64-do-not-force-wasm-in-c-loop.patch +31 -0
  867. package/scripts/bun-riscv64/rust-core/webkit-patches/0005-domjit-effect-allow-no-dfg-c-loop.patch +40 -0
  868. package/scripts/bun-riscv64/rust-core/webkit-patches/0006-disable-usewasm-when-webassembly-compiled-out.patch +33 -0
  869. package/scripts/bun-riscv64/rust-core/webkit-patches/0007-restore-dropped-includes-and-llint-fwd-decl.patch +31 -0
  870. package/scripts/bun-riscv64/validate.sh +264 -0
  871. package/scripts/bun-riscv64/webkit-patches/0001-cherry-pick-llint-riscv64.recipe +155 -0
  872. package/scripts/bun-riscv64/webkit-patches/0002-cherry-pick-baseline-jit-riscv64.recipe +40 -0
  873. package/scripts/bun-riscv64/webkit-patches/0003-disable-dfg-ftl-on-riscv64.patch +60 -0
  874. package/scripts/bun-riscv64/webkit-patches/0004-riscv64-do-not-force-wasm-in-c-loop.patch +31 -0
  875. package/scripts/bun-riscv64/webkit-patches/0005-domjit-effect-allow-no-dfg-c-loop.patch +40 -0
  876. package/scripts/bun-riscv64/webkit-patches/0006-disable-usewasm-when-webassembly-compiled-out.patch +33 -0
  877. package/scripts/bun-riscv64/webkit-patches/0007-restore-dropped-includes-and-llint-fwd-decl.patch +72 -0
  878. package/scripts/bun-riscv64/webkit-patches/README.md +146 -0
  879. package/scripts/check-homepage-public-readiness.mjs +353 -0
  880. package/scripts/check-homepage-release-data.mjs +110 -0
  881. package/scripts/check-i18n.mjs +1 -1
  882. package/scripts/check-real-local-chat.ts +147 -0
  883. package/scripts/check-real-local-provisioning.ts +104 -0
  884. package/scripts/check-real-local-reset.ts +249 -0
  885. package/scripts/check-sms-gateway-completion-audit.mjs +428 -0
  886. package/scripts/check-sms-gateway-readiness.mjs +266 -0
  887. package/scripts/clean-repo.mjs +5 -5
  888. package/scripts/codesign-mas.mjs +222 -16
  889. package/scripts/collect-docker-runtime-deps.mjs +229 -0
  890. package/scripts/continue-sms-gateway-work.mjs +121 -0
  891. package/scripts/copy-runtime-node-modules.ts +1050 -195
  892. package/scripts/deploy-cloud-api-production-gateway.mjs +52 -0
  893. package/scripts/desktop-build.mjs +965 -98
  894. package/scripts/dev-platform.mjs +346 -102
  895. package/scripts/dev-startup-smoke.mjs +248 -0
  896. package/scripts/dev-ui.mjs +418 -176
  897. package/scripts/disable-local-eliza-workspace.mjs +35 -0
  898. package/scripts/docker-ci-smoke.sh +465 -96
  899. package/scripts/docker-entrypoint.sh +62 -1
  900. package/scripts/docker-entrypoint.test.ts +283 -0
  901. package/scripts/ensure-avatars.mjs +2 -2
  902. package/scripts/ensure-electrobun-core.mjs +1 -1
  903. package/scripts/ensure-generated-core-proto-js.mjs +1 -1
  904. package/scripts/ensure-type-package-aliases.mjs +62 -5
  905. package/scripts/ensure-vision-deps.mjs +20 -1
  906. package/scripts/entry.ts +1 -1
  907. package/scripts/ffi-stub/Makefile +64 -0
  908. package/scripts/ffi-stub/README.md +391 -0
  909. package/scripts/ffi-stub/asr-ffi-smoke.ts +139 -0
  910. package/scripts/ffi-stub/ffi-stub.c +539 -0
  911. package/scripts/ffi-stub/ffi.h +538 -0
  912. package/scripts/ffi-stub/libelizainference_stub.so +0 -0
  913. package/scripts/ffi-stub/tts-stream-ffi-smoke.ts +349 -0
  914. package/scripts/generate-first-run-voicelines.mjs +194 -0
  915. package/scripts/generate-plugin-index.js +4 -3
  916. package/scripts/generate-static-asset-manifest.mjs +1 -1
  917. package/scripts/i18n-dynamic-keys.json +5 -5
  918. package/scripts/init-submodules.mjs +2 -2
  919. package/scripts/install-android-sms-gateway.md +177 -0
  920. package/scripts/install-android-sms-gateway.mjs +1088 -0
  921. package/scripts/ios-xcframework/README.md +74 -72
  922. package/scripts/ios-xcframework/build-xcframework.mjs +204 -43
  923. package/scripts/ios-xcframework/run-physical-device-smoke.mjs +1943 -0
  924. package/scripts/ios-xcframework/runtime-symbol-shim.c +450 -0
  925. package/scripts/kernel-patches/cpu-polar-kernels.mjs +441 -0
  926. package/scripts/kernel-patches/cpu-simd-kernels.mjs +253 -0
  927. package/scripts/kernel-patches/cpu-thread-parallelism.mjs +368 -0
  928. package/scripts/kernel-patches/cuda-kernels.mjs +117 -0
  929. package/scripts/kernel-patches/metal-kernels.mjs +1698 -109
  930. package/scripts/kernel-patches/server-omnivoice-route.mjs +718 -0
  931. package/scripts/kernel-patches/server-structured-output.mjs +279 -0
  932. package/scripts/kernel-patches/vulkan-dispatch-log.mjs +166 -0
  933. package/scripts/kernel-patches/vulkan-dispatch-log.test.mjs +50 -0
  934. package/scripts/kernel-patches/vulkan-dispatch-patches/01-vulkan-shaders-gen.patch +30 -16
  935. package/scripts/kernel-patches/vulkan-dispatch-patches/02-ggml-vulkan-pipelines.patch +75 -30
  936. package/scripts/kernel-patches/vulkan-kernels.mjs +800 -49
  937. package/scripts/lib/agent-source-watcher.mjs +173 -0
  938. package/scripts/lib/agent-source-watcher.test.mjs +184 -0
  939. package/scripts/lib/api-supervisor.mjs +78 -9
  940. package/scripts/lib/api-supervisor.test.mjs +121 -0
  941. package/scripts/lib/app-dir.mjs +2 -16
  942. package/scripts/lib/apple-entitlement-audit.mjs +655 -0
  943. package/scripts/lib/apple-entitlement-audit.test.mjs +144 -0
  944. package/scripts/lib/artifact-staleness.d.mts +33 -0
  945. package/scripts/lib/artifact-staleness.mjs +122 -0
  946. package/scripts/lib/artifact-staleness.test.mts +98 -0
  947. package/scripts/lib/bun-version-guard.mjs +13 -13
  948. package/scripts/lib/capacitor-plugin-build-needed.mjs +4 -3
  949. package/scripts/lib/capacitor-plugin-names.mjs +30 -14
  950. package/scripts/lib/desktop-preflight.mjs +9 -5
  951. package/scripts/lib/desktop-startup-embedding-warmup-policy.mjs +51 -0
  952. package/scripts/lib/desktop-startup-embedding-warmup-policy.test.mjs +55 -0
  953. package/scripts/lib/duet-bridge.d.mts +63 -0
  954. package/scripts/lib/duet-bridge.mjs +193 -0
  955. package/scripts/lib/ios-plist-url-scheme.mjs +80 -0
  956. package/scripts/lib/mobile-build-decisions.mjs +97 -0
  957. package/scripts/lib/node-path-env.mjs +4 -2
  958. package/scripts/lib/orchestrator-desktop-dev-banner.mjs +12 -3
  959. package/scripts/lib/patch-bun-exports.mjs +90 -27
  960. package/scripts/lib/patch-bun-exports.test.mjs +79 -0
  961. package/scripts/lib/renderer-build-action.mjs +35 -0
  962. package/scripts/lib/renderer-build-action.test.mjs +70 -0
  963. package/scripts/lib/renderer-build-manifest.d.mts +65 -0
  964. package/scripts/lib/renderer-build-manifest.mjs +257 -0
  965. package/scripts/lib/renderer-build-manifest.test.mts +277 -0
  966. package/scripts/lib/stage-android-agent.mjs +757 -99
  967. package/scripts/lib/sync-eliza-env-aliases.mjs +3 -25
  968. package/scripts/lib/ui-smoke-stub-decision.mjs +33 -0
  969. package/scripts/lib/ui-smoke-stub-decision.test.mjs +46 -0
  970. package/scripts/lib/verify-ondevice-artifact.d.mts +16 -0
  971. package/scripts/lib/verify-ondevice-artifact.mjs +61 -0
  972. package/scripts/lib/verify-ondevice-artifact.test.mts +98 -0
  973. package/scripts/lib/vite-renderer-dist-stale.mjs +5 -0
  974. package/scripts/lib/voice-latency-report.mjs +154 -0
  975. package/scripts/lib/websocket-pending-queue.ts +92 -0
  976. package/scripts/lifeops-prompt-benchmark.ts +21 -12
  977. package/scripts/link-docker-local-app-packages.mjs +109 -36
  978. package/scripts/local-stt-bench.ts +192 -0
  979. package/scripts/maintain-cloud-api-production-gateway.mjs +54 -0
  980. package/scripts/mas-smoke.mjs +459 -0
  981. package/scripts/mas-smoke.test.mjs +220 -0
  982. package/scripts/mobile-auth-simulator-smoke.mjs +0 -1
  983. package/scripts/normalize-eliza-capture.ts +97 -0
  984. package/scripts/omnivoice-fuse/prepare.mjs +2543 -23
  985. package/scripts/pack-upstreams.mjs +65 -5
  986. package/scripts/package-electrobun-linux.mjs +303 -0
  987. package/scripts/patch-deps.mjs +5 -3
  988. package/scripts/patches/llama-asr-word-timestamps.patch +218 -0
  989. package/scripts/playwright-ui-live-stack.ts +588 -157
  990. package/scripts/playwright-ui-smoke-api-stub.mjs +3495 -111
  991. package/scripts/pre-review-local.mjs +2 -2
  992. package/scripts/prepare-ios-cocoapods.sh +41 -3
  993. package/scripts/release-check.ts +180 -84
  994. package/scripts/relink-workspace-packages-to-dist.mjs +21 -4
  995. package/scripts/rt.mjs +16 -1
  996. package/scripts/run-biome-check.mjs +1 -1
  997. package/scripts/run-coding-agent-e2e.mjs +3 -3
  998. package/scripts/run-eliza-app-core-script.mjs +34 -0
  999. package/scripts/run-local-plugin-live-smoke.mjs +66 -2
  1000. package/scripts/run-mobile-build-android-app-actions.test.mjs +426 -0
  1001. package/scripts/run-mobile-build-brand-separation.test.mts +33 -0
  1002. package/scripts/run-mobile-build-ios-engine-gate.test.mjs +85 -0
  1003. package/scripts/run-mobile-build-mtp-staleness.test.mts +198 -0
  1004. package/scripts/run-mobile-build.mjs +5082 -610
  1005. package/scripts/run-node-runtime.mjs +184 -7
  1006. package/scripts/run-node-runtime.test.mjs +167 -0
  1007. package/scripts/run-node-tsx.mjs +80 -33
  1008. package/scripts/run-node.mjs +41 -1
  1009. package/scripts/run-production-build.mjs +34 -27
  1010. package/scripts/run-release-check.mjs +19 -0
  1011. package/scripts/run-release-contract-suite.mjs +107 -14
  1012. package/scripts/run-ui-smoke-playwright-suite.mjs +0 -2
  1013. package/scripts/runtime-package-manifest.ts +21 -3
  1014. package/scripts/serve-real-local-agent.ts +72 -0
  1015. package/scripts/setup-upstreams.mjs +42 -1
  1016. package/scripts/sms-gateway-status.mjs +194 -0
  1017. package/scripts/stage-android-agent.test.mjs +97 -0
  1018. package/scripts/stage-desktop-fused-lib.mjs +453 -0
  1019. package/scripts/stage-elizavoice-lib.mjs +363 -0
  1020. package/scripts/startup-integration-script-drift.test.ts +82 -4
  1021. package/scripts/streaming-pipeline-bench.ts +543 -0
  1022. package/scripts/sync-homepage-porkbun-dns.mjs +262 -0
  1023. package/scripts/test-sms-gateway-software.mjs +100 -0
  1024. package/scripts/type-audit.mjs +1 -1
  1025. package/scripts/validate-bluebubbles-outbound.mjs +293 -0
  1026. package/scripts/validate-cdn-assets.mjs +15 -7
  1027. package/scripts/validate-regression-matrix.mjs +109 -8
  1028. package/scripts/verify-android-sms-gateway-e2e.mjs +362 -0
  1029. package/scripts/verify-bluebubbles-gateway-e2e.mjs +191 -0
  1030. package/scripts/verify-bluebubbles-inbound-readiness.mjs +88 -0
  1031. package/scripts/verify-cloud-api-production-deploy.mjs +87 -0
  1032. package/scripts/verify-cloud-sms-onboarding-flow.mjs +336 -0
  1033. package/scripts/verify-ondevice-artifact.mjs +111 -0
  1034. package/scripts/voice/freeze-voice.mjs +521 -0
  1035. package/scripts/voice-attribution-smoke.ts +644 -0
  1036. package/scripts/voice-create-profile.mjs +379 -0
  1037. package/scripts/voice-duet.mjs +1355 -0
  1038. package/scripts/voice-e2e-hardware.ts +877 -0
  1039. package/scripts/voice-interactive.mjs +1721 -0
  1040. package/scripts/voice-latency-report.mjs +96 -0
  1041. package/scripts/voice-latency-report.test.ts +176 -0
  1042. package/scripts/voice-preset/build-default-voice-preset.mjs +249 -0
  1043. package/scripts/voice-preset/build-onboarding-voice.mjs +281 -0
  1044. package/scripts/watch-sms-gateway-readiness.mjs +303 -0
  1045. package/scripts/write-homepage-release-data.mjs +458 -26
  1046. package/security/agent-vault-id.d.ts +2 -2
  1047. package/security/agent-vault-id.js +1 -1
  1048. package/security/hydrate-wallet-keys-from-platform-store.d.ts.map +1 -1
  1049. package/security/hydrate-wallet-keys-from-platform-store.js +23 -14
  1050. package/security/platform-secure-store-node.d.ts +3 -3
  1051. package/security/platform-secure-store-node.js +3 -3
  1052. package/security/wallet-os-store-actions.d.ts +0 -9
  1053. package/security/wallet-os-store-actions.d.ts.map +1 -1
  1054. package/security/wallet-os-store-actions.js +3 -10
  1055. package/services/account-pool.d.ts +23 -14
  1056. package/services/account-pool.d.ts.map +1 -1
  1057. package/services/account-pool.js +86 -24
  1058. package/services/account-usage.d.ts.map +1 -1
  1059. package/services/account-usage.js +2 -5
  1060. package/services/ambient-audio/consent.d.ts +9 -0
  1061. package/services/ambient-audio/consent.d.ts.map +1 -0
  1062. package/services/ambient-audio/consent.js +28 -0
  1063. package/services/ambient-audio/index.d.ts +7 -0
  1064. package/services/ambient-audio/index.d.ts.map +1 -0
  1065. package/services/ambient-audio/index.js +4 -0
  1066. package/services/ambient-audio/replay-buffer.d.ts +14 -0
  1067. package/services/ambient-audio/replay-buffer.d.ts.map +1 -0
  1068. package/services/ambient-audio/replay-buffer.js +66 -0
  1069. package/services/ambient-audio/response-gate.d.ts +3 -0
  1070. package/services/ambient-audio/response-gate.d.ts.map +1 -0
  1071. package/services/ambient-audio/response-gate.js +33 -0
  1072. package/services/ambient-audio/service.d.ts +22 -0
  1073. package/services/ambient-audio/service.d.ts.map +1 -0
  1074. package/services/ambient-audio/service.js +47 -0
  1075. package/services/ambient-audio/types.d.ts +42 -0
  1076. package/services/ambient-audio/types.d.ts.map +1 -0
  1077. package/services/app-updates/update-policy.d.ts +64 -0
  1078. package/services/app-updates/update-policy.d.ts.map +1 -0
  1079. package/services/app-updates/update-policy.js +228 -0
  1080. package/services/auth-store.d.ts +37 -1
  1081. package/services/auth-store.d.ts.map +1 -1
  1082. package/services/auth-store.js +59 -26
  1083. package/services/cloud-jwks-store.d.ts +3 -3
  1084. package/services/cloud-jwks-store.d.ts.map +1 -1
  1085. package/services/cloud-jwks-store.js +5 -8
  1086. package/services/coding-account-bridge.d.ts +71 -0
  1087. package/services/coding-account-bridge.d.ts.map +1 -0
  1088. package/services/coding-account-bridge.js +267 -0
  1089. package/services/connector-target-catalog.d.ts +11 -4
  1090. package/services/connector-target-catalog.d.ts.map +1 -1
  1091. package/services/connector-target-catalog.js +7 -4
  1092. package/services/credential-tunnel-service.d.ts +66 -0
  1093. package/services/credential-tunnel-service.d.ts.map +1 -0
  1094. package/services/credential-tunnel-service.js +227 -0
  1095. package/services/github-credentials.d.ts +1 -1
  1096. package/services/github-credentials.js +1 -1
  1097. package/services/inference-abort.d.ts +47 -0
  1098. package/services/inference-abort.d.ts.map +1 -0
  1099. package/services/inference-abort.js +76 -0
  1100. package/services/persistence.d.ts +2 -3
  1101. package/services/persistence.d.ts.map +1 -1
  1102. package/services/persistence.js +2 -3
  1103. package/services/phrase-chunked-tts.d.ts +136 -0
  1104. package/services/phrase-chunked-tts.d.ts.map +1 -0
  1105. package/services/phrase-chunked-tts.js +208 -0
  1106. package/services/sandbox-registry.d.ts +78 -0
  1107. package/services/sandbox-registry.d.ts.map +1 -0
  1108. package/services/sandbox-registry.js +323 -0
  1109. package/services/secrets-manager-installer.d.ts +8 -1
  1110. package/services/secrets-manager-installer.d.ts.map +1 -1
  1111. package/services/secrets-manager-installer.js +27 -2
  1112. package/services/sensitive-requests/cloud-link-adapter.d.ts +15 -0
  1113. package/services/sensitive-requests/cloud-link-adapter.d.ts.map +1 -0
  1114. package/services/sensitive-requests/cloud-link-adapter.js +73 -0
  1115. package/services/sensitive-requests/index.d.ts +27 -0
  1116. package/services/sensitive-requests/index.d.ts.map +1 -0
  1117. package/services/sensitive-requests/index.js +51 -0
  1118. package/services/sensitive-requests/instruct-dm-only-adapter.d.ts +14 -0
  1119. package/services/sensitive-requests/instruct-dm-only-adapter.d.ts.map +1 -0
  1120. package/services/sensitive-requests/instruct-dm-only-adapter.js +22 -0
  1121. package/services/sensitive-requests/owner-app-inline-adapter.d.ts +3 -0
  1122. package/services/sensitive-requests/owner-app-inline-adapter.d.ts.map +1 -0
  1123. package/services/sensitive-requests/owner-app-inline-adapter.js +146 -0
  1124. package/services/sensitive-requests/owner-app-oauth-adapter.d.ts +3 -0
  1125. package/services/sensitive-requests/owner-app-oauth-adapter.d.ts.map +1 -0
  1126. package/services/sensitive-requests/owner-app-oauth-adapter.js +156 -0
  1127. package/services/sensitive-requests/public-link-adapter.d.ts +14 -0
  1128. package/services/sensitive-requests/public-link-adapter.d.ts.map +1 -0
  1129. package/services/sensitive-requests/public-link-adapter.js +86 -0
  1130. package/services/sensitive-requests/tunnel-link-adapter.d.ts +17 -0
  1131. package/services/sensitive-requests/tunnel-link-adapter.d.ts.map +1 -0
  1132. package/services/sensitive-requests/tunnel-link-adapter.js +38 -0
  1133. package/services/steward-credentials.d.ts +1 -1
  1134. package/services/steward-credentials.d.ts.map +1 -1
  1135. package/services/steward-credentials.js +10 -6
  1136. package/services/steward-sidecar/health-check.d.ts.map +1 -1
  1137. package/services/steward-sidecar/health-check.js +4 -3
  1138. package/services/steward-sidecar/process-management.d.ts +1 -1
  1139. package/services/steward-sidecar/process-management.d.ts.map +1 -1
  1140. package/services/steward-sidecar/process-management.js +9 -3
  1141. package/services/steward-sidecar/types.d.ts +1 -1
  1142. package/services/steward-sidecar/types.d.ts.map +1 -1
  1143. package/services/steward-sidecar/wallet-setup.d.ts +1 -1
  1144. package/services/steward-sidecar/wallet-setup.d.ts.map +1 -1
  1145. package/services/steward-sidecar/wallet-setup.js +8 -7
  1146. package/services/steward-sidecar.d.ts +5 -5
  1147. package/services/steward-sidecar.d.ts.map +1 -1
  1148. package/services/steward-sidecar.js +27 -19
  1149. package/services/task-host-capabilities.d.ts +60 -0
  1150. package/services/task-host-capabilities.d.ts.map +1 -0
  1151. package/services/task-host-capabilities.js +122 -0
  1152. package/services/tool-call-cache/index.d.ts +2 -2
  1153. package/services/tool-call-cache/index.d.ts.map +1 -1
  1154. package/services/tool-call-cache/index.js +1 -1
  1155. package/services/trigger-event-bridge.js +1 -1
  1156. package/services/tunnel-to-mobile/index.d.ts +2 -0
  1157. package/services/tunnel-to-mobile/index.d.ts.map +1 -0
  1158. package/services/tunnel-to-mobile/index.js +1 -0
  1159. package/services/tunnel-to-mobile/tunnel-to-mobile-client.d.ts +105 -0
  1160. package/services/tunnel-to-mobile/tunnel-to-mobile-client.d.ts.map +1 -0
  1161. package/services/tunnel-to-mobile/tunnel-to-mobile-client.js +190 -0
  1162. package/services/vault-bootstrap.d.ts.map +1 -1
  1163. package/services/vault-bootstrap.js +52 -21
  1164. package/services/vault-mirror.d.ts +1 -1
  1165. package/services/vault-mirror.d.ts.map +1 -1
  1166. package/services/vault-mirror.js +29 -6
  1167. package/services/voice-profiles/diarization-pipeline.d.ts +6 -0
  1168. package/services/voice-profiles/diarization-pipeline.d.ts.map +1 -0
  1169. package/services/voice-profiles/diarization-pipeline.js +20 -0
  1170. package/services/voice-profiles/index.d.ts +12 -0
  1171. package/services/voice-profiles/index.d.ts.map +1 -0
  1172. package/services/voice-profiles/index.js +5 -0
  1173. package/services/voice-profiles/nickname-evaluator.d.ts +14 -0
  1174. package/services/voice-profiles/nickname-evaluator.d.ts.map +1 -0
  1175. package/services/voice-profiles/nickname-evaluator.js +46 -0
  1176. package/services/voice-profiles/owner-confidence.d.ts +10 -0
  1177. package/services/voice-profiles/owner-confidence.d.ts.map +1 -0
  1178. package/services/voice-profiles/owner-confidence.js +38 -0
  1179. package/services/voice-profiles/private-challenge.d.ts +20 -0
  1180. package/services/voice-profiles/private-challenge.d.ts.map +1 -0
  1181. package/services/voice-profiles/private-challenge.js +44 -0
  1182. package/services/voice-profiles/store.d.ts +21 -0
  1183. package/services/voice-profiles/store.d.ts.map +1 -0
  1184. package/services/voice-profiles/store.js +50 -0
  1185. package/services/voice-profiles/types.d.ts +38 -0
  1186. package/services/voice-profiles/types.d.ts.map +1 -0
  1187. package/services/voice-profiles/types.js +1 -0
  1188. package/styles/electrobun-mac-window-drag.css +4 -4
  1189. package/test/helpers/__tests__/live-agent-test.smoke.test.ts +43 -70
  1190. package/test/helpers/browser-mocks.ts +2 -2
  1191. package/test/helpers/conditional-tests.ts +2 -2
  1192. package/test/helpers/live-agent-test.ts +537 -551
  1193. package/test/helpers/live-provider.test.ts +95 -5
  1194. package/test/helpers/live-provider.ts +234 -56
  1195. package/test/helpers/live-runtime-server.ts +4 -4
  1196. package/test/helpers/pglite-runtime.ts +1 -1
  1197. package/test/helpers/real-runtime.ts +72 -20
  1198. package/test/helpers/trajectory-harness.ts +11 -7
  1199. package/test/scripts/start-eliza-live.ts +9 -0
  1200. package/test/scripts/test-parallel.mjs +1 -1
  1201. package/test/scripts/test-root-unit.mjs +6 -7
  1202. package/test/scripts/websocket-pending-queue.test.ts +119 -0
  1203. package/test-support/test-helpers.d.ts +0 -33
  1204. package/test-support/test-helpers.d.ts.map +1 -1
  1205. package/test-support/test-helpers.js +81 -189
  1206. package/ui-compat.d.ts +14 -2
  1207. package/ui-compat.d.ts.map +1 -1
  1208. package/ui-compat.js +23 -3
  1209. package/api/auth-pairing-compat-routes.d.ts +0 -17
  1210. package/api/auth-pairing-compat-routes.d.ts.map +0 -1
  1211. package/api/auth-pairing-compat-routes.js +0 -301
  1212. package/api/local-inference-compat-routes.d.ts +0 -16
  1213. package/api/local-inference-compat-routes.d.ts.map +0 -1
  1214. package/api/local-inference-compat-routes.js +0 -617
  1215. package/api/onboarding-compat-routes.d.ts +0 -4
  1216. package/api/onboarding-compat-routes.d.ts.map +0 -1
  1217. package/api/onboarding-compat-routes.js +0 -207
  1218. package/api/plugins-compat-routes.d.ts +0 -103
  1219. package/api/plugins-compat-routes.d.ts.map +0 -1
  1220. package/api/plugins-compat-routes.js +0 -1181
  1221. package/api/server-onboarding-compat.d.ts +0 -31
  1222. package/api/server-onboarding-compat.d.ts.map +0 -1
  1223. package/api/server-onboarding-compat.js +0 -283
  1224. package/benchmark/cua-routes.d.ts +0 -10
  1225. package/benchmark/cua-routes.d.ts.map +0 -1
  1226. package/benchmark/cua-routes.js +0 -179
  1227. package/benchmark/mock-plugin-base.d.ts +0 -9
  1228. package/benchmark/mock-plugin-base.d.ts.map +0 -1
  1229. package/benchmark/mock-plugin-base.js +0 -325
  1230. package/cli/parse-duration.d.ts +0 -5
  1231. package/cli/parse-duration.d.ts.map +0 -1
  1232. package/cli/parse-duration.js +0 -27
  1233. package/patches/llama-cpp-capacitor@0.1.5.patch +0 -2387
  1234. package/platform/agent-browser-stub.d.ts +0 -27
  1235. package/platform/agent-browser-stub.d.ts.map +0 -1
  1236. package/platform/agent-browser-stub.js +0 -16
  1237. package/platforms/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +0 -26
  1238. package/platforms/android/app/src/main/res/drawable/ic_launcher_background.xml +0 -170
  1239. package/platforms/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -34
  1240. package/platforms/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +0 -18
  1241. package/platforms/electrobun/assets/appIcon.iconset/icon_512x512@2x.png +0 -0
  1242. package/platforms/electrobun/assets/appIcon.png +0 -0
  1243. package/platforms/electrobun/scripts/build-whisper-universal.sh +0 -137
  1244. package/platforms/electrobun/scripts/build-whisper.sh +0 -95
  1245. package/platforms/electrobun/scripts/ensure-whisper-model.sh +0 -93
  1246. package/platforms/electrobun/src/libMacWindowEffects.dylib +0 -0
  1247. package/platforms/electrobun/src/native/whisper.ts +0 -280
  1248. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png +0 -0
  1249. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png +0 -0
  1250. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png +0 -0
  1251. package/registry/generate-apps.d.ts +0 -2
  1252. package/registry/generate-apps.d.ts.map +0 -1
  1253. package/registry/generate-apps.js +0 -338
  1254. package/registry/generate.d.ts +0 -2
  1255. package/registry/generate.d.ts.map +0 -1
  1256. package/registry/generate.js +0 -506
  1257. package/registry/loader.d.ts +0 -27
  1258. package/registry/loader.d.ts.map +0 -1
  1259. package/registry/loader.js +0 -111
  1260. package/registry/schema.d.ts +0 -989
  1261. package/registry/schema.d.ts.map +0 -1
  1262. package/registry/schema.js +0 -264
  1263. package/runtime/embedding-manager-support.d.ts +0 -77
  1264. package/runtime/embedding-manager-support.d.ts.map +0 -1
  1265. package/runtime/embedding-manager-support.js +0 -309
  1266. package/runtime/embedding-presets.d.ts +0 -5
  1267. package/runtime/embedding-presets.d.ts.map +0 -1
  1268. package/runtime/embedding-presets.js +0 -47
  1269. package/runtime/embedding-warmup-policy.d.ts +0 -13
  1270. package/runtime/embedding-warmup-policy.d.ts.map +0 -1
  1271. package/runtime/embedding-warmup-policy.js +0 -33
  1272. package/runtime/ensure-local-inference-handler.d.ts +0 -25
  1273. package/runtime/ensure-local-inference-handler.d.ts.map +0 -1
  1274. package/runtime/ensure-local-inference-handler.js +0 -389
  1275. package/runtime/mobile-local-inference-gate.d.ts +0 -21
  1276. package/runtime/mobile-local-inference-gate.d.ts.map +0 -1
  1277. package/runtime/mobile-local-inference-gate.js +0 -24
  1278. package/scripts/aosp/avd-test.mjs +0 -403
  1279. package/scripts/aosp/boot-validate.mjs +0 -536
  1280. package/scripts/aosp/build-aosp.mjs +0 -448
  1281. package/scripts/aosp/build-bootanimation.mjs +0 -178
  1282. package/scripts/aosp/capture-screens.mjs +0 -325
  1283. package/scripts/aosp/e2e-validate.mjs +0 -225
  1284. package/scripts/aosp/lint-init-rc.mjs +0 -258
  1285. package/scripts/aosp/llama-shim/eliza_llama_shim.c +0 -276
  1286. package/scripts/aosp/sim.mjs +0 -277
  1287. package/scripts/aosp/sync-to-aosp.mjs +0 -134
  1288. package/scripts/aosp/validate.mjs +0 -1273
  1289. package/scripts/build-llama-cpp-dflash.mjs +0 -1866
  1290. package/scripts/generate-onboarding-voicelines.mjs +0 -194
  1291. package/scripts/generated/static-asset-manifest.json +0 -4
  1292. package/scripts/normalize-parallax-capture.ts +0 -97
  1293. package/scripts/omnivoice-fuse/Makefile +0 -44
  1294. package/scripts/omnivoice-fuse/README.md +0 -266
  1295. package/scripts/omnivoice-fuse/cmake-graft.mjs +0 -180
  1296. package/scripts/omnivoice-fuse/ffi-stub.c +0 -222
  1297. package/scripts/omnivoice-fuse/ffi.h +0 -158
  1298. package/scripts/omnivoice-fuse/libelizainference_stub.dylib +0 -0
  1299. package/scripts/omnivoice-fuse/verify-symbols.mjs +0 -138
  1300. package/security/cloud-secret-store.d.ts +0 -34
  1301. package/security/cloud-secret-store.d.ts.map +0 -1
  1302. package/security/cloud-secret-store.js +0 -65
  1303. package/security/export-guard.d.ts +0 -34
  1304. package/security/export-guard.d.ts.map +0 -1
  1305. package/security/export-guard.js +0 -127
  1306. package/services/local-inference/__stress__/cache-stress-helpers.d.ts +0 -76
  1307. package/services/local-inference/__stress__/cache-stress-helpers.d.ts.map +0 -1
  1308. package/services/local-inference/__stress__/cache-stress-helpers.js +0 -238
  1309. package/services/local-inference/active-model.d.ts +0 -180
  1310. package/services/local-inference/active-model.d.ts.map +0 -1
  1311. package/services/local-inference/active-model.js +0 -362
  1312. package/services/local-inference/assignments.d.ts +0 -58
  1313. package/services/local-inference/assignments.d.ts.map +0 -1
  1314. package/services/local-inference/assignments.js +0 -179
  1315. package/services/local-inference/backend.d.ts +0 -200
  1316. package/services/local-inference/backend.d.ts.map +0 -1
  1317. package/services/local-inference/backend.js +0 -242
  1318. package/services/local-inference/bundled-models.d.ts +0 -34
  1319. package/services/local-inference/bundled-models.d.ts.map +0 -1
  1320. package/services/local-inference/bundled-models.js +0 -104
  1321. package/services/local-inference/cache-bridge.d.ts +0 -184
  1322. package/services/local-inference/cache-bridge.d.ts.map +0 -1
  1323. package/services/local-inference/cache-bridge.js +0 -333
  1324. package/services/local-inference/catalog.d.ts +0 -57
  1325. package/services/local-inference/catalog.d.ts.map +0 -1
  1326. package/services/local-inference/catalog.js +0 -262
  1327. package/services/local-inference/conversation-registry.d.ts +0 -122
  1328. package/services/local-inference/conversation-registry.d.ts.map +0 -1
  1329. package/services/local-inference/conversation-registry.js +0 -182
  1330. package/services/local-inference/device-bridge.d.ts +0 -139
  1331. package/services/local-inference/device-bridge.d.ts.map +0 -1
  1332. package/services/local-inference/device-bridge.js +0 -774
  1333. package/services/local-inference/dflash-doctor.d.ts +0 -27
  1334. package/services/local-inference/dflash-doctor.d.ts.map +0 -1
  1335. package/services/local-inference/dflash-doctor.js +0 -149
  1336. package/services/local-inference/dflash-server.d.ts +0 -248
  1337. package/services/local-inference/dflash-server.d.ts.map +0 -1
  1338. package/services/local-inference/dflash-server.js +0 -1076
  1339. package/services/local-inference/downloader.d.ts +0 -48
  1340. package/services/local-inference/downloader.d.ts.map +0 -1
  1341. package/services/local-inference/downloader.js +0 -688
  1342. package/services/local-inference/engine.d.ts +0 -282
  1343. package/services/local-inference/engine.d.ts.map +0 -1
  1344. package/services/local-inference/engine.js +0 -743
  1345. package/services/local-inference/external-scanner.d.ts +0 -17
  1346. package/services/local-inference/external-scanner.d.ts.map +0 -1
  1347. package/services/local-inference/external-scanner.js +0 -261
  1348. package/services/local-inference/handler-registry.d.ts +0 -72
  1349. package/services/local-inference/handler-registry.d.ts.map +0 -1
  1350. package/services/local-inference/handler-registry.js +0 -159
  1351. package/services/local-inference/hardware.d.ts +0 -26
  1352. package/services/local-inference/hardware.d.ts.map +0 -1
  1353. package/services/local-inference/hardware.js +0 -139
  1354. package/services/local-inference/hf-search.d.ts +0 -19
  1355. package/services/local-inference/hf-search.d.ts.map +0 -1
  1356. package/services/local-inference/hf-search.js +0 -169
  1357. package/services/local-inference/index.d.ts +0 -10
  1358. package/services/local-inference/index.d.ts.map +0 -1
  1359. package/services/local-inference/index.js +0 -7
  1360. package/services/local-inference/llama-server-metrics.d.ts +0 -108
  1361. package/services/local-inference/llama-server-metrics.d.ts.map +0 -1
  1362. package/services/local-inference/llama-server-metrics.js +0 -175
  1363. package/services/local-inference/manifest/index.d.ts +0 -4
  1364. package/services/local-inference/manifest/index.d.ts.map +0 -1
  1365. package/services/local-inference/manifest/index.js +0 -5
  1366. package/services/local-inference/manifest/schema.d.ts +0 -419
  1367. package/services/local-inference/manifest/schema.d.ts.map +0 -1
  1368. package/services/local-inference/manifest/schema.js +0 -227
  1369. package/services/local-inference/manifest/types.d.ts +0 -23
  1370. package/services/local-inference/manifest/types.d.ts.map +0 -1
  1371. package/services/local-inference/manifest/types.js +0 -5
  1372. package/services/local-inference/manifest/validator.d.ts +0 -43
  1373. package/services/local-inference/manifest/validator.d.ts.map +0 -1
  1374. package/services/local-inference/manifest/validator.js +0 -187
  1375. package/services/local-inference/paths.d.ts +0 -8
  1376. package/services/local-inference/paths.d.ts.map +0 -1
  1377. package/services/local-inference/paths.js +0 -7
  1378. package/services/local-inference/providers.d.ts +0 -61
  1379. package/services/local-inference/providers.d.ts.map +0 -1
  1380. package/services/local-inference/providers.js +0 -334
  1381. package/services/local-inference/ram-budget.d.ts +0 -57
  1382. package/services/local-inference/ram-budget.d.ts.map +0 -1
  1383. package/services/local-inference/ram-budget.js +0 -107
  1384. package/services/local-inference/readiness.d.ts +0 -9
  1385. package/services/local-inference/readiness.d.ts.map +0 -1
  1386. package/services/local-inference/readiness.js +0 -153
  1387. package/services/local-inference/recommendation.d.ts +0 -62
  1388. package/services/local-inference/recommendation.d.ts.map +0 -1
  1389. package/services/local-inference/recommendation.js +0 -309
  1390. package/services/local-inference/registry.d.ts +0 -35
  1391. package/services/local-inference/registry.d.ts.map +0 -1
  1392. package/services/local-inference/registry.js +0 -117
  1393. package/services/local-inference/router-handler.d.ts +0 -51
  1394. package/services/local-inference/router-handler.d.ts.map +0 -1
  1395. package/services/local-inference/router-handler.js +0 -165
  1396. package/services/local-inference/routing-policy.d.ts +0 -55
  1397. package/services/local-inference/routing-policy.d.ts.map +0 -1
  1398. package/services/local-inference/routing-policy.js +0 -195
  1399. package/services/local-inference/routing-preferences.d.ts +0 -8
  1400. package/services/local-inference/routing-preferences.d.ts.map +0 -1
  1401. package/services/local-inference/routing-preferences.js +0 -7
  1402. package/services/local-inference/service.d.ts +0 -88
  1403. package/services/local-inference/service.d.ts.map +0 -1
  1404. package/services/local-inference/service.js +0 -210
  1405. package/services/local-inference/session-pool.d.ts +0 -72
  1406. package/services/local-inference/session-pool.d.ts.map +0 -1
  1407. package/services/local-inference/session-pool.js +0 -125
  1408. package/services/local-inference/types.d.ts +0 -309
  1409. package/services/local-inference/types.d.ts.map +0 -1
  1410. package/services/local-inference/types.js +0 -23
  1411. package/services/local-inference/verify.d.ts +0 -8
  1412. package/services/local-inference/verify.d.ts.map +0 -1
  1413. package/services/local-inference/verify.js +0 -7
  1414. package/services/local-inference/voice/barge-in.d.ts +0 -15
  1415. package/services/local-inference/voice/barge-in.d.ts.map +0 -1
  1416. package/services/local-inference/voice/barge-in.js +0 -20
  1417. package/services/local-inference/voice/engine-bridge.d.ts +0 -256
  1418. package/services/local-inference/voice/engine-bridge.d.ts.map +0 -1
  1419. package/services/local-inference/voice/engine-bridge.js +0 -398
  1420. package/services/local-inference/voice/ffi-bindings.d.ts +0 -114
  1421. package/services/local-inference/voice/ffi-bindings.d.ts.map +0 -1
  1422. package/services/local-inference/voice/ffi-bindings.js +0 -281
  1423. package/services/local-inference/voice/index.d.ts +0 -51
  1424. package/services/local-inference/voice/index.d.ts.map +0 -1
  1425. package/services/local-inference/voice/index.js +0 -50
  1426. package/services/local-inference/voice/lifecycle.d.ts +0 -135
  1427. package/services/local-inference/voice/lifecycle.d.ts.map +0 -1
  1428. package/services/local-inference/voice/lifecycle.js +0 -189
  1429. package/services/local-inference/voice/phoneme-tokenizer.d.ts +0 -58
  1430. package/services/local-inference/voice/phoneme-tokenizer.d.ts.map +0 -1
  1431. package/services/local-inference/voice/phoneme-tokenizer.js +0 -53
  1432. package/services/local-inference/voice/phrase-cache.d.ts +0 -24
  1433. package/services/local-inference/voice/phrase-cache.d.ts.map +0 -1
  1434. package/services/local-inference/voice/phrase-cache.js +0 -32
  1435. package/services/local-inference/voice/phrase-chunker.d.ts +0 -20
  1436. package/services/local-inference/voice/phrase-chunker.d.ts.map +0 -1
  1437. package/services/local-inference/voice/phrase-chunker.js +0 -85
  1438. package/services/local-inference/voice/ring-buffer.d.ts +0 -40
  1439. package/services/local-inference/voice/ring-buffer.d.ts.map +0 -1
  1440. package/services/local-inference/voice/ring-buffer.js +0 -85
  1441. package/services/local-inference/voice/rollback-queue.d.ts +0 -24
  1442. package/services/local-inference/voice/rollback-queue.d.ts.map +0 -1
  1443. package/services/local-inference/voice/rollback-queue.js +0 -49
  1444. package/services/local-inference/voice/scheduler.d.ts +0 -47
  1445. package/services/local-inference/voice/scheduler.d.ts.map +0 -1
  1446. package/services/local-inference/voice/scheduler.js +0 -123
  1447. package/services/local-inference/voice/shared-resources.d.ts +0 -119
  1448. package/services/local-inference/voice/shared-resources.d.ts.map +0 -1
  1449. package/services/local-inference/voice/shared-resources.js +0 -83
  1450. package/services/local-inference/voice/speaker-preset-cache.d.ts +0 -28
  1451. package/services/local-inference/voice/speaker-preset-cache.d.ts.map +0 -1
  1452. package/services/local-inference/voice/speaker-preset-cache.js +0 -44
  1453. package/services/local-inference/voice/types.d.ts +0 -80
  1454. package/services/local-inference/voice/types.d.ts.map +0 -1
  1455. package/services/local-inference/voice/voice-preset-format.d.ts +0 -56
  1456. package/services/local-inference/voice/voice-preset-format.d.ts.map +0 -1
  1457. package/services/local-inference/voice/voice-preset-format.js +0 -184
  1458. package/services/plugin-installer.d.ts +0 -22
  1459. package/services/plugin-installer.d.ts.map +0 -1
  1460. package/services/plugin-installer.js +0 -41
  1461. package/test/helpers/i18n.ts +0 -5
  1462. package/test/helpers/loopback.ts +0 -33
  1463. package/test/helpers/react-test.ts +0 -40
  1464. package/test/helpers/skip-without.ts +0 -53
  1465. package/test/scripts/task-agent-live-smoke.ts +0 -1335
  1466. /package/services/{local-inference/voice → ambient-audio}/types.js +0 -0
@@ -2,97 +2,136 @@ 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";
11
+ import type { BrowserWindow } from "electrobun/bun";
6
12
  import Electrobun, {
7
- ApplicationMenu,
8
- BrowserView,
9
- BrowserWindow,
10
- BuildConfig,
11
- Updater,
12
- Utils,
13
- WGPU,
14
- webgpu,
13
+ ApplicationMenu,
14
+ BrowserView,
15
+ BuildConfig,
16
+ Updater,
17
+ Utils,
18
+ WGPU,
19
+ webgpu,
15
20
  } from "electrobun/bun";
16
21
  import {
17
- resolveDesktopRuntimeMode,
18
- resolveInitialApiBase,
19
- resolveRendererFacingApiBase,
22
+ resolveDesktopRuntimeModeWithDeployment,
23
+ resolveInitialApiBase,
24
+ resolveRendererFacingApiBase,
20
25
  } from "./api-base";
21
26
  import {
22
- buildApplicationMenu,
23
- findAppMenuEntryBySlug,
24
- getAppMenuEntries,
25
- parseSettingsWindowAction,
27
+ buildApplicationMenu,
28
+ findAppMenuEntryBySlug,
29
+ parseSettingsWindowAction,
26
30
  } from "./application-menu";
27
31
  import { setApplicationMenuActionHandler } from "./application-menu-action-registry";
28
32
  import { showBackgroundNoticeOnce } from "./background-notice";
29
33
  import { getBrandConfig } from "./brand-config";
30
34
  import { startBrowserWorkspaceBridgeServer } from "./browser-workspace-bridge-server";
31
35
  import { readNavigationEventUrl } from "./cloud-auth-window";
36
+ import { readOpenUrlEventUrl } from "./desktop-deep-link-events";
37
+ import { shouldCreateDesktopPill } from "./desktop-pill-config";
32
38
  import { startDesktopTestBridgeServer } from "./desktop-test-bridge-server";
39
+ import {
40
+ shouldCreateDesktopTray,
41
+ shouldStartOnboardingOverlay,
42
+ shouldStartTrayFirst,
43
+ } from "./desktop-tray-config";
33
44
  import { scheduleDevtoolsLayoutRefresh } from "./devtools-layout";
45
+ import { createElectrobunBrowserWindow } from "./electrobun-window-options";
46
+ import { seedFirstPartyRemotePluginsForStartup } from "./first-party-remotes";
34
47
  import { getFloatingChatManager } from "./floating-chat-window";
48
+ import { appendKioskShellModeParam, isKioskShellMode } from "./kiosk-mode";
49
+ import { publishAgentApiBase } from "./lifecycle/agent-ready-publish";
35
50
  import * as apiBaseOwner from "./lifecycle/api-base-owner";
36
51
  import {
37
- markDesktopSessionStale,
38
- primeDesktopSessionAuth,
52
+ markDesktopSessionStale,
53
+ primeDesktopSessionAuth,
39
54
  } from "./lifecycle/desktop-session-prime";
40
55
  import { logger } from "./logger";
41
56
  import {
42
- resolveBootstrapShellRenderer,
43
- resolveBootstrapViewRenderer,
44
- resolveMainWindowPartition,
45
- shouldForceMainWindowCef,
57
+ resolveBootstrapShellRenderer,
58
+ resolveBootstrapViewRenderer,
59
+ resolveMainWindowPartition,
60
+ shouldForceMainWindowCef,
61
+ shouldUseIsolatedMainView,
46
62
  } from "./main-window-session";
47
63
  import {
48
- buildMainMenuResetApiCandidates,
49
- pickReachableMenuResetApiBase,
50
- runMainMenuResetAfterApiBaseResolved,
64
+ buildMainMenuResetApiCandidates,
65
+ pickReachableMenuResetApiBase,
66
+ runMainMenuResetAfterApiBaseResolved,
51
67
  } from "./menu-reset-from-main";
52
68
  import {
53
- configureDesktopLocalApiAuth,
54
- getAgentManager,
55
- getDiagnosticLogPath,
56
- getStartupDiagnosticLogTail,
57
- getStartupDiagnosticsSnapshot,
58
- getStartupStatusPath,
69
+ configureDesktopLocalApiAuth,
70
+ getAgentManager,
71
+ getDiagnosticLogPath,
72
+ getHealthPollTimeoutMs,
73
+ getStartupDiagnosticLogTail,
74
+ getStartupDiagnosticsSnapshot,
75
+ getStartupStatusPath,
59
76
  } from "./native/agent";
60
77
  import { getDesktopManager } from "./native/desktop";
61
78
  import { disposeNativeModules, initializeNativeModules } from "./native/index";
62
79
  import {
63
- enableVibrancy,
64
- ensureShadow,
65
- setNativeDragRegion,
66
- setTrafficLightsPosition,
80
+ enableVibrancy,
81
+ ensureShadow,
82
+ setNativeDragRegion,
83
+ setTrafficLightsPosition,
67
84
  } from "./native/mac-window-effects";
68
85
  import { getPermissionManager } from "./native/permissions";
86
+ import { getRemotePluginHost } from "./native/remote-plugin-host";
69
87
  import { checkWebGpuSupport } from "./native/webgpu-browser-support";
88
+ import {
89
+ submitOnboardingFirstRun,
90
+ waitForApiReady,
91
+ waitForOnboardingNotificationChoice,
92
+ } from "./native-onboarding";
93
+ import {
94
+ closeOnboardingOverlayWindow,
95
+ createOnboardingOverlayWindow,
96
+ getOnboardingOverlayWindow,
97
+ } from "./onboarding-overlay-window";
98
+ import { getPersistedDeployment } from "./persisted-deployment";
99
+ import { createPillWindow, getPillWindow } from "./pill-window";
70
100
  import { printElectrobunDevSettingsBanner } from "./print-electrobun-dev-settings-banner";
71
- import { resolveRendererAsset } from "./renderer-static";
72
101
  import {
73
- buildBunRpcHandlers,
74
- wireBrowserWorkspaceCaller,
102
+ createRendererApiProxyRequestInit,
103
+ isRendererApiProxyPath,
104
+ resolveRendererProxyIdleTimeoutSeconds,
105
+ } from "./renderer-api-proxy";
106
+ import {
107
+ getRendererAssetContentType,
108
+ resolveRendererAsset,
109
+ resolveRendererAssetByteRange,
110
+ } from "./renderer-static";
111
+ import {
112
+ buildBunRpcHandlers,
113
+ wireBrowserWorkspaceCaller,
75
114
  } from "./rpc-handlers";
76
115
  import type { ElizaDesktopRPCSchema } from "./rpc-schema";
77
116
  import {
78
- readResolvedPreloadScript,
79
- resolveRendererAssetDir,
117
+ readResolvedPreloadScript,
118
+ resolveRendererAssetDir,
80
119
  } from "./runtime-layout";
81
120
  import { mergeRuntimePermissionStates } from "./runtime-permissions";
82
121
  import { startScreenshotDevServer } from "./screenshot-dev-server";
83
122
  import { recordStartupPhase, resolveStartupBundlePath } from "./startup-trace";
84
123
  import {
85
- type BoundsStore,
86
- isDetachedSurface,
87
- type ManagedWindowFrame,
88
- type ManagedWindowLike,
89
- SurfaceWindowManager,
124
+ type BoundsStore,
125
+ isDetachedSurface,
126
+ type ManagedWindowFrame,
127
+ type ManagedWindowLike,
128
+ SurfaceWindowManager,
90
129
  } from "./surface-windows";
91
130
  import type { SendToWebview } from "./types.js";
92
131
  import {
93
- resolveDesktopBundleVersion,
94
- shouldResetWindowsCefProfile,
95
- shouldWriteWindowsCefProfileMarker,
132
+ resolveDesktopBundleVersion,
133
+ shouldResetWindowsCefProfile,
134
+ shouldWriteWindowsCefProfileMarker,
96
135
  } from "./windows-cef-profile";
97
136
 
98
137
  const BRAND = getBrandConfig();
@@ -101,88 +140,102 @@ const STARTUP_CRASH_REPORT_FILE = "startup-crash-report-latest.md";
101
140
  const STARTUP_CRASH_PROMPT_MARKER_FILE = "startup-crash-last-prompted.txt";
102
141
 
103
142
  import {
104
- isAgentReady,
105
- onAgentReadyChange,
106
- setAgentReady,
143
+ isAgentReady,
144
+ onAgentReadyChange,
145
+ setAgentReady,
107
146
  } from "./agent-ready-state";
108
147
  import {
109
- clearCurrentMainWindow,
110
- setCurrentMainWindow,
111
- updateCurrentMainWindowEffectsState,
148
+ clearCurrentMainWindow,
149
+ setCurrentMainWindow,
150
+ updateCurrentMainWindowEffectsState,
112
151
  } from "./main-window-runtime";
113
152
  import {
114
- isStewardLocalEnabled,
115
- onStewardStatusChange,
116
- resetSteward,
117
- restartSteward,
118
- setStewardSendToWebview,
119
- startSteward,
120
- stopSteward,
153
+ isStewardLocalEnabled,
154
+ onStewardStatusChange,
155
+ resetSteward,
156
+ restartSteward,
157
+ setStewardSendToWebview,
158
+ startSteward,
159
+ stopSteward,
121
160
  } from "./native/steward";
122
161
 
123
162
  function resolveDesktopAppIconPath(): string {
124
- return path.join(
125
- import.meta.dir,
126
- process.platform === "win32"
127
- ? "../assets/appIcon.ico"
128
- : "../assets/appIcon.png",
129
- );
163
+ return path.join(
164
+ import.meta.dir,
165
+ process.platform === "win32"
166
+ ? "../assets/appIcon.ico"
167
+ : "../assets/appIcon.png",
168
+ );
130
169
  }
131
170
 
132
171
  function shouldUseBrowserDevtoolsFallback(): boolean {
133
- return false;
172
+ return false;
134
173
  }
135
174
 
136
175
  function setupApplicationMenu(): void {
137
- const isMac = process.platform === "darwin";
138
- const menu = buildApplicationMenu({
139
- isMac,
140
- browserEnabled: false,
141
- detachedWindows: surfaceWindowManager?.listWindows() ?? [],
142
- agentReady: isAgentReady(),
143
- });
144
- ApplicationMenu.setApplicationMenu(
145
- menu as Parameters<typeof ApplicationMenu.setApplicationMenu>[0],
146
- );
176
+ const isMac = process.platform === "darwin";
177
+ const menu = buildApplicationMenu({
178
+ isMac,
179
+ browserEnabled: false,
180
+ detachedWindows: surfaceWindowManager?.listWindows() ?? [],
181
+ agentReady: isAgentReady(),
182
+ });
183
+ ApplicationMenu.setApplicationMenu(
184
+ menu as Parameters<typeof ApplicationMenu.setApplicationMenu>[0],
185
+ );
147
186
  }
148
187
 
149
188
  onAgentReadyChange(() => setupApplicationMenu());
150
189
 
190
+ /**
191
+ * Resolve the desktop runtime mode, consulting both the env vars and the
192
+ * persisted deployment target (`eliza.json` `deploymentTarget.runtime`). A
193
+ * topology-3 (cloud-hosted) agent target with a renderer-ready cloud agent
194
+ * base resolves to `external` so the embedded agent is skipped; topology 1
195
+ * (local agent → cloud inference) and topology 2 (all-local) keep `local`.
196
+ */
197
+ function resolveDesktopRuntime(): ReturnType<
198
+ typeof resolveDesktopRuntimeModeWithDeployment
199
+ > {
200
+ return resolveDesktopRuntimeModeWithDeployment(
201
+ process.env as Record<string, string | undefined>,
202
+ getPersistedDeployment(),
203
+ );
204
+ }
205
+
151
206
  function summarizeDesktopActionError(error: unknown, fallback: string): string {
152
- const message = error instanceof Error ? error.message : fallback;
153
- const trimmed = message.trim();
154
- if (!trimmed) return fallback;
155
- return trimmed.length > 80 ? `${trimmed.slice(0, 77)}...` : trimmed;
207
+ const message = error instanceof Error ? error.message : fallback;
208
+ const trimmed = message.trim();
209
+ if (!trimmed) return fallback;
210
+ return trimmed.length > 80 ? `${trimmed.slice(0, 77)}...` : trimmed;
156
211
  }
157
212
 
158
213
  function buildApiRequestHeaders(contentType?: string): Record<string, string> {
159
- const headers: Record<string, string> = {
160
- Accept: "application/json",
161
- };
162
- if (contentType) {
163
- headers["Content-Type"] = contentType;
164
- }
165
- let apiToken = resolveApiToken(process.env);
166
- if (!apiToken) {
167
- const rt = resolveDesktopRuntimeMode(
168
- process.env as Record<string, string | undefined>,
169
- );
170
- if (rt.mode === "local") {
171
- apiToken = configureDesktopLocalApiAuth().trim();
172
- }
173
- }
174
- if (apiToken) {
175
- headers.Authorization = `Bearer ${apiToken}`;
176
- }
177
- return headers;
214
+ const headers: Record<string, string> = {
215
+ Accept: "application/json",
216
+ };
217
+ if (contentType) {
218
+ headers["Content-Type"] = contentType;
219
+ }
220
+ let apiToken = resolveApiToken(process.env);
221
+ if (!apiToken) {
222
+ const rt = resolveDesktopRuntime();
223
+ if (rt.mode === "local") {
224
+ apiToken = configureDesktopLocalApiAuth().trim();
225
+ }
226
+ }
227
+ if (apiToken) {
228
+ headers.Authorization = `Bearer ${apiToken}`;
229
+ }
230
+ return headers;
178
231
  }
179
232
 
180
233
  function resolveLoopbackApiBase(): string | null {
181
- const port = getAgentManager().getStatus().port;
182
- if (typeof port === "number" && port > 0) {
183
- return `http://127.0.0.1:${port}`;
184
- }
185
- return resolveInitialApiBase(process.env);
234
+ const port = getAgentManager().getStatus().port;
235
+ if (typeof port === "number" && port > 0) {
236
+ return `http://127.0.0.1:${port}`;
237
+ }
238
+ return resolveInitialApiBase(process.env);
186
239
  }
187
240
 
188
241
  /**
@@ -194,28 +247,28 @@ function resolveLoopbackApiBase(): string | null {
194
247
  * port, menu Reset must not blindly POST to the dead env URL.
195
248
  */
196
249
  async function resolveReachableApiBaseForMainReset(): Promise<string | null> {
197
- const candidates = buildMainMenuResetApiCandidates({
198
- embeddedPort: getAgentManager().getStatus().port,
199
- configuredBase: resolveInitialApiBase(process.env),
200
- });
201
- if (candidates.length === 0) {
202
- return null;
203
- }
204
- const base = await pickReachableMenuResetApiBase({
205
- candidates,
206
- fetchImpl: fetch,
207
- buildHeaders: buildApiRequestHeaders,
208
- });
209
- if (base) {
210
- logger.info(
211
- `[Main][reset] Using reachable API base ${base} (tried: ${candidates.join(", ")})`,
212
- );
213
- } else {
214
- logger.warn(
215
- `[Main][reset] No reachable API base among candidates (tried: ${candidates.join(", ")})`,
216
- );
217
- }
218
- return base;
250
+ const candidates = buildMainMenuResetApiCandidates({
251
+ embeddedPort: getAgentManager().getStatus().port,
252
+ configuredBase: resolveInitialApiBase(process.env),
253
+ });
254
+ if (candidates.length === 0) {
255
+ return null;
256
+ }
257
+ const base = await pickReachableMenuResetApiBase({
258
+ candidates,
259
+ fetchImpl: fetch,
260
+ buildHeaders: buildApiRequestHeaders,
261
+ });
262
+ if (base) {
263
+ logger.info(
264
+ `[Main][reset] Using reachable API base ${base} (tried: ${candidates.join(", ")})`,
265
+ );
266
+ } else {
267
+ logger.warn(
268
+ `[Main][reset] No reachable API base among candidates (tried: ${candidates.join(", ")})`,
269
+ );
270
+ }
271
+ return base;
219
272
  }
220
273
 
221
274
  /**
@@ -230,112 +283,110 @@ async function resolveReachableApiBaseForMainReset(): Promise<string | null> {
230
283
  * @see `docs/apps/desktop-main-process-reset.md`
231
284
  */
232
285
  async function resetTheAppFromApplicationMenu(): Promise<void> {
233
- logger.info(
234
- `[Main][reset] App menu: Reset ${BRAND.appName} — confirm + POST /api/agent/reset + restart (main process)`,
235
- );
236
- await getDesktopManager()
237
- .showWindow()
238
- .catch((err: unknown) => {
239
- logger.warn(
240
- `[Main][reset] showWindow failed (continuing): ${err instanceof Error ? err.message : String(err)}`,
241
- );
242
- });
243
-
244
- const autoConfirm =
245
- process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_DIALOGS === "1" ||
246
- process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_RESET === "1";
247
- const response = autoConfirm
248
- ? 0
249
- : await Utils.showMessageBox({
250
- type: "warning",
251
- title: "Reset Agent",
252
- message:
253
- "This will reset the agent: config, cloud keys, and local agent database (conversations / memory).",
254
- detail:
255
- "Downloaded GGUF embedding models are kept. You will return to the onboarding wizard.",
256
- buttons: ["Reset", "Cancel"],
257
- defaultId: 0,
258
- cancelId: 1,
259
- }).then((box) =>
260
- box && typeof box === "object" && "response" in box
261
- ? (box as { response: number }).response
262
- : typeof box === "number"
263
- ? box
264
- : 1,
265
- );
266
- if (response !== 0) {
267
- logger.info("[Main][reset] User cancelled native confirm");
268
- return;
269
- }
270
-
271
- const apiBase = await resolveReachableApiBaseForMainReset();
272
- if (!apiBase) {
273
- Utils.showNotification({
274
- title: "Reset Failed",
275
- body: `Could not reach the ${BRAND.appName} API (tried embedded port and ELIZA_DESKTOP_API_BASE / defaults). Start the agent or dev server, or fix your API base env.`,
276
- });
277
- return;
278
- }
279
-
280
- try {
281
- const runtimeMode = resolveDesktopRuntimeMode(
282
- process.env as Record<string, string | undefined>,
283
- );
284
-
285
- await runMainMenuResetAfterApiBaseResolved({
286
- apiBase,
287
- fetchImpl: fetch,
288
- buildHeaders: buildApiRequestHeaders,
289
- useEmbeddedRestart: runtimeMode.mode === "local",
290
- restartEmbeddedClearingLocalDb: async () => {
291
- const status = await getAgentManager().restartClearingLocalDb();
292
- return { port: status.port ?? undefined };
293
- },
294
- pushEmbeddedApiBaseToRenderer: (port, apiToken) => {
295
- if (currentWindow) {
296
- const base = port
297
- ? resolveRendererFacingApiBase(
298
- process.env as Record<string, string | undefined>,
299
- port,
300
- )
301
- : (resolveLoopbackApiBase() ??
302
- resolveInitialApiBase(
303
- process.env as Record<string, string | undefined>,
304
- ) ??
305
- apiBase);
306
- if (base) {
307
- apiBaseOwner.notifyChange(currentWindow, base, apiToken);
308
- }
309
- }
310
- },
311
- getLocalApiAuthToken: () => configureDesktopLocalApiAuth(),
312
- postExternalAgentRestart: async () => {
313
- try {
314
- await fetch(`${apiBase}/api/agent/restart`, {
315
- method: "POST",
316
- headers: buildApiRequestHeaders(),
317
- });
318
- } catch {
319
- /* 409 / race while restarting — poll below */
320
- }
321
- },
322
- resolveApiBaseForStatusPoll: () => resolveLoopbackApiBase() ?? apiBase,
323
- sendMenuResetAppliedToRenderer: (payload) => {
324
- sendToActiveRenderer("desktopTrayMenuClick", payload);
325
- },
326
- });
327
- logger.info(
328
- "[Main][reset] Pushed menu-reset-app-applied to renderer with /api/status snapshot",
329
- );
330
- } catch (err) {
331
- logger.error(
332
- `[Main][reset] Main-process reset failed: ${err instanceof Error ? err.message : String(err)}`,
333
- );
334
- Utils.showNotification({
335
- title: "Reset Failed",
336
- body: summarizeDesktopActionError(err, "Reset failed"),
337
- });
338
- }
286
+ logger.info(
287
+ `[Main][reset] App menu: Reset ${BRAND.appName} — confirm + POST /api/agent/reset + restart (main process)`,
288
+ );
289
+ await getDesktopManager()
290
+ .showWindow()
291
+ .catch((err: unknown) => {
292
+ logger.warn(
293
+ `[Main][reset] showWindow failed (continuing): ${err instanceof Error ? err.message : String(err)}`,
294
+ );
295
+ });
296
+
297
+ const autoConfirm =
298
+ process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_DIALOGS === "1" ||
299
+ process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_RESET === "1";
300
+ const response = autoConfirm
301
+ ? 0
302
+ : await Utils.showMessageBox({
303
+ type: "warning",
304
+ title: "Reset Agent",
305
+ message:
306
+ "This will reset the agent: config, cloud keys, and local agent database (conversations / memory).",
307
+ detail:
308
+ "Downloaded GGUF embedding models are kept. You will return to first-run runtime setup.",
309
+ buttons: ["Reset", "Cancel"],
310
+ defaultId: 0,
311
+ cancelId: 1,
312
+ }).then((box) =>
313
+ box && typeof box === "object" && "response" in box
314
+ ? (box as { response: number }).response
315
+ : typeof box === "number"
316
+ ? box
317
+ : 1,
318
+ );
319
+ if (response !== 0) {
320
+ logger.info("[Main][reset] User cancelled native confirm");
321
+ return;
322
+ }
323
+
324
+ const apiBase = await resolveReachableApiBaseForMainReset();
325
+ if (!apiBase) {
326
+ Utils.showNotification({
327
+ title: "Reset Failed",
328
+ body: `Could not reach the ${BRAND.appName} API (tried embedded port and ELIZA_DESKTOP_API_BASE / defaults). Start the agent or dev server, or fix your API base env.`,
329
+ });
330
+ return;
331
+ }
332
+
333
+ try {
334
+ const runtimeMode = resolveDesktopRuntime();
335
+
336
+ await runMainMenuResetAfterApiBaseResolved({
337
+ apiBase,
338
+ fetchImpl: fetch,
339
+ buildHeaders: buildApiRequestHeaders,
340
+ useEmbeddedRestart: runtimeMode.mode === "local",
341
+ restartEmbeddedClearingLocalDb: async () => {
342
+ const status = await getAgentManager().restartClearingLocalDb();
343
+ return { port: status.port ?? undefined };
344
+ },
345
+ pushEmbeddedApiBaseToRenderer: (port, apiToken) => {
346
+ if (currentWindow) {
347
+ const base = port
348
+ ? resolveRendererFacingApiBase(
349
+ process.env as Record<string, string | undefined>,
350
+ port,
351
+ )
352
+ : (resolveLoopbackApiBase() ??
353
+ resolveInitialApiBase(
354
+ process.env as Record<string, string | undefined>,
355
+ ) ??
356
+ apiBase);
357
+ if (base) {
358
+ apiBaseOwner.notifyChange(currentWindow, base, apiToken);
359
+ }
360
+ }
361
+ },
362
+ getLocalApiAuthToken: () => configureDesktopLocalApiAuth(),
363
+ postExternalAgentRestart: async () => {
364
+ try {
365
+ await fetch(`${apiBase}/api/agent/restart`, {
366
+ method: "POST",
367
+ headers: buildApiRequestHeaders(),
368
+ });
369
+ } catch {
370
+ /* 409 / race while restarting — poll below */
371
+ }
372
+ },
373
+ resolveApiBaseForStatusPoll: () => resolveLoopbackApiBase() ?? apiBase,
374
+ sendMenuResetAppliedToRenderer: (payload) => {
375
+ sendToActiveRenderer("desktopTrayMenuClick", payload);
376
+ },
377
+ });
378
+ logger.info(
379
+ "[Main][reset] Pushed menu-reset-app-applied to renderer with /api/status snapshot",
380
+ );
381
+ } catch (err) {
382
+ logger.error(
383
+ `[Main][reset] Main-process reset failed: ${err instanceof Error ? err.message : String(err)}`,
384
+ );
385
+ Utils.showNotification({
386
+ title: "Reset Failed",
387
+ body: summarizeDesktopActionError(err, "Reset failed"),
388
+ });
389
+ }
339
390
  }
340
391
 
341
392
  const MAC_TRAFFIC_LIGHTS_X = 14;
@@ -355,75 +406,75 @@ const MAC_NATIVE_DRAG_REGION_HEIGHT = 38;
355
406
  * view stays above WKWebView.
356
407
  */
357
408
  function applyMacOSWindowEffects(win: BrowserWindow): void {
358
- if (process.platform !== "darwin") return;
359
-
360
- const ptr = (win as { ptr?: unknown }).ptr;
361
- if (!ptr) {
362
- logger.warn("[MacEffects] win.ptr unavailable — skipping native effects");
363
- return;
364
- }
365
-
366
- const vibrancyEnabled = enableVibrancy(
367
- ptr as Parameters<typeof enableVibrancy>[0],
368
- );
369
- const shadowEnabled = ensureShadow(ptr as Parameters<typeof ensureShadow>[0]);
370
- updateCurrentMainWindowEffectsState({
371
- vibrancyEnabled,
372
- shadowEnabled,
373
- });
374
-
375
- const alignButtons = () =>
376
- setTrafficLightsPosition(
377
- ptr as Parameters<typeof setTrafficLightsPosition>[0],
378
- MAC_TRAFFIC_LIGHTS_X,
379
- MAC_TRAFFIC_LIGHTS_Y,
380
- );
381
- const alignDragRegion = () =>
382
- setNativeDragRegion(
383
- ptr as Parameters<typeof setNativeDragRegion>[0],
384
- MAC_NATIVE_DRAG_REGION_X,
385
- MAC_NATIVE_DRAG_REGION_HEIGHT,
386
- );
387
-
388
- const alignChrome = () => {
389
- alignButtons();
390
- alignDragRegion();
391
- };
392
-
393
- alignChrome();
394
- setTimeout(alignChrome, 120);
395
- const chromeRefreshTimer = setInterval(alignChrome, 1000);
396
-
397
- win.on("resize", alignChrome);
398
- win.on("focus", alignChrome);
399
- win.on("blur", () => {
400
- alignChrome();
401
- setTimeout(alignChrome, 80);
402
- setTimeout(alignChrome, 240);
403
- setTimeout(alignChrome, 700);
404
- });
405
- // Display (NSScreen) changes without a resize edge case — depth uses window.screen.
406
- win.on("move", alignChrome);
407
- win.on("close", () => clearInterval(chromeRefreshTimer));
408
-
409
- // WKWebView is often inserted or reordered after first layout; restack native
410
- // views so drag/resize strips stay hit-testable above the page.
411
- try {
412
- win.webview.on("dom-ready", () => {
413
- alignChrome();
414
- setTimeout(alignChrome, 50);
415
- setTimeout(alignChrome, 300);
416
- });
417
- } catch {
418
- // webview may not accept listeners yet in some embed paths
419
- }
409
+ if (process.platform !== "darwin") return;
410
+
411
+ const ptr = (win as { ptr?: unknown }).ptr;
412
+ if (!ptr) {
413
+ logger.warn("[MacEffects] win.ptr unavailable — skipping native effects");
414
+ return;
415
+ }
416
+
417
+ const vibrancyEnabled = enableVibrancy(
418
+ ptr as Parameters<typeof enableVibrancy>[0],
419
+ );
420
+ const shadowEnabled = ensureShadow(ptr as Parameters<typeof ensureShadow>[0]);
421
+ updateCurrentMainWindowEffectsState({
422
+ vibrancyEnabled,
423
+ shadowEnabled,
424
+ });
425
+
426
+ const alignButtons = () =>
427
+ setTrafficLightsPosition(
428
+ ptr as Parameters<typeof setTrafficLightsPosition>[0],
429
+ MAC_TRAFFIC_LIGHTS_X,
430
+ MAC_TRAFFIC_LIGHTS_Y,
431
+ );
432
+ const alignDragRegion = () =>
433
+ setNativeDragRegion(
434
+ ptr as Parameters<typeof setNativeDragRegion>[0],
435
+ MAC_NATIVE_DRAG_REGION_X,
436
+ MAC_NATIVE_DRAG_REGION_HEIGHT,
437
+ );
438
+
439
+ const alignChrome = () => {
440
+ alignButtons();
441
+ alignDragRegion();
442
+ };
443
+
444
+ alignChrome();
445
+ setTimeout(alignChrome, 120);
446
+ const chromeRefreshTimer = setInterval(alignChrome, 1000);
447
+
448
+ win.on("resize", alignChrome);
449
+ win.on("focus", alignChrome);
450
+ win.on("blur", () => {
451
+ alignChrome();
452
+ setTimeout(alignChrome, 80);
453
+ setTimeout(alignChrome, 240);
454
+ setTimeout(alignChrome, 700);
455
+ });
456
+ // Display (NSScreen) changes without a resize edge case — depth uses window.screen.
457
+ win.on("move", alignChrome);
458
+ win.on("close", () => clearInterval(chromeRefreshTimer));
459
+
460
+ // WKWebView is often inserted or reordered after first layout; restack native
461
+ // views so drag/resize strips stay hit-testable above the page.
462
+ try {
463
+ win.webview.on("dom-ready", () => {
464
+ alignChrome();
465
+ setTimeout(alignChrome, 50);
466
+ setTimeout(alignChrome, 300);
467
+ });
468
+ } catch {
469
+ // webview may not accept listeners yet in some embed paths
470
+ }
420
471
  }
421
472
 
422
473
  interface WindowState {
423
- x: number;
424
- y: number;
425
- width: number;
426
- height: number;
474
+ x: number;
475
+ y: number;
476
+ width: number;
477
+ height: number;
427
478
  }
428
479
 
429
480
  /**
@@ -434,10 +485,10 @@ interface WindowState {
434
485
  * systems where maximize() hasn't registered yet.
435
486
  */
436
487
  const DEFAULT_WINDOW_STATE: WindowState = {
437
- x: 60,
438
- y: 60,
439
- width: 1440,
440
- height: 900,
488
+ x: 60,
489
+ y: 60,
490
+ width: 1440,
491
+ height: 900,
441
492
  };
442
493
 
443
494
  /**
@@ -449,58 +500,58 @@ const DEFAULT_WINDOW_STATE: WindowState = {
449
500
  const MAXIMIZE_ON_LAUNCH_SENTINEL = 1;
450
501
 
451
502
  interface PersistedWindowState extends WindowState {
452
- /** When truthy, call win.maximize() right after creation. */
453
- shouldMaximize?: number;
503
+ /** When truthy, call win.maximize() right after creation. */
504
+ shouldMaximize?: number;
454
505
  }
455
506
 
456
507
  function loadWindowState(statePath: string): PersistedWindowState {
457
- try {
458
- if (fs.existsSync(statePath)) {
459
- const data = JSON.parse(fs.readFileSync(statePath, "utf8"));
460
- if (typeof data.width === "number" && typeof data.height === "number") {
461
- const state = { ...DEFAULT_WINDOW_STATE, ...data };
462
- // Discard state saved while the window was minimized. On Windows,
463
- // minimized windows report position (-32000, -32000) and a tiny
464
- // size, which makes the window invisible on next launch.
465
- if (state.width < 200 || state.height < 200 || state.x < -16000) {
466
- return {
467
- ...DEFAULT_WINDOW_STATE,
468
- shouldMaximize: MAXIMIZE_ON_LAUNCH_SENTINEL,
469
- };
470
- }
471
- return state;
472
- }
473
- }
474
- } catch {}
475
- // No saved state → first launch. Open at the default 1440×900 window
476
- // size (centered-ish near top-left) instead of maximizing. Maximizing on
477
- // first launch buries the welcome content in a vast empty workspace and
478
- // gives a "this is overwhelming" impression. The user can always
479
- // maximize themselves; subsequent launches restore their last size.
480
- return { ...DEFAULT_WINDOW_STATE };
508
+ try {
509
+ if (fs.existsSync(statePath)) {
510
+ const data = JSON.parse(fs.readFileSync(statePath, "utf8"));
511
+ if (typeof data.width === "number" && typeof data.height === "number") {
512
+ const state = { ...DEFAULT_WINDOW_STATE, ...data };
513
+ // Discard state saved while the window was minimized. On Windows,
514
+ // minimized windows report position (-32000, -32000) and a tiny
515
+ // size, which makes the window invisible on next launch.
516
+ if (state.width < 200 || state.height < 200 || state.x < -16000) {
517
+ return {
518
+ ...DEFAULT_WINDOW_STATE,
519
+ shouldMaximize: MAXIMIZE_ON_LAUNCH_SENTINEL,
520
+ };
521
+ }
522
+ return state;
523
+ }
524
+ }
525
+ } catch {}
526
+ // No saved state → first launch. Open at the default 1440×900 window
527
+ // size (centered-ish near top-left) instead of maximizing. Maximizing on
528
+ // first launch buries the welcome content in a vast empty workspace and
529
+ // gives a "this is overwhelming" impression. The user can always
530
+ // maximize themselves; subsequent launches restore their last size.
531
+ return { ...DEFAULT_WINDOW_STATE };
481
532
  }
482
533
 
483
534
  let saveTimer: ReturnType<typeof setTimeout> | null = null;
484
535
 
485
536
  function scheduleStateSave(statePath: string, win: BrowserWindow): void {
486
- if (saveTimer) clearTimeout(saveTimer);
487
- saveTimer = setTimeout(() => {
488
- try {
489
- const { x, y } = win.getPosition();
490
- const { width, height } = win.getSize();
491
- // Skip saving when the window is minimized — Windows reports
492
- // position (-32000, -32000) and a collapsed size, which would make
493
- // the window invisible on next launch.
494
- if (width < 200 || height < 200 || x < -16000) return;
495
- const dir = path.dirname(statePath);
496
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
497
- fs.writeFileSync(
498
- statePath,
499
- JSON.stringify({ x, y, width, height }),
500
- "utf8",
501
- );
502
- } catch {}
503
- }, 500);
537
+ if (saveTimer) clearTimeout(saveTimer);
538
+ saveTimer = setTimeout(() => {
539
+ try {
540
+ const { x, y } = win.getPosition();
541
+ const { width, height } = win.getSize();
542
+ // Skip saving when the window is minimized — Windows reports
543
+ // position (-32000, -32000) and a collapsed size, which would make
544
+ // the window invisible on next launch.
545
+ if (width < 200 || height < 200 || x < -16000) return;
546
+ const dir = path.dirname(statePath);
547
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
548
+ fs.writeFileSync(
549
+ statePath,
550
+ JSON.stringify({ x, y, width, height }),
551
+ "utf8",
552
+ );
553
+ } catch {}
554
+ }, 500);
504
555
  }
505
556
 
506
557
  /**
@@ -512,69 +563,69 @@ function scheduleStateSave(statePath: string, win: BrowserWindow): void {
512
563
  * Mixing them would couple unrelated lifecycles.
513
564
  */
514
565
  function createAppWindowBoundsStore(): BoundsStore {
515
- const storePath = path.join(Utils.paths.userData, "app-window-bounds.json");
516
- type Blob = Record<string, ManagedWindowFrame>;
517
- let cache: Blob | null = null;
518
-
519
- function isFrame(value: unknown): value is ManagedWindowFrame {
520
- if (!value || typeof value !== "object") return false;
521
- const f = value as Record<string, unknown>;
522
- return (
523
- typeof f.x === "number" &&
524
- typeof f.y === "number" &&
525
- typeof f.width === "number" &&
526
- typeof f.height === "number" &&
527
- f.width >= 200 &&
528
- f.height >= 200 &&
529
- f.x > -16000 &&
530
- f.y > -16000
531
- );
532
- }
533
-
534
- function readCache(): Blob {
535
- if (cache) return cache;
536
- try {
537
- if (fs.existsSync(storePath)) {
538
- const raw = JSON.parse(fs.readFileSync(storePath, "utf8")) as unknown;
539
- if (raw && typeof raw === "object") {
540
- const next: Blob = {};
541
- for (const [slug, frame] of Object.entries(raw)) {
542
- if (isFrame(frame)) next[slug] = frame;
543
- }
544
- cache = next;
545
- return next;
546
- }
547
- }
548
- } catch {
549
- /* ignore — corrupt or missing file just yields empty cache */
550
- }
551
- cache = {};
552
- return cache;
553
- }
554
-
555
- function writeCache(): void {
556
- if (!cache) return;
557
- try {
558
- const dir = path.dirname(storePath);
559
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
560
- fs.writeFileSync(storePath, JSON.stringify(cache), "utf8");
561
- } catch {
562
- /* ignore — bounds save must never break the window */
563
- }
564
- }
565
-
566
- return {
567
- load: (slug) => {
568
- const blob = readCache();
569
- return blob[slug] ?? null;
570
- },
571
- save: (slug, frame) => {
572
- if (!isFrame(frame)) return;
573
- const blob = readCache();
574
- blob[slug] = frame;
575
- writeCache();
576
- },
577
- };
566
+ const storePath = path.join(Utils.paths.userData, "app-window-bounds.json");
567
+ type Blob = Record<string, ManagedWindowFrame>;
568
+ let cache: Blob | null = null;
569
+
570
+ function isFrame(value: unknown): value is ManagedWindowFrame {
571
+ if (!value || typeof value !== "object") return false;
572
+ const f = value as Record<string, unknown>;
573
+ return (
574
+ typeof f.x === "number" &&
575
+ typeof f.y === "number" &&
576
+ typeof f.width === "number" &&
577
+ typeof f.height === "number" &&
578
+ f.width >= 200 &&
579
+ f.height >= 200 &&
580
+ f.x > -16000 &&
581
+ f.y > -16000
582
+ );
583
+ }
584
+
585
+ function readCache(): Blob {
586
+ if (cache) return cache;
587
+ try {
588
+ if (fs.existsSync(storePath)) {
589
+ const raw = JSON.parse(fs.readFileSync(storePath, "utf8")) as unknown;
590
+ if (raw && typeof raw === "object") {
591
+ const next: Blob = {};
592
+ for (const [slug, frame] of Object.entries(raw)) {
593
+ if (isFrame(frame)) next[slug] = frame;
594
+ }
595
+ cache = next;
596
+ return next;
597
+ }
598
+ }
599
+ } catch {
600
+ /* ignore — corrupt or missing file just yields empty cache */
601
+ }
602
+ cache = {};
603
+ return cache;
604
+ }
605
+
606
+ function writeCache(): void {
607
+ if (!cache) return;
608
+ try {
609
+ const dir = path.dirname(storePath);
610
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
611
+ fs.writeFileSync(storePath, JSON.stringify(cache), "utf8");
612
+ } catch {
613
+ /* ignore — bounds save must never break the window */
614
+ }
615
+ }
616
+
617
+ return {
618
+ load: (slug) => {
619
+ const blob = readCache();
620
+ return blob[slug] ?? null;
621
+ },
622
+ save: (slug, frame) => {
623
+ if (!isFrame(frame)) return;
624
+ const blob = readCache();
625
+ blob[slug] = frame;
626
+ writeCache();
627
+ },
628
+ };
578
629
  }
579
630
 
580
631
  let currentWindow: BrowserWindow | null = null;
@@ -583,65 +634,90 @@ let surfaceWindowManager: SurfaceWindowManager | null = null;
583
634
  let rendererUrlPromise: Promise<string> | null = null;
584
635
  let backgroundWindowPromise: Promise<void> | null = null;
585
636
  let isQuitting = false;
637
+ let quitRequestPromise: Promise<void> | null = null;
638
+
639
+ function requestAppQuit(): Promise<void> {
640
+ if (quitRequestPromise) {
641
+ return quitRequestPromise;
642
+ }
643
+
644
+ isQuitting = true;
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;
654
+ }
586
655
 
587
- function requestAppQuit(): void {
588
- isQuitting = true;
589
- Utils.quit();
656
+ /**
657
+ * True for packaged desktop builds, false for the in-repo dev runtime.
658
+ * The compiled bundle no longer runs out of a `/src/` directory, so its
659
+ * absence is the dev/prod signal already used by loadTheAppEnvFilesForMain.
660
+ * Normalize Windows separators first so dev paths containing `\src\` are not
661
+ * misclassified as packaged (which would make dev builds fatal on Windows).
662
+ */
663
+ function isPackagedDesktopBuild(): boolean {
664
+ return !import.meta.dir.replaceAll("\\", "/").includes("/src/");
590
665
  }
591
666
 
592
667
  const cleanupFns: Array<() => void | Promise<void>> = [];
668
+ let shutdownCleanupPromise: Promise<void> | null = null;
593
669
  let lastFocusedWindow: ManagedWindowLike | null = null;
594
670
  const macOpenedDevtoolsWindowIds = new Set<number>();
595
671
 
596
672
  async function openBrowserDevtoolsFallback(
597
- targetWindow: ManagedWindowLike | BrowserWindow | null,
673
+ targetWindow: ManagedWindowLike | BrowserWindow | null,
598
674
  ): Promise<void> {
599
- const currentUrl = (
600
- targetWindow?.webview as { url?: string | null } | undefined
601
- )?.url;
602
- const url = currentUrl?.trim() || (await resolveRendererUrl());
603
-
604
- if (!/^https?:\/\//i.test(url)) {
605
- Utils.showNotification({
606
- title: "Developer Tools Unavailable",
607
- body: "Native macOS Electrobun devtools are disabled, and the renderer URL is not browser-openable.",
608
- });
609
- return;
610
- }
611
-
612
- Utils.openExternal(url);
613
- Utils.showNotification({
614
- title: "Opened Renderer in Browser",
615
- body: "Native macOS Electrobun devtools are disabled due to a WKWebView crash/layout bug. Use browser devtools instead.",
616
- });
675
+ const currentUrl = (
676
+ targetWindow?.webview as { url?: string | null } | undefined
677
+ )?.url;
678
+ const url = currentUrl?.trim() || (await resolveRendererUrl());
679
+
680
+ if (!/^https?:\/\//i.test(url)) {
681
+ Utils.showNotification({
682
+ title: "Developer Tools Unavailable",
683
+ body: "Native macOS Electrobun devtools are disabled, and the renderer URL is not browser-openable.",
684
+ });
685
+ return;
686
+ }
687
+
688
+ Utils.openExternal(url);
689
+ Utils.showNotification({
690
+ title: "Opened Renderer in Browser",
691
+ body: "Native macOS Electrobun devtools are disabled due to a WKWebView crash/layout bug. Use browser devtools instead.",
692
+ });
617
693
  }
618
694
 
619
695
  function sendToActiveRenderer(message: string, payload?: unknown): void {
620
- currentSendToWebview?.(message, payload);
621
- if (!currentSendToWebview) {
622
- const level =
623
- message === "desktopTrayMenuClick" ? console.warn : console.debug;
624
- level.call(
625
- console,
626
- "[Main] Dropped renderer message (no window):",
627
- message,
628
- );
629
- }
696
+ currentSendToWebview?.(message, payload);
697
+ if (!currentSendToWebview) {
698
+ const level =
699
+ message === "desktopTrayMenuClick" ? console.warn : console.debug;
700
+ level.call(
701
+ console,
702
+ "[Main] Dropped renderer message (no window):",
703
+ message,
704
+ );
705
+ }
630
706
  }
631
707
 
632
708
  function sendManagedWindowsChanged(): void {
633
- sendToActiveRenderer("desktopManagedWindowsChanged", {
634
- windows: surfaceWindowManager?.listWindows() ?? [],
635
- });
709
+ sendToActiveRenderer("desktopManagedWindowsChanged", {
710
+ windows: surfaceWindowManager?.listWindows() ?? [],
711
+ });
636
712
  }
637
713
 
638
714
  function shouldRestoreWindowBeforeMenuAction(
639
- action: string | undefined,
715
+ action: string | undefined,
640
716
  ): boolean {
641
- if (!action || action.startsWith("focus-window:")) {
642
- return false;
643
- }
644
- return action !== "quit";
717
+ if (!action || action.startsWith("focus-window:")) {
718
+ return false;
719
+ }
720
+ return action !== "quit";
645
721
  }
646
722
 
647
723
  /**
@@ -650,632 +726,804 @@ function shouldRestoreWindowBeforeMenuAction(
650
726
  * Returns the base URL e.g. "http://localhost:5174".
651
727
  */
652
728
  async function startRendererServer(): Promise<string> {
653
- const rendererDir = resolveRendererAssetDir(import.meta.dir);
654
- if (!fs.existsSync(rendererDir)) {
655
- logger.warn("[Renderer] renderer dir not found:", rendererDir);
656
- return "";
657
- }
658
-
659
- // Find a free port starting at 5174 (5173 reserved for Vite dev)
660
- const getPort = (start: number): Promise<number> =>
661
- new Promise((resolve) => {
662
- const srv = createNetServer();
663
- srv.listen(start, "127.0.0.1", () => {
664
- const { port } = srv.address() as { port: number };
665
- srv.close(() => resolve(port));
666
- });
667
- srv.on("error", () => resolve(getPort(start + 1)));
668
- });
669
-
670
- const port = await getPort(5174);
671
-
672
- const mimeTypes: Record<string, string> = {
673
- ".html": "text/html; charset=utf-8",
674
- ".js": "application/javascript",
675
- ".mjs": "application/javascript",
676
- ".css": "text/css",
677
- ".png": "image/png",
678
- ".jpg": "image/jpeg",
679
- ".svg": "image/svg+xml",
680
- ".ico": "image/x-icon",
681
- ".json": "application/json",
682
- ".gz": "application/octet-stream",
683
- ".wasm": "application/wasm",
684
- ".glb": "model/gltf-binary",
685
- ".gltf": "model/gltf+json",
686
- ".vrm": "model/gltf-binary",
687
- };
688
-
689
- // Seed the api-base-owner singleton with the initial value so the
690
- // HTML-inject path and the RPC push path both read the same source of
691
- // truth. Without this seeding, the static server would inject one value
692
- // into HTML before the renderer mounts and the RPC bridge would push a
693
- // different value moments later — the renderer racing two answers is
694
- // what produced the port-shift disconnect documented in MASTER.md §0.
695
- const initialApiBase = resolveInitialApiBase(
696
- process.env as Record<string, string | undefined>,
697
- );
698
- const initialApiToken =
699
- resolveDesktopRuntimeMode(process.env as Record<string, string | undefined>)
700
- .mode === "local"
701
- ? configureDesktopLocalApiAuth()
702
- : (resolveApiToken(process.env) ?? "");
703
- apiBaseOwner.setCurrent(initialApiBase, initialApiToken);
704
-
705
- const resolveRendererCacheControl = (
706
- pathname: string,
707
- mimeExt: string,
708
- ): string => {
709
- if (pathname.startsWith("/assets/")) {
710
- return "public, max-age=31536000, immutable";
711
- }
712
- if (
713
- mimeExt === ".vrm" ||
714
- pathname.endsWith(".vrm.gz") ||
715
- pathname.startsWith("/vrms/previews/") ||
716
- pathname.startsWith("/vrms/backgrounds/") ||
717
- [
718
- ".png",
719
- ".jpg",
720
- ".jpeg",
721
- ".gif",
722
- ".webp",
723
- ".avif",
724
- ".svg",
725
- ".mp3",
726
- ".wav",
727
- ".ogg",
728
- ".m4a",
729
- ".aac",
730
- ".flac",
731
- ".glb",
732
- ].includes(mimeExt)
733
- ) {
734
- return "public, max-age=86400";
735
- }
736
- return "public, max-age=0, must-revalidate";
737
- };
738
-
739
- Bun.serve({
740
- port,
741
- hostname: "127.0.0.1",
742
- async fetch(req) {
743
- const url = new URL(req.url);
744
- const pathname = url.pathname;
745
-
746
- // Proxy /api/*, /ws, /music-player to the agent port. Mirrors the Vite
747
- // dev-server proxy in apps/app/vite.config.ts so the renderer can rely
748
- // on same-origin /api fetches whether it's loaded via Vite (watch mode)
749
- // or this static server (non-watch dev:desktop). Without this, every
750
- // /api/* call returned SPA HTML and Settings sat on "Loading…" forever.
751
- if (
752
- initialApiBase &&
753
- (pathname.startsWith("/api/") ||
754
- pathname === "/ws" ||
755
- pathname.startsWith("/music-player"))
756
- ) {
757
- const target = new URL(pathname + url.search, initialApiBase);
758
- const headers = new Headers(req.headers);
759
- headers.set("host", target.host);
760
- try {
761
- const upstream = await fetch(target, {
762
- method: req.method,
763
- headers,
764
- body: req.body,
765
- // @ts-expect-error Bun fetch supports duplex for streaming bodies
766
- duplex: "half",
767
- redirect: "manual",
768
- });
769
- return new Response(upstream.body, {
770
- status: upstream.status,
771
- statusText: upstream.statusText,
772
- headers: upstream.headers,
773
- });
774
- } catch (err) {
775
- return new Response(
776
- JSON.stringify({
777
- error: "API server unavailable",
778
- detail: err instanceof Error ? err.message : String(err),
779
- }),
780
- {
781
- status: 502,
782
- headers: { "Content-Type": "application/json" },
783
- },
784
- );
785
- }
786
- }
787
-
788
- const { filePath, isGzipped, mimeExt } = resolveRendererAsset({
789
- rendererDir,
790
- urlPath: pathname,
791
- existsSync: fs.existsSync,
792
- statSync: fs.statSync,
793
- });
794
-
795
- try {
796
- const content = fs.readFileSync(filePath);
797
- // Inject API base into HTML responses
798
- if (mimeExt === ".html" || filePath.endsWith("index.html")) {
799
- const html = apiBaseOwner.injectIntoHtml(content.toString("utf8"));
800
- return new Response(html, {
801
- headers: {
802
- "Content-Type": "text/html; charset=utf-8",
803
- "Access-Control-Allow-Origin": "*",
804
- "Cache-Control": "public, max-age=0, must-revalidate",
805
- },
806
- });
807
- }
808
-
809
- const headers: Record<string, string> = {
810
- "Content-Type": mimeTypes[mimeExt] ?? "application/octet-stream",
811
- "Access-Control-Allow-Origin": "*",
812
- "Cache-Control": resolveRendererCacheControl(pathname, mimeExt),
813
- };
814
-
815
- if (isGzipped) {
816
- headers["Content-Encoding"] = "gzip";
817
- }
818
-
819
- return new Response(content, { headers });
820
- } catch {
821
- return new Response("Not found", { status: 404 });
822
- }
823
- },
824
- });
825
-
826
- console.log(`[Renderer] Static server on http://127.0.0.1:${port}`);
827
- return `http://127.0.0.1:${port}`;
729
+ const rendererDir = resolveRendererAssetDir(import.meta.dir);
730
+ if (!fs.existsSync(rendererDir)) {
731
+ logger.warn("[Renderer] renderer dir not found:", rendererDir);
732
+ return "";
733
+ }
734
+
735
+ // Find a free port starting at 5174 (5173 reserved for Vite dev)
736
+ const getPort = (start: number): Promise<number> =>
737
+ new Promise((resolve) => {
738
+ const srv = createNetServer();
739
+ srv.listen(start, "127.0.0.1", () => {
740
+ const { port } = srv.address() as { port: number };
741
+ srv.close(() => resolve(port));
742
+ });
743
+ srv.on("error", () => resolve(getPort(start + 1)));
744
+ });
745
+
746
+ const port = await getPort(5174);
747
+
748
+ // Seed the api-base-owner singleton with the initial value so the
749
+ // HTML-inject path and the RPC push path both read the same source of
750
+ // truth. Without this seeding, the static server would inject one value
751
+ // into HTML before the renderer mounts and the RPC bridge would push a
752
+ // different value moments later — the renderer racing two answers is
753
+ // what produced the port-shift disconnect documented in MASTER.md §0.
754
+ const initialRuntime = resolveDesktopRuntime();
755
+ // External mode (env-forced OR a cloud-hosted deployment target) seeds the
756
+ // resolved external base directly; local mode keeps the loopback agent port.
757
+ const initialApiBase =
758
+ initialRuntime.mode === "external" && initialRuntime.externalApi.base
759
+ ? initialRuntime.externalApi.base
760
+ : resolveInitialApiBase(
761
+ process.env as Record<string, string | undefined>,
762
+ );
763
+ const initialApiToken =
764
+ initialRuntime.mode === "local"
765
+ ? configureDesktopLocalApiAuth()
766
+ : (resolveApiToken(process.env) ?? "");
767
+ apiBaseOwner.setCurrent(initialApiBase, initialApiToken);
768
+
769
+ const resolveRendererCacheControl = (
770
+ pathname: string,
771
+ mimeExt: string,
772
+ ): string => {
773
+ if (pathname.startsWith("/assets/")) {
774
+ return "public, max-age=31536000, immutable";
775
+ }
776
+ if (
777
+ mimeExt === ".vrm" ||
778
+ pathname.endsWith(".vrm.gz") ||
779
+ pathname.startsWith("/vrms/previews/") ||
780
+ pathname.startsWith("/vrms/backgrounds/") ||
781
+ [
782
+ ".png",
783
+ ".jpg",
784
+ ".jpeg",
785
+ ".gif",
786
+ ".webp",
787
+ ".avif",
788
+ ".svg",
789
+ ".mp3",
790
+ ".wav",
791
+ ".ogg",
792
+ ".m4a",
793
+ ".aac",
794
+ ".flac",
795
+ ".mp4",
796
+ ".webm",
797
+ ".glb",
798
+ ".gltf",
799
+ ".vrm",
800
+ ".woff",
801
+ ".woff2",
802
+ ".ttf",
803
+ ".otf",
804
+ ].includes(mimeExt)
805
+ ) {
806
+ return "public, max-age=86400";
807
+ }
808
+ return "public, max-age=0, must-revalidate";
809
+ };
810
+
811
+ const rendererProxyIdleTimeoutSeconds =
812
+ resolveRendererProxyIdleTimeoutSeconds(process.env);
813
+
814
+ Bun.serve({
815
+ port,
816
+ hostname: "127.0.0.1",
817
+ // The renderer fetches long-lived chat/SSE endpoints through this
818
+ // same-origin proxy. Bun's default 10s idle timeout cuts those streams
819
+ // while local inference is still pre-filling; keep it aligned with the
820
+ // API server's long request budget, capped to Bun.serve's accepted range.
821
+ idleTimeout: rendererProxyIdleTimeoutSeconds,
822
+ async fetch(req) {
823
+ const url = new URL(req.url);
824
+ const pathname = url.pathname;
825
+
826
+ // Proxy /api/*, /ws, /music-player to the agent port. Mirrors the Vite
827
+ // dev-server proxy in apps/app/vite.config.ts so the renderer can rely
828
+ // on same-origin /api fetches whether it's loaded via Vite (watch mode)
829
+ // or this static server (non-watch dev:desktop). Without this, every
830
+ // /api/* call returned SPA HTML and Settings sat on "Loading…" forever.
831
+ const apiBase = apiBaseOwner.getCurrent().base ?? initialApiBase;
832
+ if (apiBase && isRendererApiProxyPath(pathname)) {
833
+ const target = new URL(pathname + url.search, apiBase);
834
+ try {
835
+ const upstreamRequest = createRendererApiProxyRequestInit(
836
+ req,
837
+ target,
838
+ );
839
+ const upstream = await fetch(target, upstreamRequest);
840
+ return new Response(upstream.body, {
841
+ status: upstream.status,
842
+ statusText: upstream.statusText,
843
+ headers: upstream.headers,
844
+ });
845
+ } catch (err) {
846
+ return new Response(
847
+ JSON.stringify({
848
+ error: "API server unavailable",
849
+ detail: err instanceof Error ? err.message : String(err),
850
+ }),
851
+ {
852
+ status: 502,
853
+ headers: { "Content-Type": "application/json" },
854
+ },
855
+ );
856
+ }
857
+ }
858
+
859
+ const { filePath, isGzipped, mimeExt } = resolveRendererAsset({
860
+ rendererDir,
861
+ urlPath: pathname,
862
+ existsSync: fs.existsSync,
863
+ statSync: fs.statSync,
864
+ });
865
+
866
+ try {
867
+ const content = fs.readFileSync(filePath);
868
+ // Inject API base into HTML responses
869
+ if (mimeExt === ".html" || filePath.endsWith("index.html")) {
870
+ const html = apiBaseOwner.injectIntoHtml(content.toString("utf8"));
871
+ return new Response(html, {
872
+ headers: {
873
+ "Content-Type": "text/html; charset=utf-8",
874
+ "Access-Control-Allow-Origin": "*",
875
+ "Cache-Control": "public, max-age=0, must-revalidate",
876
+ },
877
+ });
878
+ }
879
+
880
+ const headers: Record<string, string> = {
881
+ "Content-Type": getRendererAssetContentType(mimeExt),
882
+ "Access-Control-Allow-Origin": "*",
883
+ "Cache-Control": resolveRendererCacheControl(pathname, mimeExt),
884
+ "Accept-Ranges": "bytes",
885
+ "Content-Length": String(content.byteLength),
886
+ };
887
+
888
+ if (isGzipped) {
889
+ headers["Content-Encoding"] = "gzip";
890
+ }
891
+
892
+ const byteRange = isGzipped
893
+ ? null
894
+ : resolveRendererAssetByteRange(
895
+ req.headers.get("range"),
896
+ content.byteLength,
897
+ );
898
+ if (byteRange) {
899
+ const body = content.subarray(byteRange.start, byteRange.end + 1);
900
+ headers["Content-Length"] = String(body.byteLength);
901
+ headers["Content-Range"] =
902
+ `bytes ${byteRange.start}-${byteRange.end}/${content.byteLength}`;
903
+ return new Response(body, {
904
+ status: 206,
905
+ headers,
906
+ });
907
+ }
908
+
909
+ return new Response(content, { headers });
910
+ } catch {
911
+ return new Response("Not found", { status: 404 });
912
+ }
913
+ },
914
+ });
915
+
916
+ console.log(`[Renderer] Static server on http://127.0.0.1:${port}`);
917
+ return `http://127.0.0.1:${port}`;
828
918
  }
829
919
 
830
920
  async function resolveRendererUrl(): Promise<string> {
831
- // Prefer ELIZA_RENDERER_URL / VITE_DEV_SERVER_URL when set (e.g. dev-platform.mjs watch mode).
832
- // Why: Vite HMR only works against the dev server; serving pre-built dist from this static
833
- // server would force a full rebuild for every UI change.
834
- let rendererUrl =
835
- process.env.ELIZA_RENDERER_URL ?? process.env.VITE_DEV_SERVER_URL ?? "";
836
-
837
- if (!rendererUrl) {
838
- rendererUrlPromise ??= startRendererServer();
839
- rendererUrl = await rendererUrlPromise;
840
- }
841
-
842
- if (!rendererUrl) {
843
- // Last resort: file:// (may have CORS issues with crossorigin module scripts)
844
- rendererUrl = `file://${path.join(resolveRendererAssetDir(import.meta.dir), "index.html")}`;
845
- logger.warn(
846
- "[Main] Falling back to file:// renderer URL — CORS issues possible",
847
- );
848
- }
849
-
850
- return rendererUrl;
921
+ // Prefer ELIZA_RENDERER_URL / VITE_DEV_SERVER_URL when set (e.g. dev-platform.mjs watch mode).
922
+ // Why: Vite HMR only works against the dev server; serving pre-built dist from this static
923
+ // server would force a full rebuild for every UI change.
924
+ let rendererUrl =
925
+ process.env.ELIZA_RENDERER_URL ?? process.env.VITE_DEV_SERVER_URL ?? "";
926
+
927
+ if (!rendererUrl) {
928
+ rendererUrlPromise ??= startRendererServer();
929
+ rendererUrl = await rendererUrlPromise;
930
+ }
931
+
932
+ if (!rendererUrl) {
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;
939
+ logger.warn(
940
+ "[Main] Falling back to file:// renderer URL — CORS issues possible",
941
+ );
942
+ }
943
+
944
+ return rendererUrl;
851
945
  }
852
946
 
853
947
  async function createMainWindow(rpc: ElizaDesktopRpc): Promise<BrowserWindow> {
854
- const rendererUrl = await resolveRendererUrl();
855
- const mainWindowPartition = resolveMainWindowPartition(process.env);
856
- if (mainWindowPartition) {
857
- logger.info(
858
- `[Main] Using isolated main window partition ${mainWindowPartition}`,
859
- );
860
- }
861
-
862
- const statePath = path.join(Utils.paths.userData, "window-state.json");
863
- const state = loadWindowState(statePath);
864
-
865
- let preload: string;
866
- try {
867
- preload = readResolvedPreloadScript(import.meta.dir);
868
- } catch (err) {
869
- logger.error(
870
- `[Main] Failed to read preload script: ${err instanceof Error ? err.message : String(err)}`,
871
- );
872
- preload = "// preload unavailable";
873
- }
874
-
875
- const windowFrame = {
876
- width: state.width,
877
- height: state.height,
878
- x: state.x,
879
- y: state.y,
880
- };
881
- const titleBarStyle =
882
- process.platform === "darwin" ? "hiddenInset" : "default";
883
- const transparent = process.platform === "darwin";
884
- const buildInfo = await BuildConfig.get();
885
- const forceMainWindowCef = shouldForceMainWindowCef(process.env);
886
- const canUseCefView = buildInfo.availableRenderers.includes("cef");
887
- const useIsolatedMainView =
888
- (process.platform === "win32" && mainWindowPartition) ||
889
- (forceMainWindowCef && canUseCefView && !!mainWindowPartition);
890
-
891
- if (forceMainWindowCef && !canUseCefView) {
892
- logger.warn(
893
- "[Main] ELIZA_DESKTOP_FORCE_CEF=1 requested, but this Electrobun build does not bundle the CEF renderer. Falling back to the native renderer.",
894
- );
895
- }
896
-
897
- let win: BrowserWindow;
898
- if (useIsolatedMainView) {
899
- // Shell window with the empty default webview. The actual content
900
- // (and therefore the RPC channel) is hosted on the separate mainView
901
- // BrowserView constructed below — that's what we attach `rpc` to.
902
- win = new BrowserWindow({
903
- title: BRAND.appName,
904
- // @ts-expect-error: Electrobun doesn't expose icon in JS typings yet
905
- icon: resolveDesktopAppIconPath(),
906
- url: null,
907
- preload: null,
908
- frame: windowFrame,
909
- renderer: resolveBootstrapShellRenderer(buildInfo),
910
- titleBarStyle,
911
- transparent,
912
- });
913
- win.webview.remove();
914
- const mainView = new BrowserView({
915
- url: rendererUrl,
916
- preload,
917
- renderer: forceMainWindowCef
918
- ? "cef"
919
- : resolveBootstrapViewRenderer(buildInfo),
920
- partition: mainWindowPartition,
921
- frame: {
922
- x: 0,
923
- y: 0,
924
- width: state.width,
925
- height: state.height,
926
- },
927
- windowId: win.id,
928
- rpc,
929
- });
930
- win.webviewId = mainView.id;
931
- if (forceMainWindowCef) {
932
- logger.info(
933
- `[Main] Using CEF main-window workaround with persistent partition ${mainWindowPartition}`,
934
- );
935
- }
936
- } else {
937
- win = new BrowserWindow({
938
- title: BRAND.appName,
939
- // @ts-expect-error: Electrobun doesn't expose icon in JS typings yet
940
- icon: resolveDesktopAppIconPath(),
941
- url: rendererUrl,
942
- preload,
943
- frame: windowFrame,
944
- titleBarStyle,
945
- transparent,
946
- rpc,
947
- });
948
- }
949
-
950
- applyMacOSWindowEffects(win);
951
- win.on("resize", () => scheduleStateSave(statePath, win));
952
- win.on("move", () => scheduleStateSave(statePath, win));
953
-
954
- // First-launch ergonomics: when there's no saved state (or the
955
- // saved state was garbage and we're falling back to defaults), open
956
- // the window maximized so the user gets a full workspace instead of
957
- // a 1440x900 rectangle in the corner they have to resize by hand.
958
- // Subsequent launches skip this because loadWindowState returns the
959
- // real persisted dimensions without the shouldMaximize sentinel.
960
- if (state.shouldMaximize === MAXIMIZE_ON_LAUNCH_SENTINEL) {
961
- try {
962
- (win as typeof win & { maximize?: () => void }).maximize?.();
963
- } catch (err) {
964
- // Non-fatal — if maximize() isn't available on this electrobun
965
- // build, the window still opens at the default dimensions.
966
- logger.warn(
967
- `[main-window] maximize() failed: ${err instanceof Error ? err.message : String(err)}`,
968
- );
969
- }
970
- }
971
-
972
- return win;
948
+ const kiosk = isKioskShellMode();
949
+ const rendererUrl = kiosk
950
+ ? appendKioskShellModeParam(await resolveRendererUrl())
951
+ : await resolveRendererUrl();
952
+ const buildInfo = await BuildConfig.get();
953
+ const mainWindowPartition = resolveMainWindowPartition(process.env, {
954
+ platform: process.platform,
955
+ buildInfo,
956
+ });
957
+ if (mainWindowPartition) {
958
+ logger.info(`[Main] Using main window partition ${mainWindowPartition}`);
959
+ }
960
+
961
+ const statePath = path.join(Utils.paths.userData, "window-state.json");
962
+ const state = loadWindowState(statePath);
963
+
964
+ let preload: string;
965
+ try {
966
+ preload = readResolvedPreloadScript(import.meta.dir);
967
+ } catch (err) {
968
+ // A missing/stale/empty preload means the main window has no API bridge —
969
+ // the renderer boots into a white screen. In packaged builds that is a
970
+ // fatal misbuild, so surface it (the error already names `build:preload`)
971
+ // instead of silently shipping a broken window. Keep the soft fallback in
972
+ // the dev runtime so an unbuilt preload doesn't block iterating on the UI.
973
+ if (isPackagedDesktopBuild()) {
974
+ throw err;
975
+ }
976
+ logger.error(
977
+ `[Main] Failed to read preload script (dev fallback): ${err instanceof Error ? err.message : String(err)}`,
978
+ );
979
+ preload = "// preload unavailable";
980
+ }
981
+
982
+ const windowFrame = {
983
+ width: state.width,
984
+ height: state.height,
985
+ x: state.x,
986
+ y: state.y,
987
+ };
988
+ const titleBarStyle = kiosk
989
+ ? "hidden"
990
+ : process.platform === "darwin"
991
+ ? "hiddenInset"
992
+ : "default";
993
+ const transparent = !kiosk && process.platform === "darwin";
994
+ const forceMainWindowCef = shouldForceMainWindowCef(
995
+ process.env,
996
+ process.platform,
997
+ );
998
+ const canUseCefView = buildInfo.availableRenderers.includes("cef");
999
+ const useIsolatedMainView = shouldUseIsolatedMainView({
1000
+ platform: process.platform,
1001
+ mainWindowPartition,
1002
+ forceMainWindowCef,
1003
+ buildInfo,
1004
+ });
1005
+
1006
+ if (forceMainWindowCef && !canUseCefView) {
1007
+ logger.warn(
1008
+ "[Main] ELIZA_DESKTOP_FORCE_CEF=1 requested, but this Electrobun build does not bundle the CEF renderer. Falling back to the native renderer.",
1009
+ );
1010
+ }
1011
+
1012
+ let win: BrowserWindow;
1013
+ if (useIsolatedMainView) {
1014
+ // Shell window with the empty default webview. The actual content
1015
+ // (and therefore the RPC channel) is hosted on the separate mainView
1016
+ // BrowserView constructed below — that's what we attach `rpc` to.
1017
+ win = createElectrobunBrowserWindow({
1018
+ title: BRAND.appName,
1019
+ icon: resolveDesktopAppIconPath(),
1020
+ url: null,
1021
+ preload: null,
1022
+ frame: windowFrame,
1023
+ renderer: resolveBootstrapShellRenderer(buildInfo),
1024
+ titleBarStyle,
1025
+ transparent,
1026
+ });
1027
+ win.webview.remove();
1028
+ const mainView = new BrowserView({
1029
+ url: rendererUrl,
1030
+ preload,
1031
+ renderer: forceMainWindowCef
1032
+ ? "cef"
1033
+ : resolveBootstrapViewRenderer(buildInfo),
1034
+ partition: mainWindowPartition,
1035
+ frame: {
1036
+ x: 0,
1037
+ y: 0,
1038
+ width: state.width,
1039
+ height: state.height,
1040
+ },
1041
+ windowId: win.id,
1042
+ rpc,
1043
+ });
1044
+ win.webviewId = mainView.id;
1045
+ if (forceMainWindowCef) {
1046
+ logger.info(
1047
+ `[Main] Using CEF main-window workaround with persistent partition ${mainWindowPartition}`,
1048
+ );
1049
+ }
1050
+ } else {
1051
+ win = createElectrobunBrowserWindow({
1052
+ title: BRAND.appName,
1053
+ icon: resolveDesktopAppIconPath(),
1054
+ url: rendererUrl,
1055
+ preload,
1056
+ frame: windowFrame,
1057
+ titleBarStyle,
1058
+ transparent,
1059
+ rpc,
1060
+ ...(mainWindowPartition ? { partition: mainWindowPartition } : {}),
1061
+ });
1062
+ }
1063
+
1064
+ // Kiosk mode: the app IS the GUI. Go fullscreen and skip the bounds
1065
+ // persistence + maximize ergonomics — the window is fixed fullscreen and
1066
+ // must never restore to a smaller frame.
1067
+ if (kiosk) {
1068
+ try {
1069
+ win.setFullScreen(true);
1070
+ } catch (err) {
1071
+ logger.warn(
1072
+ `[main-window] kiosk setFullScreen() failed: ${err instanceof Error ? err.message : String(err)}`,
1073
+ );
1074
+ }
1075
+ return win;
1076
+ }
1077
+
1078
+ applyMacOSWindowEffects(win);
1079
+ win.on("resize", () => scheduleStateSave(statePath, win));
1080
+ win.on("move", () => scheduleStateSave(statePath, win));
1081
+
1082
+ // First-launch ergonomics: when there's no saved state (or the
1083
+ // saved state was garbage and we're falling back to defaults), open
1084
+ // the window maximized so the user gets a full workspace instead of
1085
+ // a 1440x900 rectangle in the corner they have to resize by hand.
1086
+ // Subsequent launches skip this because loadWindowState returns the
1087
+ // real persisted dimensions without the shouldMaximize sentinel.
1088
+ if (state.shouldMaximize === MAXIMIZE_ON_LAUNCH_SENTINEL) {
1089
+ try {
1090
+ (win as typeof win & { maximize?: () => void }).maximize?.();
1091
+ } catch (err) {
1092
+ // Non-fatal — if maximize() isn't available on this electrobun
1093
+ // build, the window still opens at the default dimensions.
1094
+ logger.warn(
1095
+ `[main-window] maximize() failed: ${err instanceof Error ? err.message : String(err)}`,
1096
+ );
1097
+ }
1098
+ }
1099
+
1100
+ return win;
973
1101
  }
974
1102
 
975
1103
  function attachMainWindow(
976
- win: BrowserWindow,
977
- rpc: ElizaDesktopRpc,
978
- sendToWebview: SendToWebview,
1104
+ win: BrowserWindow,
1105
+ rpc: ElizaDesktopRpc,
1106
+ sendToWebview: SendToWebview,
979
1107
  ): BrowserWindow {
980
- wireMainWindowAfterCreate(win, rpc, sendToWebview);
981
- currentWindow = win;
982
- currentSendToWebview = sendToWebview;
983
- setCurrentMainWindow(win, {
984
- titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "default",
985
- transparent: process.platform === "darwin",
986
- });
987
- trackFocusedWindow(win);
988
-
989
- win.webview.on("dom-ready", () => {
990
- injectApiBase(win);
991
- });
992
-
993
- // Prevent the main webview from navigating to external URLs.
994
- // The renderer is always served from localhost — any other navigation
995
- // (e.g. from a compromised plugin) should open in the default browser.
996
- win.webview.on("will-navigate", (event: unknown) => {
997
- const e = event as {
998
- url?: string;
999
- data?: { detail?: string };
1000
- preventDefault?: () => void;
1001
- };
1002
- const url = readNavigationEventUrl(e);
1003
- try {
1004
- const parsed = new URL(url);
1005
- const isAllowed =
1006
- parsed.protocol === "file:" ||
1007
- parsed.hostname === "localhost" ||
1008
- parsed.hostname === "127.0.0.1" ||
1009
- parsed.protocol === "views:";
1010
- if (!isAllowed) {
1011
- e.preventDefault?.();
1012
- void import("electrobun/bun")
1013
- .then(({ Utils }) => {
1014
- try {
1015
- Utils.openExternal(url);
1016
- } catch {
1017
- // Ignore external open failures during navigation blocking.
1018
- }
1019
- })
1020
- .catch(() => {});
1021
- }
1022
- } catch {
1023
- // Unparseable URL — block it.
1024
- e.preventDefault?.();
1025
- }
1026
- });
1027
-
1028
- win.on("close", () => {
1029
- if (currentWindow?.id === win.id) {
1030
- currentWindow = null;
1031
- currentSendToWebview = null;
1032
- }
1033
- clearCurrentMainWindow(win);
1034
- getDesktopManager().clearMainWindow(win);
1035
-
1036
- if (!isQuitting) {
1037
- void ensureBackgroundWindow();
1038
- }
1039
- });
1040
-
1041
- return win;
1108
+ wireMainWindowAfterCreate(win, rpc, sendToWebview);
1109
+ currentWindow = win;
1110
+ currentSendToWebview = sendToWebview;
1111
+ setCurrentMainWindow(win, {
1112
+ titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "default",
1113
+ transparent: process.platform === "darwin",
1114
+ });
1115
+ trackFocusedWindow(win);
1116
+ // Reveal the Dock icon in tray-first mode whenever a main window is attached,
1117
+ // regardless of how it was opened (boot, tray "Show Window", Dock reopen, or
1118
+ // a direct restoreWindow() from a deep link that bypasses showWindow()).
1119
+ getDesktopManager().markMainWindowShown();
1120
+
1121
+ win.webview.on("dom-ready", () => {
1122
+ injectApiBase(win);
1123
+ });
1124
+
1125
+ // Prevent the main webview from navigating to external URLs.
1126
+ // The renderer is always served from localhost — any other navigation
1127
+ // (e.g. from a compromised plugin) should open in the default browser.
1128
+ win.webview.on("will-navigate", (event: unknown) => {
1129
+ const e = event as {
1130
+ url?: string;
1131
+ data?: { detail?: string };
1132
+ preventDefault?: () => void;
1133
+ };
1134
+ const url = readNavigationEventUrl(e);
1135
+ try {
1136
+ const parsed = new URL(url);
1137
+ const isAllowed =
1138
+ parsed.protocol === "file:" ||
1139
+ parsed.hostname === "localhost" ||
1140
+ parsed.hostname === "127.0.0.1" ||
1141
+ parsed.protocol === "views:";
1142
+ if (!isAllowed) {
1143
+ e.preventDefault?.();
1144
+ void import("electrobun/bun")
1145
+ .then(({ Utils }) => {
1146
+ try {
1147
+ Utils.openExternal(url);
1148
+ } catch {
1149
+ // Ignore external open failures during navigation blocking.
1150
+ }
1151
+ })
1152
+ .catch(() => {});
1153
+ }
1154
+ } catch {
1155
+ // Unparseable URL — block it.
1156
+ e.preventDefault?.();
1157
+ }
1158
+ });
1159
+
1160
+ win.on("close", (event: unknown) => {
1161
+ // Kiosk mode: the app is the entire GUI under a single-window compositor.
1162
+ // The window must never close — block every close request and keep it up.
1163
+ if (isKioskShellMode() && !isQuitting) {
1164
+ const closeEvent = event as { preventDefault?: () => void } | undefined;
1165
+ closeEvent?.preventDefault?.();
1166
+ logger.info("[Main] Kiosk window close blocked — staying fullscreen");
1167
+ return;
1168
+ }
1169
+
1170
+ // On Linux with no tray configured, minimizing-to-tray (or running in the
1171
+ // background) strands an invisible process: there is no dock reopen like
1172
+ // macOS and no StatusNotifier item to restore from. Quit cleanly in that
1173
+ // case so closing the last window can't leave the agent unreachable. The
1174
+ // tray decision is read from the environment up front — not from a flag set
1175
+ // after createTray() resolves — so closing during startup, before the tray
1176
+ // icon appears, doesn't spuriously quit. macOS/Windows and the
1177
+ // Linux-with-tray path keep their existing behavior.
1178
+ if (
1179
+ !isQuitting &&
1180
+ process.platform === "linux" &&
1181
+ !shouldCreateDesktopTray(process.env)
1182
+ ) {
1183
+ logger.info(
1184
+ "[Main] Window close on Linux with no tray — quitting (no surface to restore from)",
1185
+ );
1186
+ void requestAppQuit();
1187
+ return;
1188
+ }
1189
+
1190
+ if (!isQuitting && process.env.ELIZAOS_CLOSE_MINIMIZES_TO_TRAY !== "0") {
1191
+ const closeEvent = event as { preventDefault?: () => void } | undefined;
1192
+ if (typeof closeEvent?.preventDefault === "function") {
1193
+ closeEvent.preventDefault();
1194
+ void getDesktopManager()
1195
+ .hideWindow()
1196
+ .catch((err: unknown) => {
1197
+ logger.warn(
1198
+ `[Main] Failed to minimize window on close: ${err instanceof Error ? err.message : String(err)}`,
1199
+ );
1200
+ });
1201
+ logger.info("[Main] Window close requested - minimized to tray");
1202
+ showBackgroundRunNoticeOnce();
1203
+ return;
1204
+ }
1205
+ logger.info(
1206
+ "[Main] Window close requested - agent continues in background",
1207
+ );
1208
+ }
1209
+
1210
+ if (currentWindow?.id === win.id) {
1211
+ currentWindow = null;
1212
+ currentSendToWebview = null;
1213
+ }
1214
+ clearCurrentMainWindow(win);
1215
+ getDesktopManager().clearMainWindow(win);
1216
+
1217
+ if (!isQuitting) {
1218
+ void ensureBackgroundWindow();
1219
+ }
1220
+ });
1221
+
1222
+ return win;
1042
1223
  }
1043
1224
 
1044
1225
  async function ensureBackgroundWindow(): Promise<void> {
1045
- if (isQuitting || currentWindow) {
1046
- return;
1047
- }
1048
-
1049
- // Don't recreate the window — just keep the process alive in the
1050
- // background (exitOnLastWindowClosed is false in electrobun.config.ts).
1051
- // The dock icon click fires the "reopen" event which restores the window.
1052
- logger.info("[Main] Window closed — agent continues in background");
1053
- showBackgroundRunNoticeOnce();
1226
+ if (isQuitting || currentWindow) {
1227
+ return;
1228
+ }
1229
+
1230
+ // Don't recreate the window — just keep the process alive in the
1231
+ // background (exitOnLastWindowClosed is false in electrobun.config.ts).
1232
+ // The dock icon click fires the "reopen" event which restores the window.
1233
+ logger.info("[Main] Window closed — agent continues in background");
1234
+ showBackgroundRunNoticeOnce();
1235
+ }
1236
+
1237
+ /**
1238
+ * Create — or focus, if already open — the transparent onboarding overlay
1239
+ * window and wire its API base. Shared by the first-run boot branch and the
1240
+ * dock-reopen path so the overlay (not the dashboard) is always the surface
1241
+ * shown while onboarding is the active first-run mode.
1242
+ */
1243
+ async function openOnboardingOverlayWindow(): Promise<BrowserWindow> {
1244
+ const existing = getOnboardingOverlayWindow();
1245
+ if (existing) {
1246
+ try {
1247
+ existing.focus();
1248
+ } catch {
1249
+ // focus may be unavailable on this platform
1250
+ }
1251
+ return existing;
1252
+ }
1253
+ const { rpc } = createDesktopRpc("onboarding-overlay");
1254
+ const rendererUrl = await resolveRendererUrl();
1255
+ let preload = "";
1256
+ try {
1257
+ preload = readResolvedPreloadScript(import.meta.dir);
1258
+ } catch {
1259
+ // Dev fallback — an unbuilt preload should not block the overlay.
1260
+ }
1261
+ const win = createOnboardingOverlayWindow({ rendererUrl, preload, rpc });
1262
+ win.webview.on("dom-ready", () => {
1263
+ injectApiBase(win);
1264
+ });
1265
+
1266
+ // When the overlay closes (renderer calls window.close() after the first-run
1267
+ // API completes), transition to the main dashboard window. Without this the
1268
+ // overlay disappears but no dashboard appears.
1269
+ win.on("close", () => {
1270
+ logger.info(
1271
+ "[Main] Onboarding overlay closed — creating main dashboard window",
1272
+ );
1273
+ void (async () => {
1274
+ try {
1275
+ const { rpc: mainRpc, sendToWebview: mainSendToWebview } =
1276
+ createDesktopRpc("main");
1277
+ attachMainWindow(
1278
+ await createMainWindow(mainRpc),
1279
+ mainRpc,
1280
+ mainSendToWebview,
1281
+ );
1282
+ } catch (err) {
1283
+ logger.error(
1284
+ `[Main] Failed to create dashboard after overlay close: ${err instanceof Error ? err.message : String(err)}`,
1285
+ );
1286
+ }
1287
+ })();
1288
+ });
1289
+
1290
+ return win;
1054
1291
  }
1055
1292
 
1056
1293
  /** Restore or recreate the main window (called on dock icon click). */
1057
1294
  async function restoreWindow(): Promise<void> {
1058
- if (currentWindow) {
1059
- try {
1060
- currentWindow.unminimize();
1061
- currentWindow.focus();
1062
- } catch {
1063
- // unminimize/focus may not be available
1064
- }
1065
- return;
1066
- }
1067
- if (backgroundWindowPromise) {
1068
- await backgroundWindowPromise;
1069
- return;
1070
- }
1071
- backgroundWindowPromise = (async () => {
1072
- const { rpc, sendToWebview } = createDesktopRpc("main");
1073
- const win = attachMainWindow(
1074
- await createMainWindow(rpc),
1075
- rpc,
1076
- sendToWebview,
1077
- );
1078
- injectApiBase(win);
1079
- logger.info("[Main] Restored window from dock click");
1080
- })().finally(() => {
1081
- backgroundWindowPromise = null;
1082
- });
1083
- await backgroundWindowPromise;
1295
+ if (currentWindow) {
1296
+ try {
1297
+ currentWindow.unminimize();
1298
+ currentWindow.focus();
1299
+ } catch {
1300
+ // unminimize/focus may not be available
1301
+ }
1302
+ // Re-reveal the Dock icon for an already-open window (tray-first only).
1303
+ getDesktopManager().markMainWindowShown();
1304
+ return;
1305
+ }
1306
+ // Onboarding-overlay mode: the correct first-run surface is the transparent
1307
+ // overlay card, NOT the opaque dashboard. Re-show (or recreate) the overlay
1308
+ // instead of building a main window. Once onboarding completes and a
1309
+ // dashboard is attached, `currentWindow` is set and the branch above wins.
1310
+ if (shouldStartOnboardingOverlay()) {
1311
+ await openOnboardingOverlayWindow();
1312
+ logger.info("[Main] Reopened onboarding overlay (dock reopen)");
1313
+ return;
1314
+ }
1315
+ if (backgroundWindowPromise) {
1316
+ await backgroundWindowPromise;
1317
+ return;
1318
+ }
1319
+ backgroundWindowPromise = (async () => {
1320
+ const { rpc, sendToWebview } = createDesktopRpc("main");
1321
+ const win = attachMainWindow(
1322
+ await createMainWindow(rpc),
1323
+ rpc,
1324
+ sendToWebview,
1325
+ );
1326
+ injectApiBase(win);
1327
+ logger.info("[Main] Restored window from dock click");
1328
+ })().finally(() => {
1329
+ backgroundWindowPromise = null;
1330
+ });
1331
+ await backgroundWindowPromise;
1084
1332
  }
1085
1333
 
1086
1334
  function showBackgroundRunNoticeOnce(): void {
1087
- try {
1088
- showBackgroundNoticeOnce({
1089
- fileSystem: fs,
1090
- userDataDir: Utils.paths.userData,
1091
- showNotification: (options) => {
1092
- Utils.showNotification(options);
1093
- },
1094
- });
1095
- } catch (error) {
1096
- logger.warn(
1097
- `[Main] Failed to persist background notice marker: ${error instanceof Error ? error.message : String(error)}`,
1098
- );
1099
- }
1335
+ try {
1336
+ showBackgroundNoticeOnce({
1337
+ fileSystem: fs,
1338
+ userDataDir: Utils.paths.userData,
1339
+ showNotification: (options) => {
1340
+ Utils.showNotification(options);
1341
+ },
1342
+ });
1343
+ } catch (error) {
1344
+ logger.warn(
1345
+ `[Main] Failed to persist background notice marker: ${error instanceof Error ? error.message : String(error)}`,
1346
+ );
1347
+ }
1100
1348
  }
1101
1349
 
1102
1350
  async function createSettingsWindow(tabHint?: string): Promise<void> {
1103
- if (!surfaceWindowManager) return;
1104
- await surfaceWindowManager.openSettingsWindow(tabHint);
1351
+ if (!surfaceWindowManager) return;
1352
+ await surfaceWindowManager.openSettingsWindow(tabHint);
1105
1353
  }
1106
1354
 
1107
1355
  async function showMainSurface(surface: string): Promise<void> {
1108
- if (!currentWindow) {
1109
- await restoreWindow();
1110
- }
1111
- void getDesktopManager().showWindow();
1112
- sendToActiveRenderer("desktopTrayMenuClick", {
1113
- itemId: `show-main:${surface}`,
1114
- });
1356
+ if (!currentWindow) {
1357
+ await restoreWindow();
1358
+ }
1359
+ void getDesktopManager().showWindow();
1360
+ sendToActiveRenderer("desktopTrayMenuClick", {
1361
+ itemId: `show-main:${surface}`,
1362
+ });
1115
1363
  }
1116
1364
 
1117
1365
  function resolveDefaultDialogPath(): string {
1118
- const downloadsPath = path.join(os.homedir(), "Downloads");
1119
- return fs.existsSync(downloadsPath) ? downloadsPath : os.homedir();
1366
+ const downloadsPath = path.join(os.homedir(), "Downloads");
1367
+ return fs.existsSync(downloadsPath) ? downloadsPath : os.homedir();
1120
1368
  }
1121
1369
 
1122
1370
  async function exportConfigFromMenu(): Promise<void> {
1123
- const apiBase = resolveLoopbackApiBase();
1124
- if (!apiBase) {
1125
- Utils.showNotification({
1126
- title: "Config Export Failed",
1127
- body: "Agent unavailable",
1128
- });
1129
- return;
1130
- }
1131
-
1132
- try {
1133
- const response = await fetch(`${apiBase}/api/config`, {
1134
- headers: buildApiRequestHeaders(),
1135
- });
1136
- if (!response.ok) {
1137
- throw new Error(`Config fetch failed (${response.status})`);
1138
- }
1139
-
1140
- const config = await response.json();
1141
- const dialog = await getDesktopManager().showSaveDialog({
1142
- defaultPath: resolveDefaultDialogPath(),
1143
- allowedFileTypes: "json",
1144
- });
1145
- if (dialog.canceled || dialog.filePaths.length === 0) {
1146
- return;
1147
- }
1148
-
1149
- const outputPath = path.join(dialog.filePaths[0], CONFIG_EXPORT_FILE_NAME);
1150
- fs.writeFileSync(
1151
- outputPath,
1152
- `${JSON.stringify(config, null, 2)}\n`,
1153
- "utf8",
1154
- );
1155
-
1156
- Utils.showNotification({
1157
- title: "Config Exported",
1158
- body: `Saved to ${outputPath}`,
1159
- });
1160
- } catch (error) {
1161
- Utils.showNotification({
1162
- title: "Config Export Failed",
1163
- body: summarizeDesktopActionError(error, "Config export failed"),
1164
- });
1165
- }
1371
+ const apiBase = resolveLoopbackApiBase();
1372
+ if (!apiBase) {
1373
+ Utils.showNotification({
1374
+ title: "Config Export Failed",
1375
+ body: "Agent unavailable",
1376
+ });
1377
+ return;
1378
+ }
1379
+
1380
+ try {
1381
+ const response = await fetch(`${apiBase}/api/config`, {
1382
+ headers: buildApiRequestHeaders(),
1383
+ });
1384
+ if (!response.ok) {
1385
+ throw new Error(`Config fetch failed (${response.status})`);
1386
+ }
1387
+
1388
+ const config = await response.json();
1389
+ const dialog = await getDesktopManager().showSaveDialog({
1390
+ defaultPath: resolveDefaultDialogPath(),
1391
+ allowedFileTypes: "json",
1392
+ });
1393
+ if (dialog.canceled || dialog.filePaths.length === 0) {
1394
+ return;
1395
+ }
1396
+
1397
+ const outputPath = path.join(dialog.filePaths[0], CONFIG_EXPORT_FILE_NAME);
1398
+ fs.writeFileSync(
1399
+ outputPath,
1400
+ `${JSON.stringify(config, null, 2)}\n`,
1401
+ "utf8",
1402
+ );
1403
+
1404
+ Utils.showNotification({
1405
+ title: "Config Exported",
1406
+ body: `Saved to ${outputPath}`,
1407
+ });
1408
+ } catch (error) {
1409
+ Utils.showNotification({
1410
+ title: "Config Export Failed",
1411
+ body: summarizeDesktopActionError(error, "Config export failed"),
1412
+ });
1413
+ }
1166
1414
  }
1167
1415
 
1168
1416
  async function importConfigFromMenu(): Promise<void> {
1169
- const apiBase = resolveLoopbackApiBase();
1170
- if (!apiBase) {
1171
- Utils.showNotification({
1172
- title: "Config Import Failed",
1173
- body: "Agent unavailable",
1174
- });
1175
- return;
1176
- }
1177
-
1178
- try {
1179
- const dialog = await getDesktopManager().showOpenDialog({
1180
- defaultPath: resolveDefaultDialogPath(),
1181
- allowedFileTypes: "json",
1182
- canChooseFiles: true,
1183
- canChooseDirectory: false,
1184
- allowsMultipleSelection: false,
1185
- });
1186
- if (dialog.canceled || dialog.filePaths.length === 0) {
1187
- return;
1188
- }
1189
-
1190
- const inputPath = dialog.filePaths[0];
1191
- const rawConfig = fs.readFileSync(inputPath, "utf8");
1192
- const parsedConfig = JSON.parse(rawConfig) as unknown;
1193
- if (
1194
- typeof parsedConfig !== "object" ||
1195
- parsedConfig === null ||
1196
- Array.isArray(parsedConfig)
1197
- ) {
1198
- throw new Error("Config file must contain a JSON object");
1199
- }
1200
-
1201
- const response = await fetch(`${apiBase}/api/config`, {
1202
- method: "PUT",
1203
- headers: buildApiRequestHeaders("application/json"),
1204
- body: JSON.stringify(parsedConfig),
1205
- });
1206
- if (!response.ok) {
1207
- throw new Error(`Config import failed (${response.status})`);
1208
- }
1209
-
1210
- Utils.showNotification({
1211
- title: "Config Imported",
1212
- body: `Loaded ${path.basename(inputPath)}`,
1213
- });
1214
- } catch (error) {
1215
- Utils.showNotification({
1216
- title: "Config Import Failed",
1217
- body: summarizeDesktopActionError(error, "Config import failed"),
1218
- });
1219
- }
1417
+ const apiBase = resolveLoopbackApiBase();
1418
+ if (!apiBase) {
1419
+ Utils.showNotification({
1420
+ title: "Config Import Failed",
1421
+ body: "Agent unavailable",
1422
+ });
1423
+ return;
1424
+ }
1425
+
1426
+ try {
1427
+ const dialog = await getDesktopManager().showOpenDialog({
1428
+ defaultPath: resolveDefaultDialogPath(),
1429
+ allowedFileTypes: "json",
1430
+ canChooseFiles: true,
1431
+ canChooseDirectory: false,
1432
+ allowsMultipleSelection: false,
1433
+ });
1434
+ if (dialog.canceled || dialog.filePaths.length === 0) {
1435
+ return;
1436
+ }
1437
+
1438
+ const inputPath = dialog.filePaths[0];
1439
+ const rawConfig = fs.readFileSync(inputPath, "utf8");
1440
+ const parsedConfig = JSON.parse(rawConfig) as unknown;
1441
+ if (
1442
+ typeof parsedConfig !== "object" ||
1443
+ parsedConfig === null ||
1444
+ Array.isArray(parsedConfig)
1445
+ ) {
1446
+ throw new Error("Config file must contain a JSON object");
1447
+ }
1448
+
1449
+ const response = await fetch(`${apiBase}/api/config`, {
1450
+ method: "PUT",
1451
+ headers: buildApiRequestHeaders("application/json"),
1452
+ body: JSON.stringify(parsedConfig),
1453
+ });
1454
+ if (!response.ok) {
1455
+ throw new Error(`Config import failed (${response.status})`);
1456
+ }
1457
+
1458
+ Utils.showNotification({
1459
+ title: "Config Imported",
1460
+ body: `Loaded ${path.basename(inputPath)}`,
1461
+ });
1462
+ } catch (error) {
1463
+ Utils.showNotification({
1464
+ title: "Config Import Failed",
1465
+ body: summarizeDesktopActionError(error, "Config import failed"),
1466
+ });
1467
+ }
1220
1468
  }
1221
1469
 
1222
1470
  function trackFocusedWindow(window: ManagedWindowLike): void {
1223
- lastFocusedWindow = window;
1224
- window.on("focus", () => {
1225
- lastFocusedWindow = window;
1226
- const windowId = (window as { id?: number }).id;
1227
- if (
1228
- process.platform === "darwin" &&
1229
- typeof windowId === "number" &&
1230
- macOpenedDevtoolsWindowIds.has(windowId)
1231
- ) {
1232
- scheduleDevtoolsLayoutRefresh(
1233
- window as Parameters<typeof scheduleDevtoolsLayoutRefresh>[0],
1234
- );
1235
- }
1236
- });
1237
- window.on("close", () => {
1238
- const windowId = (window as { id?: number }).id;
1239
- if (typeof windowId === "number") {
1240
- macOpenedDevtoolsWindowIds.delete(windowId);
1241
- }
1242
- });
1471
+ lastFocusedWindow = window;
1472
+ window.on("focus", () => {
1473
+ lastFocusedWindow = window;
1474
+ const windowId = (window as { id?: number }).id;
1475
+ if (
1476
+ process.platform === "darwin" &&
1477
+ typeof windowId === "number" &&
1478
+ macOpenedDevtoolsWindowIds.has(windowId)
1479
+ ) {
1480
+ scheduleDevtoolsLayoutRefresh(
1481
+ window as Parameters<typeof scheduleDevtoolsLayoutRefresh>[0],
1482
+ );
1483
+ }
1484
+ });
1485
+ window.on("close", () => {
1486
+ const windowId = (window as { id?: number }).id;
1487
+ if (typeof windowId === "number") {
1488
+ macOpenedDevtoolsWindowIds.delete(windowId);
1489
+ }
1490
+ });
1243
1491
  }
1244
1492
 
1245
1493
  function toggleFocusedWindowDevTools(): void {
1246
- const targetWindow = lastFocusedWindow ?? currentWindow;
1247
- const webview = targetWindow?.webview as
1248
- | {
1249
- toggleDevTools?: () => void;
1250
- openDevTools?: () => void;
1251
- }
1252
- | undefined;
1253
-
1254
- if (shouldUseBrowserDevtoolsFallback()) {
1255
- void openBrowserDevtoolsFallback(targetWindow);
1256
- return;
1257
- }
1258
-
1259
- if (typeof webview?.toggleDevTools === "function") {
1260
- webview.toggleDevTools();
1261
- scheduleDevtoolsLayoutRefresh(
1262
- targetWindow as Parameters<typeof scheduleDevtoolsLayoutRefresh>[0],
1263
- );
1264
- return;
1265
- }
1266
-
1267
- if (typeof webview?.openDevTools === "function") {
1268
- webview.openDevTools();
1269
- scheduleDevtoolsLayoutRefresh(
1270
- targetWindow as Parameters<typeof scheduleDevtoolsLayoutRefresh>[0],
1271
- );
1272
- return;
1273
- }
1274
-
1275
- Utils.showNotification({
1276
- title: "Developer Tools Unavailable",
1277
- body: "The focused window does not expose Electrobun devtools controls.",
1278
- });
1494
+ const targetWindow = lastFocusedWindow ?? currentWindow;
1495
+ const webview = targetWindow?.webview as
1496
+ | {
1497
+ toggleDevTools?: () => void;
1498
+ openDevTools?: () => void;
1499
+ }
1500
+ | undefined;
1501
+
1502
+ if (shouldUseBrowserDevtoolsFallback()) {
1503
+ void openBrowserDevtoolsFallback(targetWindow);
1504
+ return;
1505
+ }
1506
+
1507
+ if (typeof webview?.toggleDevTools === "function") {
1508
+ webview.toggleDevTools();
1509
+ scheduleDevtoolsLayoutRefresh(
1510
+ targetWindow as Parameters<typeof scheduleDevtoolsLayoutRefresh>[0],
1511
+ );
1512
+ return;
1513
+ }
1514
+
1515
+ if (typeof webview?.openDevTools === "function") {
1516
+ webview.openDevTools();
1517
+ scheduleDevtoolsLayoutRefresh(
1518
+ targetWindow as Parameters<typeof scheduleDevtoolsLayoutRefresh>[0],
1519
+ );
1520
+ return;
1521
+ }
1522
+
1523
+ Utils.showNotification({
1524
+ title: "Developer Tools Unavailable",
1525
+ body: "The focused window does not expose Electrobun devtools controls.",
1526
+ });
1279
1527
  }
1280
1528
 
1281
1529
  /**
@@ -1284,7 +1532,7 @@ function toggleFocusedWindowDevTools(): void {
1284
1532
  * and `send` proxies.
1285
1533
  */
1286
1534
  type ElizaDesktopRpc = ReturnType<
1287
- typeof BrowserView.defineRPC<ElizaDesktopRPCSchema>
1535
+ typeof BrowserView.defineRPC<ElizaDesktopRPCSchema>
1288
1536
  >;
1289
1537
 
1290
1538
  /**
@@ -1315,49 +1563,51 @@ const MAX_RPC_REQUEST_TIME_MS = 600_000;
1315
1563
  * main / settings / surface windows are distinguishable.
1316
1564
  */
1317
1565
  function createDesktopRpc(label: string): {
1318
- rpc: ElizaDesktopRpc;
1319
- sendToWebview: SendToWebview;
1566
+ rpc: ElizaDesktopRpc;
1567
+ sendToWebview: SendToWebview;
1320
1568
  } {
1321
- let rpc: ElizaDesktopRpc | undefined;
1322
-
1323
- const sendToWebview: SendToWebview = (message, payload) => {
1324
- if (!rpc) {
1325
- logger.warn(
1326
- `[sendToWebview:${label}] RPC not yet initialised; dropping message: ${message}`,
1327
- );
1328
- return;
1329
- }
1330
- try {
1331
- // `rpc.send` is a Proxy<sendFn> from defineElectrobunRPC: both
1332
- // `rpc.send(message, payload)` and `rpc.send.<message>(payload)`
1333
- // dispatch through the same underlying sendFn. Cast to a plain
1334
- // function signature to call it dynamically by name without the
1335
- // schema-typed overloads narrowing the message string.
1336
- (rpc.send as unknown as (m: string, p?: unknown) => void)(
1337
- message,
1338
- payload ?? null,
1339
- );
1340
- } catch (err) {
1341
- logger.warn(
1342
- `[sendToWebview:${label}] send(${message}) failed: ${err instanceof Error ? err.message : String(err)}`,
1343
- );
1344
- }
1345
- };
1346
-
1347
- type BunRpcRequestsHandlers = NonNullable<
1348
- Parameters<typeof BrowserView.defineRPC<ElizaDesktopRPCSchema>>[0]["handlers"]
1349
- >["requests"];
1350
-
1351
- rpc = BrowserView.defineRPC<ElizaDesktopRPCSchema>({
1352
- maxRequestTime: MAX_RPC_REQUEST_TIME_MS,
1353
- handlers: {
1354
- requests: buildBunRpcHandlers({
1355
- sendToWebview,
1356
- }) as BunRpcRequestsHandlers,
1357
- },
1358
- });
1359
-
1360
- return { rpc, sendToWebview };
1569
+ let rpc: ElizaDesktopRpc | undefined;
1570
+
1571
+ const sendToWebview: SendToWebview = (message, payload) => {
1572
+ if (!rpc) {
1573
+ logger.warn(
1574
+ `[sendToWebview:${label}] RPC not yet initialised; dropping message: ${message}`,
1575
+ );
1576
+ return;
1577
+ }
1578
+ try {
1579
+ // `rpc.send` is a Proxy<sendFn> from defineElectrobunRPC: both
1580
+ // `rpc.send(message, payload)` and `rpc.send.<message>(payload)`
1581
+ // dispatch through the same underlying sendFn. Cast to a plain
1582
+ // function signature to call it dynamically by name without the
1583
+ // schema-typed overloads narrowing the message string.
1584
+ (rpc.send as unknown as (m: string, p?: unknown) => void)(
1585
+ message,
1586
+ payload ?? null,
1587
+ );
1588
+ } catch (err) {
1589
+ logger.warn(
1590
+ `[sendToWebview:${label}] send(${message}) failed: ${err instanceof Error ? err.message : String(err)}`,
1591
+ );
1592
+ }
1593
+ };
1594
+
1595
+ type BunRpcRequestsHandlers = NonNullable<
1596
+ Parameters<
1597
+ typeof BrowserView.defineRPC<ElizaDesktopRPCSchema>
1598
+ >[0]["handlers"]
1599
+ >["requests"];
1600
+
1601
+ rpc = BrowserView.defineRPC<ElizaDesktopRPCSchema>({
1602
+ maxRequestTime: MAX_RPC_REQUEST_TIME_MS,
1603
+ handlers: {
1604
+ requests: buildBunRpcHandlers({
1605
+ sendToWebview,
1606
+ }) as BunRpcRequestsHandlers,
1607
+ },
1608
+ });
1609
+
1610
+ return { rpc, sendToWebview };
1361
1611
  }
1362
1612
 
1363
1613
  /**
@@ -1375,15 +1625,15 @@ function createDesktopRpc(label: string): {
1375
1625
  * - steward sidecar's send-to-webview is wired
1376
1626
  */
1377
1627
  function wireMainWindowAfterCreate(
1378
- win: BrowserWindow,
1379
- rpc: ElizaDesktopRpc,
1380
- sendToWebview: SendToWebview,
1628
+ win: BrowserWindow,
1629
+ rpc: ElizaDesktopRpc,
1630
+ sendToWebview: SendToWebview,
1381
1631
  ): void {
1382
- initializeNativeModules(win, sendToWebview);
1383
- setStewardSendToWebview(sendToWebview);
1384
- wireBrowserWorkspaceCaller({
1385
- request: rpc.request as unknown as RpcRequestProxy,
1386
- });
1632
+ initializeNativeModules(win, sendToWebview);
1633
+ setStewardSendToWebview(sendToWebview);
1634
+ wireBrowserWorkspaceCaller({
1635
+ request: rpc.request as unknown as RpcRequestProxy,
1636
+ });
1387
1637
  }
1388
1638
 
1389
1639
  /**
@@ -1396,47 +1646,80 @@ function wireMainWindowAfterCreate(
1396
1646
  * settings windows don't need most of the wiring.
1397
1647
  */
1398
1648
  function wireSettingsRpcAfterCreate(rpc: ElizaDesktopRpc): void {
1399
- wireBrowserWorkspaceCaller({
1400
- request: rpc.request as unknown as RpcRequestProxy,
1401
- });
1649
+ wireBrowserWorkspaceCaller({
1650
+ request: rpc.request as unknown as RpcRequestProxy,
1651
+ });
1402
1652
  }
1403
1653
 
1404
1654
  function injectApiBase(win: BrowserWindow): void {
1405
- const runtimeResolution = resolveDesktopRuntimeMode(
1406
- process.env as Record<string, string | undefined>,
1407
- );
1408
-
1409
- if (runtimeResolution.externalApi.invalidSources.length > 0) {
1410
- logger.warn(
1411
- `[Main] Invalid API base env vars: ${runtimeResolution.externalApi.invalidSources.join(", ")}`,
1412
- );
1413
- }
1414
-
1415
- if (
1416
- runtimeResolution.mode === "external" &&
1417
- runtimeResolution.externalApi.base
1418
- ) {
1419
- apiBaseOwner.notifyChange(
1420
- win,
1421
- runtimeResolution.externalApi.base,
1422
- resolveApiToken(process.env) ?? "",
1423
- );
1424
- setAgentReady(true);
1425
- return;
1426
- }
1427
-
1428
- const agent = getAgentManager();
1429
- const port = agent.getPort() ?? resolveDesktopApiPort(process.env);
1430
- const apiToken = configureDesktopLocalApiAuth();
1431
- apiBaseOwner.notifyChange(
1432
- win,
1433
- resolveRendererFacingApiBase(
1434
- process.env as Record<string, string | undefined>,
1435
- port,
1436
- ),
1437
- apiToken,
1438
- );
1439
- setAgentReady(true);
1655
+ const runtimeResolution = resolveDesktopRuntime();
1656
+
1657
+ if (runtimeResolution.externalApi.invalidSources.length > 0) {
1658
+ logger.warn(
1659
+ `[Main] Invalid API base env vars: ${runtimeResolution.externalApi.invalidSources.join(", ")}`,
1660
+ );
1661
+ }
1662
+
1663
+ if (
1664
+ runtimeResolution.mode === "external" &&
1665
+ runtimeResolution.externalApi.base
1666
+ ) {
1667
+ apiBaseOwner.notifyChange(
1668
+ win,
1669
+ runtimeResolution.externalApi.base,
1670
+ resolveApiToken(process.env) ?? "",
1671
+ );
1672
+ setAgentReady(true);
1673
+ return;
1674
+ }
1675
+
1676
+ const agent = getAgentManager();
1677
+ const port = agent.getPort() ?? resolveDesktopApiPort(process.env);
1678
+ const apiToken = configureDesktopLocalApiAuth();
1679
+ apiBaseOwner.notifyChange(
1680
+ win,
1681
+ resolveRendererFacingApiBase(
1682
+ process.env as Record<string, string | undefined>,
1683
+ port,
1684
+ ),
1685
+ apiToken,
1686
+ );
1687
+ setAgentReady(true);
1688
+ }
1689
+
1690
+ function injectApiBaseIntoOpenRendererWindows(): void {
1691
+ if (currentWindow) {
1692
+ injectApiBase(currentWindow);
1693
+ }
1694
+
1695
+ const pillWindow = getPillWindow();
1696
+ if (pillWindow && pillWindow !== currentWindow) {
1697
+ injectApiBase(pillWindow);
1698
+ }
1699
+
1700
+ surfaceWindowManager?.forEachWindow((w) => {
1701
+ injectApiBase(w as BrowserWindow);
1702
+ });
1703
+ }
1704
+
1705
+ /**
1706
+ * Snapshot of every currently-open renderer window the agent API base should
1707
+ * be pushed to. Mirrors the window set in injectApiBaseIntoOpenRendererWindows.
1708
+ * Returns an empty array when no window exists yet (headless boot).
1709
+ */
1710
+ function collectOpenRendererWindows(): BrowserWindow[] {
1711
+ const windows: BrowserWindow[] = [];
1712
+ if (currentWindow) {
1713
+ windows.push(currentWindow);
1714
+ }
1715
+ const pillWindow = getPillWindow();
1716
+ if (pillWindow && pillWindow !== currentWindow) {
1717
+ windows.push(pillWindow);
1718
+ }
1719
+ surfaceWindowManager?.forEachWindow((w) => {
1720
+ windows.push(w as BrowserWindow);
1721
+ });
1722
+ return windows;
1440
1723
  }
1441
1724
 
1442
1725
  /**
@@ -1444,332 +1727,394 @@ function injectApiBase(win: BrowserWindow): void {
1444
1727
  * PermissionsSection shows correct statuses and capability toggles unlock.
1445
1728
  */
1446
1729
  async function syncPermissionsToRestApi(
1447
- port: number,
1448
- startup = false,
1730
+ port: number,
1731
+ startup = false,
1449
1732
  ): Promise<void> {
1450
- try {
1451
- const permissions = await mergeRuntimePermissionStates(
1452
- port,
1453
- await getPermissionManager().checkAllPermissions(),
1454
- );
1455
- await fetch(`http://127.0.0.1:${port}/api/permissions/state`, {
1456
- method: "PUT",
1457
- headers: { "Content-Type": "application/json" },
1458
- body: JSON.stringify({ permissions, startup }),
1459
- });
1460
- } catch (err) {
1461
- logger.warn(
1462
- `[Main] Permission sync failed: ${err instanceof Error ? err.message : String(err)}`,
1463
- );
1464
- }
1733
+ try {
1734
+ const permissions = await mergeRuntimePermissionStates(
1735
+ port,
1736
+ await getPermissionManager().checkAllPermissions(),
1737
+ );
1738
+ await fetch(`http://127.0.0.1:${port}/api/permissions/state`, {
1739
+ method: "PUT",
1740
+ headers: { "Content-Type": "application/json" },
1741
+ body: JSON.stringify({ permissions, startup }),
1742
+ });
1743
+ } catch (err) {
1744
+ logger.warn(
1745
+ `[Main] Permission sync failed: ${err instanceof Error ? err.message : String(err)}`,
1746
+ );
1747
+ }
1465
1748
  }
1466
1749
 
1467
- async function _startAgent(win: BrowserWindow): Promise<void> {
1468
- const runtimeResolution = resolveDesktopRuntimeMode(
1469
- process.env as Record<string, string | undefined>,
1470
- );
1471
-
1472
- if (runtimeResolution.mode !== "local") {
1473
- logger.info(
1474
- `[Main] Skipping embedded agent startup (${runtimeResolution.mode} mode)`,
1475
- );
1476
- injectApiBase(win);
1477
- return;
1478
- }
1479
-
1480
- const agent = getAgentManager();
1481
- recordStartupPhase("autostart_requested", {
1482
- pid: process.pid,
1483
- exec_path: process.execPath,
1484
- bundle_path: resolveStartupBundlePath(process.execPath),
1485
- });
1486
-
1487
- try {
1488
- const status = await agent.start();
1489
-
1490
- if (status.state === "running" && status.port) {
1491
- const apiBase = `http://127.0.0.1:${status.port}`;
1492
- const rendererBase = resolveRendererFacingApiBase(
1493
- process.env as Record<string, string | undefined>,
1494
- status.port,
1495
- );
1496
- // Mint or reload the loopback desktop session and install the
1497
- // session+csrf cookies on the webview's cookie jar BEFORE we tell the
1498
- // renderer to start hitting /api. This is the desktop trust path: if
1499
- // the bridge succeeds, the renderer skips the login UI; if it fails,
1500
- // the renderer behaves like a remote browser (password-required).
1501
- await primeDesktopSessionAuth(apiBase, rendererBase);
1502
- const apiToken = resolveApiToken(process.env) ?? "";
1503
- apiBaseOwner.notifyChange(win, rendererBase, apiToken);
1504
- setAgentReady(true);
1505
- // Sync real OS permission states to the REST API so the renderer
1506
- // can display them and capability toggles can unlock.
1507
- // Pass startup=true so the backend skips scheduling a restart for
1508
- // capabilities that are being auto-enabled for the first time.
1509
- syncPermissionsToRestApi(status.port, true);
1510
- }
1511
- } catch (err) {
1512
- logger.error(
1513
- `[Main] Agent start failed: ${err instanceof Error ? err.message : String(err)}`,
1514
- );
1515
- }
1750
+ async function _startAgent(): Promise<void> {
1751
+ const runtimeResolution = resolveDesktopRuntime();
1752
+
1753
+ if (runtimeResolution.mode !== "local") {
1754
+ logger.info(
1755
+ `[Main] Skipping embedded agent startup (${runtimeResolution.mode} mode)`,
1756
+ );
1757
+ injectApiBaseIntoOpenRendererWindows();
1758
+ return;
1759
+ }
1760
+
1761
+ recordStartupPhase("autostart_requested", {
1762
+ pid: process.pid,
1763
+ exec_path: process.execPath,
1764
+ bundle_path: resolveStartupBundlePath(process.execPath),
1765
+ });
1766
+
1767
+ try {
1768
+ const remotePluginHost = getRemotePluginHost();
1769
+ remotePluginHost.startWorker("eliza.runtime");
1770
+ await remotePluginHost.invokeWorker({
1771
+ id: "eliza.runtime",
1772
+ method: "runtime.start",
1773
+ timeoutMs: getHealthPollTimeoutMs() + 5_000,
1774
+ });
1775
+ const status = getAgentManager().getStatus();
1776
+
1777
+ if (status.state === "running" && status.port) {
1778
+ const apiBase = `http://127.0.0.1:${status.port}`;
1779
+ const rendererBase = resolveRendererFacingApiBase(
1780
+ process.env as Record<string, string | undefined>,
1781
+ status.port,
1782
+ );
1783
+ // Mint or reload the loopback desktop session and install the
1784
+ // session+csrf cookies on the webview's cookie jar BEFORE we tell the
1785
+ // renderer to start hitting /api. This is the desktop trust path: if
1786
+ // the bridge succeeds, the renderer skips the login UI; if it fails,
1787
+ // the renderer behaves like a remote browser (password-required).
1788
+ await primeDesktopSessionAuth(apiBase, rendererBase);
1789
+ const apiToken = resolveApiToken(process.env) ?? "";
1790
+ // Set the source-of-truth API base FIRST (correct even with zero open
1791
+ // windows), then push to every open window.
1792
+ publishAgentApiBase(rendererBase, apiToken, collectOpenRendererWindows());
1793
+ setAgentReady(true);
1794
+ // Sync real OS permission states to the REST API so the renderer
1795
+ // can display them and capability toggles can unlock.
1796
+ // Pass startup=true so the backend skips scheduling a restart for
1797
+ // capabilities that are being auto-enabled for the first time.
1798
+ syncPermissionsToRestApi(status.port, true);
1799
+ }
1800
+ } catch (err) {
1801
+ logger.error(
1802
+ `[Main] Agent start failed: ${err instanceof Error ? err.message : String(err)}`,
1803
+ );
1804
+ }
1516
1805
  }
1517
1806
 
1518
1807
  async function setupUpdater(): Promise<void> {
1519
- const runUpdateCheck = async (notifyOnNoUpdate = false): Promise<void> => {
1520
- try {
1521
- const updaterState = await getDesktopManager().getUpdaterState();
1522
- if (!updaterState.canAutoUpdate) {
1523
- if (updaterState.autoUpdateDisabledReason) {
1524
- logger.info(
1525
- `[Updater] Skipping auto-update check: ${updaterState.autoUpdateDisabledReason}`,
1526
- );
1527
- if (notifyOnNoUpdate) {
1528
- Utils.showNotification({
1529
- title: "Updates Unavailable",
1530
- body: updaterState.autoUpdateDisabledReason,
1531
- });
1532
- }
1533
- }
1534
- return;
1535
- }
1536
-
1537
- const updateResult = await Updater.checkForUpdate();
1538
- if (updateResult?.updateAvailable) {
1539
- Updater.downloadUpdate().catch((err: unknown) => {
1540
- logger.warn(
1541
- `[Updater] Download failed: ${err instanceof Error ? err.message : String(err)}`,
1542
- );
1543
- });
1544
- return;
1545
- }
1546
-
1547
- if (notifyOnNoUpdate) {
1548
- Utils.showNotification({
1549
- title: `${BRAND.appName} Up To Date`,
1550
- body: "You already have the latest release installed.",
1551
- });
1552
- }
1553
- } catch (err) {
1554
- logger.warn(
1555
- `[Updater] Update check failed: ${err instanceof Error ? err.message : String(err)}`,
1556
- );
1557
- if (notifyOnNoUpdate) {
1558
- Utils.showNotification({
1559
- title: "Update Check Failed",
1560
- body: `${BRAND.appName} could not reach the update server.`,
1561
- });
1562
- }
1563
- }
1564
- };
1565
-
1566
- try {
1567
- // Subscribe to update status changes so we can notify the renderer
1568
- // at the right lifecycle points.
1569
- Updater.onStatusChange((entry: { status: string; message?: string }) => {
1570
- if (entry.status === "update-available") {
1571
- // checkForUpdate found a new version — notify renderer
1572
- const info = Updater.updateInfo();
1573
- sendToActiveRenderer("desktopUpdateAvailable", {
1574
- version: info.version,
1575
- });
1576
- } else if (entry.status === "download-complete") {
1577
- // downloadUpdate finished — update is ready to apply
1578
- const info = Updater.updateInfo();
1579
- sendToActiveRenderer("desktopUpdateReady", { version: info.version });
1580
- Utils.showNotification({
1581
- title: `${BRAND.appName} Update Ready`,
1582
- body: `Version ${info.version} is ready. Restart to apply.`,
1583
- });
1584
- }
1585
- });
1586
-
1587
- const triggerManualUpdateCheck = () => {
1588
- Utils.showNotification({
1589
- title: "Checking for Updates",
1590
- body: `${BRAND.appName} is checking for a newer release.`,
1591
- });
1592
- void runUpdateCheck(true);
1593
- };
1594
-
1595
- const handleApplicationMenuAction = async (
1596
- action: string | undefined,
1597
- ): Promise<void> => {
1598
- if (!currentWindow && shouldRestoreWindowBeforeMenuAction(action)) {
1599
- await restoreWindow();
1600
- }
1601
- if (action === "check-for-updates") {
1602
- triggerManualUpdateCheck();
1603
- } else if (action === "open-about") {
1604
- const updaterState = await getDesktopManager().getUpdaterState();
1605
- const version = updaterState.currentVersion || "unknown";
1606
- Utils.showNotification({
1607
- title: `About ${BRAND.appName}`,
1608
- body: `Version ${version} (${process.platform}/${process.arch})`,
1609
- });
1610
- void createSettingsWindow("updates");
1611
- } else if (action === "export-config") {
1612
- void exportConfigFromMenu();
1613
- } else if (action === "import-config") {
1614
- void importConfigFromMenu();
1615
- } else if (action === "toggle-devtools") {
1616
- toggleFocusedWindowDevTools();
1617
- } else if (action === "relaunch") {
1618
- void getDesktopManager().relaunch();
1619
- } else if (action === "reset-app") {
1620
- void resetTheAppFromApplicationMenu();
1621
- } else if (action === "open-secrets-manager") {
1622
- // The Secrets Storage modal lives in the renderer. Make sure
1623
- // the main window is visible, then notify the renderer to
1624
- // show the modal. The keyboard accelerator
1625
- // (⌘⌥⌃V on Mac / Ctrl+Alt+Shift+V on Win/Linux) flows
1626
- // through this same path; the renderer's `keydown` listener
1627
- // also dispatches the same toggle directly when a Eliza
1628
- // window is already focused.
1629
- void restoreWindow();
1630
- sendToActiveRenderer("openSecretsManager", {});
1631
- } else if (
1632
- action === "open-settings" ||
1633
- action?.startsWith("open-settings-")
1634
- ) {
1635
- void createSettingsWindow(parseSettingsWindowAction(action));
1636
- } else if (action?.startsWith("new-window:")) {
1637
- const surface = action.slice("new-window:".length);
1638
- if (surfaceWindowManager && isDetachedSurface(surface)) {
1639
- void surfaceWindowManager.openSurfaceWindow(surface);
1640
- }
1641
- } else if (action?.startsWith("focus-window:")) {
1642
- const windowId = action.slice("focus-window:".length);
1643
- surfaceWindowManager?.focusWindow(windowId);
1644
- } else if (action?.startsWith("show-main:")) {
1645
- const surface = action.slice("show-main:".length);
1646
- showMainSurface(surface);
1647
- } else if (action === "focus-main-window") {
1648
- void getDesktopManager().focusWindow();
1649
- } else if (action === "hide-main-window") {
1650
- void getDesktopManager().hideWindow();
1651
- } else if (action === "maximize-main-window") {
1652
- void getDesktopManager().maximizeWindow();
1653
- } else if (action === "restore-main-window") {
1654
- void getDesktopManager().unmaximizeWindow();
1655
- } else if (action === "desktop-notify") {
1656
- void getDesktopManager().showNotification({
1657
- title: `${BRAND.appName} Desktop`,
1658
- body: `${BRAND.appName} native application menu actions are wired and responding.`,
1659
- urgency: "normal",
1660
- });
1661
- } else if (action === "restart-steward") {
1662
- if (isStewardLocalEnabled()) {
1663
- restartSteward().catch((err: unknown) => {
1664
- logger.error(
1665
- `[Main] Steward restart failed: ${err instanceof Error ? err.message : String(err)}`,
1666
- );
1667
- Utils.showNotification({
1668
- title: "Steward Restart Failed",
1669
- body: err instanceof Error ? err.message : "Unknown error",
1670
- });
1671
- });
1672
- }
1673
- } else if (action === "reset-steward") {
1674
- if (isStewardLocalEnabled()) {
1675
- resetSteward().catch((err: unknown) => {
1676
- logger.error(
1677
- `[Main] Steward reset failed: ${err instanceof Error ? err.message : String(err)}`,
1678
- );
1679
- Utils.showNotification({
1680
- title: "Steward Reset Failed",
1681
- body: err instanceof Error ? err.message : "Unknown error",
1682
- });
1683
- });
1684
- }
1685
- } else if (
1686
- action?.startsWith("apps:") ||
1687
- action?.startsWith("tray-app-")
1688
- ) {
1689
- // Both shapes resolve to the same flow:
1690
- // 1. Look up the app entry by slug.
1691
- // 2. If the app declares hasDetailsPage, focus the main window
1692
- // and tell the renderer to navigate to /apps/<slug>/details
1693
- // (where the user can review config + click Launch).
1694
- // 3. Otherwise, open or focus its dedicated native window
1695
- // directly (zero-config viewers / overlays).
1696
- // WHY two prefixes: `apps:<slug>` is what `buildAppsMenu` emits
1697
- // for the OS menu bar; `tray-app-<slug>` is what the tray icons
1698
- // emit. Both arrive here.
1699
- const slug = action.startsWith("apps:")
1700
- ? action.slice("apps:".length)
1701
- : action.slice("tray-app-".length);
1702
- const entry = findAppMenuEntryBySlug(slug);
1703
- if (entry) {
1704
- if (entry.hasDetailsPage) {
1705
- // Restore main window first so the renderer route is visible.
1706
- void restoreWindow();
1707
- sendToActiveRenderer("desktopAppDetailsRequested", {
1708
- slug: entry.slug,
1709
- });
1710
- } else {
1711
- void getDesktopManager().openAppWindow({
1712
- slug: entry.slug,
1713
- title: entry.displayName,
1714
- path: entry.windowPath,
1715
- alwaysOnTop: false,
1716
- });
1717
- }
1718
- }
1719
- } else if (action === "restart-agent") {
1720
- getAgentManager()
1721
- .restart()
1722
- .catch((err: unknown) => {
1723
- logger.error(
1724
- `[Main] Agent restart failed: ${err instanceof Error ? err.message : String(err)}`,
1725
- );
1726
- });
1727
- } else if (action === "quit") {
1728
- void getDesktopManager().quit();
1729
- } else if (action === "show") {
1730
- void getDesktopManager().showWindow();
1731
- } else if (action?.startsWith("navigate-")) {
1732
- void getDesktopManager().showWindow();
1733
- sendToActiveRenderer("desktopTrayMenuClick", { itemId: action });
1734
- }
1735
- };
1736
-
1737
- setApplicationMenuActionHandler(handleApplicationMenuAction);
1738
-
1739
- Electrobun.events.on(
1740
- "application-menu-clicked",
1741
- (e: { data?: { action?: string } }) => {
1742
- void handleApplicationMenuAction(e?.data?.action);
1743
- },
1744
- );
1745
-
1746
- // Route tray app entries (`tray-app-<slug>`) into the same handler as the
1747
- // OS menu bar. WHY: the desktop manager forwards every tray click to the
1748
- // renderer, but spawning native windows must happen on the bun side.
1749
- Electrobun.events.on(
1750
- "tray-clicked",
1751
- (e: { data?: { action?: string } }) => {
1752
- const action = e?.data?.action;
1753
- if (typeof action === "string" && action.startsWith("tray-app-")) {
1754
- void handleApplicationMenuAction(action);
1755
- }
1756
- },
1757
- );
1758
-
1759
- Electrobun.events.on("context-menu-clicked", (action: string) => {
1760
- if (action === "check-for-updates") {
1761
- triggerManualUpdateCheck();
1762
- } else if (action === "relaunch") {
1763
- void getDesktopManager().relaunch();
1764
- }
1765
- });
1766
-
1767
- await runUpdateCheck(false);
1768
- } catch (err) {
1769
- logger.warn(
1770
- `[Updater] Update check failed: ${err instanceof Error ? err.message : String(err)}`,
1771
- );
1772
- }
1808
+ const runUpdateCheck = async (notifyOnNoUpdate = false): Promise<void> => {
1809
+ try {
1810
+ const updaterState = await getDesktopManager().getUpdaterState();
1811
+ if (!updaterState.canAutoUpdate) {
1812
+ if (updaterState.autoUpdateDisabledReason) {
1813
+ logger.info(
1814
+ `[Updater] Skipping auto-update check: ${updaterState.autoUpdateDisabledReason}`,
1815
+ );
1816
+ if (notifyOnNoUpdate) {
1817
+ Utils.showNotification({
1818
+ title: "Updates Unavailable",
1819
+ body: updaterState.autoUpdateDisabledReason,
1820
+ });
1821
+ }
1822
+ }
1823
+ return;
1824
+ }
1825
+
1826
+ const updateResult = await Updater.checkForUpdate();
1827
+ if (updateResult.updateAvailable) {
1828
+ Updater.downloadUpdate().catch((err: unknown) => {
1829
+ logger.warn(
1830
+ `[Updater] Download failed: ${err instanceof Error ? err.message : String(err)}`,
1831
+ );
1832
+ });
1833
+ return;
1834
+ }
1835
+
1836
+ if (notifyOnNoUpdate) {
1837
+ Utils.showNotification({
1838
+ title: `${BRAND.appName} Up To Date`,
1839
+ body: "You already have the latest release installed.",
1840
+ });
1841
+ }
1842
+ } catch (err) {
1843
+ logger.warn(
1844
+ `[Updater] Update check failed: ${err instanceof Error ? err.message : String(err)}`,
1845
+ );
1846
+ if (notifyOnNoUpdate) {
1847
+ Utils.showNotification({
1848
+ title: "Update Check Failed",
1849
+ body: `${BRAND.appName} could not reach the update server.`,
1850
+ });
1851
+ }
1852
+ }
1853
+ };
1854
+
1855
+ try {
1856
+ // Subscribe to update status changes so we can notify the renderer
1857
+ // at the right lifecycle points.
1858
+ Updater.onStatusChange((entry: { status: string; message?: string }) => {
1859
+ if (entry.status === "update-available") {
1860
+ // checkForUpdate found a new version — notify renderer
1861
+ const info = Updater.updateInfo();
1862
+ sendToActiveRenderer("desktopUpdateAvailable", {
1863
+ version: info.version,
1864
+ });
1865
+ } else if (entry.status === "download-complete") {
1866
+ // downloadUpdate finished — update is ready to apply
1867
+ const info = Updater.updateInfo();
1868
+ sendToActiveRenderer("desktopUpdateReady", { version: info.version });
1869
+ Utils.showNotification({
1870
+ title: `${BRAND.appName} Update Ready`,
1871
+ body: `Version ${info.version} is ready. Restart to apply.`,
1872
+ });
1873
+ }
1874
+ });
1875
+
1876
+ const triggerManualUpdateCheck = () => {
1877
+ Utils.showNotification({
1878
+ title: "Checking for Updates",
1879
+ body: `${BRAND.appName} is checking for a newer release.`,
1880
+ });
1881
+ void runUpdateCheck(true);
1882
+ };
1883
+
1884
+ const handleUpdateAndConfigMenuAction = async (
1885
+ action: string | undefined,
1886
+ ): Promise<boolean> => {
1887
+ if (action === "check-for-updates") {
1888
+ triggerManualUpdateCheck();
1889
+ return true;
1890
+ }
1891
+ if (action === "open-about") {
1892
+ const updaterState = await getDesktopManager().getUpdaterState();
1893
+ const version = updaterState.currentVersion || "unknown";
1894
+ Utils.showNotification({
1895
+ title: `About ${BRAND.appName}`,
1896
+ body: `Version ${version} (${process.platform}/${process.arch})`,
1897
+ });
1898
+ void createSettingsWindow("updates");
1899
+ return true;
1900
+ }
1901
+ if (action === "export-config") {
1902
+ void exportConfigFromMenu();
1903
+ return true;
1904
+ }
1905
+ if (action === "import-config") {
1906
+ void importConfigFromMenu();
1907
+ return true;
1908
+ }
1909
+ return false;
1910
+ };
1911
+
1912
+ const handleMainWindowMenuAction = (
1913
+ action: string | undefined,
1914
+ ): boolean => {
1915
+ if (action === "toggle-devtools") {
1916
+ toggleFocusedWindowDevTools();
1917
+ return true;
1918
+ }
1919
+ if (action === "focus-main-window") {
1920
+ void getDesktopManager().focusWindow();
1921
+ return true;
1922
+ }
1923
+ if (action === "hide-main-window") {
1924
+ void getDesktopManager().hideWindow();
1925
+ return true;
1926
+ }
1927
+ if (action === "maximize-main-window") {
1928
+ void getDesktopManager().maximizeWindow();
1929
+ return true;
1930
+ }
1931
+ if (action === "restore-main-window") {
1932
+ void getDesktopManager().unmaximizeWindow();
1933
+ return true;
1934
+ }
1935
+ if (action === "show") {
1936
+ void getDesktopManager().showWindow();
1937
+ return true;
1938
+ }
1939
+ return false;
1940
+ };
1941
+
1942
+ const handleSettingsMenuAction = (action: string | undefined): boolean => {
1943
+ if (action === "open-secrets-manager") {
1944
+ void restoreWindow();
1945
+ sendToActiveRenderer("openSecretsManager", {});
1946
+ return true;
1947
+ }
1948
+ if (action === "open-settings" || action?.startsWith("open-settings-")) {
1949
+ void createSettingsWindow(parseSettingsWindowAction(action));
1950
+ return true;
1951
+ }
1952
+ return false;
1953
+ };
1954
+
1955
+ const handleSurfaceMenuAction = (action: string | undefined): boolean => {
1956
+ if (action?.startsWith("new-window:")) {
1957
+ const surface = action.slice("new-window:".length);
1958
+ if (surfaceWindowManager && isDetachedSurface(surface)) {
1959
+ void surfaceWindowManager.openSurfaceWindow(surface);
1960
+ }
1961
+ return true;
1962
+ }
1963
+ if (action?.startsWith("focus-window:")) {
1964
+ const windowId = action.slice("focus-window:".length);
1965
+ surfaceWindowManager?.focusWindow(windowId);
1966
+ return true;
1967
+ }
1968
+ if (action?.startsWith("show-main:")) {
1969
+ showMainSurface(action.slice("show-main:".length));
1970
+ return true;
1971
+ }
1972
+ return false;
1973
+ };
1974
+
1975
+ const handleStewardMenuAction = (action: string | undefined): boolean => {
1976
+ if (action === "restart-steward" && isStewardLocalEnabled()) {
1977
+ restartSteward().catch((err: unknown) => {
1978
+ logger.error(
1979
+ `[Main] Steward restart failed: ${err instanceof Error ? err.message : String(err)}`,
1980
+ );
1981
+ Utils.showNotification({
1982
+ title: "Steward Restart Failed",
1983
+ body: err instanceof Error ? err.message : "Unknown error",
1984
+ });
1985
+ });
1986
+ return true;
1987
+ }
1988
+ if (action === "reset-steward" && isStewardLocalEnabled()) {
1989
+ resetSteward().catch((err: unknown) => {
1990
+ logger.error(
1991
+ `[Main] Steward reset failed: ${err instanceof Error ? err.message : String(err)}`,
1992
+ );
1993
+ Utils.showNotification({
1994
+ title: "Steward Reset Failed",
1995
+ body: err instanceof Error ? err.message : "Unknown error",
1996
+ });
1997
+ });
1998
+ return true;
1999
+ }
2000
+ return false;
2001
+ };
2002
+
2003
+ const handleAppEntryMenuAction = (action: string | undefined): boolean => {
2004
+ if (!action?.startsWith("apps:") && !action?.startsWith("tray-app-")) {
2005
+ return false;
2006
+ }
2007
+ const slug = action.startsWith("apps:")
2008
+ ? action.slice("apps:".length)
2009
+ : action.slice("tray-app-".length);
2010
+ const entry = findAppMenuEntryBySlug(slug);
2011
+ if (!entry) return true;
2012
+ if (entry.hasDetailsPage) {
2013
+ void restoreWindow();
2014
+ sendToActiveRenderer("desktopAppDetailsRequested", {
2015
+ slug: entry.slug,
2016
+ });
2017
+ return true;
2018
+ }
2019
+ void getDesktopManager().openAppWindow({
2020
+ slug: entry.slug,
2021
+ title: entry.displayName,
2022
+ path: entry.windowPath,
2023
+ alwaysOnTop: false,
2024
+ });
2025
+ return true;
2026
+ };
2027
+
2028
+ const handleRuntimeMenuAction = (action: string | undefined): boolean => {
2029
+ if (action === "relaunch") {
2030
+ void getDesktopManager().relaunch();
2031
+ return true;
2032
+ }
2033
+ if (action === "reset-app") {
2034
+ void resetTheAppFromApplicationMenu();
2035
+ return true;
2036
+ }
2037
+ if (action === "desktop-notify") {
2038
+ void getDesktopManager().showNotification({
2039
+ title: `${BRAND.appName} Desktop`,
2040
+ body: `${BRAND.appName} native application menu actions are wired and responding.`,
2041
+ urgency: "normal",
2042
+ });
2043
+ return true;
2044
+ }
2045
+ if (action === "restart-agent") {
2046
+ getAgentManager()
2047
+ .restart()
2048
+ .catch((err: unknown) => {
2049
+ logger.error(
2050
+ `[Main] Agent restart failed: ${err instanceof Error ? err.message : String(err)}`,
2051
+ );
2052
+ });
2053
+ return true;
2054
+ }
2055
+ if (action === "quit") {
2056
+ void getDesktopManager().quit();
2057
+ return true;
2058
+ }
2059
+ if (action?.startsWith("navigate-")) {
2060
+ void getDesktopManager().showWindow();
2061
+ sendToActiveRenderer("desktopTrayMenuClick", { itemId: action });
2062
+ return true;
2063
+ }
2064
+ return false;
2065
+ };
2066
+
2067
+ const handleApplicationMenuAction = async (
2068
+ action: string | undefined,
2069
+ ): Promise<void> => {
2070
+ if (!currentWindow && shouldRestoreWindowBeforeMenuAction(action)) {
2071
+ await restoreWindow();
2072
+ }
2073
+ if (await handleUpdateAndConfigMenuAction(action)) return;
2074
+ if (handleMainWindowMenuAction(action)) return;
2075
+ if (handleSettingsMenuAction(action)) return;
2076
+ if (handleSurfaceMenuAction(action)) return;
2077
+ if (handleStewardMenuAction(action)) return;
2078
+ if (handleAppEntryMenuAction(action)) return;
2079
+ handleRuntimeMenuAction(action);
2080
+ };
2081
+
2082
+ setApplicationMenuActionHandler(handleApplicationMenuAction);
2083
+
2084
+ Electrobun.events.on(
2085
+ "application-menu-clicked",
2086
+ (e: { data?: { action?: string } }) => {
2087
+ void handleApplicationMenuAction(e.data?.action);
2088
+ },
2089
+ );
2090
+
2091
+ // Route tray app entries (`tray-app-<slug>`) into the same handler as the
2092
+ // OS menu bar. WHY: the desktop manager forwards every tray click to the
2093
+ // renderer, but spawning native windows must happen on the bun side.
2094
+ Electrobun.events.on(
2095
+ "tray-clicked",
2096
+ (e: { data?: { action?: string } }) => {
2097
+ const action = e.data?.action;
2098
+ if (typeof action === "string" && action.startsWith("tray-app-")) {
2099
+ void handleApplicationMenuAction(action);
2100
+ }
2101
+ },
2102
+ );
2103
+
2104
+ Electrobun.events.on("context-menu-clicked", (action: string) => {
2105
+ if (action === "check-for-updates") {
2106
+ triggerManualUpdateCheck();
2107
+ } else if (action === "relaunch") {
2108
+ void getDesktopManager().relaunch();
2109
+ }
2110
+ });
2111
+
2112
+ await runUpdateCheck(false);
2113
+ } catch (err) {
2114
+ logger.warn(
2115
+ `[Updater] Update check failed: ${err instanceof Error ? err.message : String(err)}`,
2116
+ );
2117
+ }
1773
2118
  }
1774
2119
 
1775
2120
  /**
@@ -1782,126 +2127,174 @@ async function setupUpdater(): Promise<void> {
1782
2127
  * `urlSchemes`, sourced from `ELIZA_URL_SCHEME`) — this handler does not
1783
2128
  * care which scheme is used; it only routes by host + pathname.
1784
2129
  */
1785
- function handleDeepLink(url: string): void {
1786
- let parsed: URL;
1787
- try {
1788
- parsed = new URL(url);
1789
- } catch {
1790
- sendToActiveRenderer("shareTargetReceived", { url });
1791
- return;
1792
- }
1793
-
1794
- // `<scheme>://apps/<slug>` → URL parses host="apps", pathname="/<slug>"
1795
- if (parsed.host === "apps") {
1796
- const slug = parsed.pathname
1797
- .replace(/^\/+/, "")
1798
- .replace(/[?#].*$/, "")
1799
- .split("/")[0];
1800
- if (slug) {
1801
- const entry = findAppMenuEntryBySlug(slug);
1802
- if (entry) {
1803
- // Mirror the menu/tray handler: apps with a details page get a config
1804
- // review screen instead of a direct window so deep links and clicks
1805
- // produce identical UX.
1806
- if (entry.hasDetailsPage) {
1807
- void restoreWindow();
1808
- sendToActiveRenderer("desktopAppDetailsRequested", {
1809
- slug: entry.slug,
1810
- });
1811
- } else {
1812
- void getDesktopManager().openAppWindow({
1813
- slug: entry.slug,
1814
- title: entry.displayName,
1815
- path: entry.windowPath,
1816
- alwaysOnTop: false,
1817
- });
1818
- }
1819
- return;
1820
- }
1821
- }
1822
- }
1823
-
1824
- sendToActiveRenderer("shareTargetReceived", { url });
2130
+ async function handleDeepLink(url: string): Promise<void> {
2131
+ let parsed: URL;
2132
+ try {
2133
+ parsed = new URL(url);
2134
+ } catch {
2135
+ await forwardDeepLinkToRenderer(url);
2136
+ return;
2137
+ }
2138
+
2139
+ // `<scheme>://apps/<slug>` → URL parses host="apps", pathname="/<slug>"
2140
+ if (parsed.host === "apps") {
2141
+ const slug = parsed.pathname
2142
+ .replace(/^\/+/, "")
2143
+ .replace(/[?#].*$/, "")
2144
+ .split("/")[0];
2145
+ if (slug) {
2146
+ const entry = findAppMenuEntryBySlug(slug);
2147
+ if (entry) {
2148
+ // Mirror the menu/tray handler: apps with a details page get a config
2149
+ // review screen instead of a direct window so deep links and clicks
2150
+ // produce identical UX.
2151
+ if (entry.hasDetailsPage) {
2152
+ void restoreWindow();
2153
+ sendToActiveRenderer("desktopAppDetailsRequested", {
2154
+ slug: entry.slug,
2155
+ });
2156
+ } else {
2157
+ void getDesktopManager().openAppWindow({
2158
+ slug: entry.slug,
2159
+ title: entry.displayName,
2160
+ path: entry.windowPath,
2161
+ alwaysOnTop: false,
2162
+ });
2163
+ }
2164
+ return;
2165
+ }
2166
+ }
2167
+ }
2168
+
2169
+ await forwardDeepLinkToRenderer(url);
2170
+ }
2171
+
2172
+ async function forwardDeepLinkToRenderer(url: string): Promise<void> {
2173
+ await restoreWindow();
2174
+ // Assistant/Siri/Shortcuts links deliberately stay renderer-owned. LifeOps
2175
+ // requests must go through the normal chat/runtime planner, which persists
2176
+ // ScheduledTask records instead of creating native macOS-only state.
2177
+ sendToActiveRenderer("shareTargetReceived", { url });
1825
2178
  }
1826
2179
 
1827
2180
  function setupDeepLinks(): void {
1828
- Electrobun.events.on("open-url", (url: string) => {
1829
- handleDeepLink(url);
1830
- });
2181
+ Electrobun.events.on("open-url", (event: unknown) => {
2182
+ const url = readOpenUrlEventUrl(event);
2183
+ if (!url) {
2184
+ logger.warn("[Main] Ignoring open-url event without a URL payload");
2185
+ return;
2186
+ }
2187
+ void handleDeepLink(url);
2188
+ });
1831
2189
  }
1832
2190
 
1833
2191
  function setupDockReopen(): void {
1834
- Electrobun.events.on("reopen", () => {
1835
- void restoreWindow();
1836
- });
2192
+ Electrobun.events.on("reopen", () => {
2193
+ void restoreWindow();
2194
+ });
1837
2195
  }
1838
2196
 
1839
2197
  async function runShutdownCleanup(reason: string): Promise<void> {
1840
- logger.info(`[Main] App quitting (${reason}), disposing native modules...`);
1841
- isQuitting = true;
1842
- sendToActiveRenderer("desktopShutdownStarted", { reason });
1843
- for (const cleanupFn of cleanupFns) {
1844
- await Promise.resolve(cleanupFn());
1845
- }
1846
- await disposeNativeModules();
2198
+ if (shutdownCleanupPromise) {
2199
+ return shutdownCleanupPromise;
2200
+ }
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;
1847
2233
  }
1848
2234
 
1849
2235
  function setupShutdown(): void {
1850
- Electrobun.events.on("before-quit", () => {
1851
- void runShutdownCleanup("before-quit");
1852
- });
2236
+ Electrobun.events.on("before-quit", () => {
2237
+ void runShutdownCleanup("before-quit");
2238
+ });
1853
2239
  }
1854
2240
 
1855
2241
  /**
1856
- * Load repo-root and ~/.eliza/.env into `process.env` (non-destructive) so the
2242
+ * Load repo-root and state-dir `.env` into `process.env` (non-destructive) so the
1857
2243
  * main process can send the same `ELIZA_API_TOKEN` as `dev-server.ts` when
1858
2244
  * calling loopback APIs (app menu reset, export, etc.). The dev API child
1859
2245
  * already loads dotenv; Electrobun did not until this ran.
1860
2246
  *
1861
2247
  * Packaged desktop builds must not load these files. On machines that also
1862
- * have a the app/Eliza dev checkout, ~/.eliza/.env can contain
2248
+ * have an app/Eliza dev checkout, the state-dir `.env` can contain
1863
2249
  * ELIZA_DESKTOP_API_BASE and related overrides that switch the packaged app
1864
2250
  * into external mode and make launcher startup appear dead.
1865
2251
  */
1866
2252
  async function loadTheAppEnvFilesForMain(): Promise<void> {
1867
- const normalizedModuleDir = import.meta.dir.replaceAll("\\", "/");
1868
- const isPackagedBuild = !normalizedModuleDir.includes("/src/");
1869
- if (isPackagedBuild) {
1870
- return;
1871
- }
1872
-
1873
- try {
1874
- const { config } = await import("dotenv");
1875
- const repoRootGuess = path.resolve(
1876
- normalizedModuleDir,
1877
- "..",
1878
- "..",
1879
- "..",
1880
- "..",
1881
- );
1882
- for (const envPath of [
1883
- path.join(repoRootGuess, ".env"),
1884
- path.join(os.homedir(), ".eliza", ".env"),
1885
- ]) {
1886
- if (fs.existsSync(envPath)) {
1887
- config({ path: envPath, override: false });
1888
- }
1889
- }
1890
- } catch {
1891
- /* dotenv may be unavailable in minimal installs */
1892
- }
2253
+ const normalizedModuleDir = import.meta.dir.replaceAll("\\", "/");
2254
+ const isPackagedBuild = !normalizedModuleDir.includes("/src/");
2255
+ if (isPackagedBuild) {
2256
+ return;
2257
+ }
2258
+
2259
+ try {
2260
+ const { config } = await import("dotenv");
2261
+ const repoRootGuess = path.resolve(
2262
+ normalizedModuleDir,
2263
+ "..",
2264
+ "..",
2265
+ "..",
2266
+ "..",
2267
+ );
2268
+ const namespace = process.env.ELIZA_NAMESPACE?.trim() || BRAND.namespace;
2269
+ const xdgStateHome = process.env.XDG_STATE_HOME?.trim();
2270
+ const stateHome = xdgStateHome
2271
+ ? path.isAbsolute(xdgStateHome)
2272
+ ? xdgStateHome
2273
+ : path.join(os.homedir(), xdgStateHome)
2274
+ : path.join(os.homedir(), ".local", "state");
2275
+ for (const envPath of [
2276
+ path.join(repoRootGuess, ".env"),
2277
+ path.join(stateHome, namespace, ".env"),
2278
+ ]) {
2279
+ if (fs.existsSync(envPath)) {
2280
+ config({ path: envPath, override: false });
2281
+ }
2282
+ }
2283
+ } catch {
2284
+ /* dotenv may be unavailable in minimal installs */
2285
+ }
1893
2286
  }
1894
2287
 
1895
2288
  function initializeBundledWebGPU(): void {
1896
- if (!WGPU.native.available) {
1897
- logger.info(
1898
- "[WebGPU] Native Dawn runtime not bundled for this run; renderer-side WebGPU remains available through the webview/browser path.",
1899
- );
1900
- return;
1901
- }
1902
-
1903
- webgpu.install();
1904
- logger.info(`[WebGPU] Native Dawn runtime ready at ${WGPU.native.path}`);
2289
+ if (!WGPU.native.available) {
2290
+ logger.info(
2291
+ "[WebGPU] Native Dawn runtime not bundled for this run; renderer-side WebGPU remains available through the webview/browser path.",
2292
+ );
2293
+ return;
2294
+ }
2295
+
2296
+ webgpu.install();
2297
+ logger.info(`[WebGPU] Native Dawn runtime ready at ${WGPU.native.path}`);
1905
2298
  }
1906
2299
 
1907
2300
  /**
@@ -1914,595 +2307,712 @@ function initializeBundledWebGPU(): void {
1914
2307
  * On macOS 26+ with native renderer, WebGPU is expected via WKWebView.
1915
2308
  * On Linux/Windows with CEF, upstream Electrobun flag support is still needed.
1916
2309
  */
1917
- function checkWebGpuBrowserSupport(): void {
1918
- const status = checkWebGpuSupport();
1919
- if (status.available) {
1920
- logger.info(`[WebGPU Browser] ${status.reason}`);
1921
- } else {
1922
- logger.warn(`[WebGPU Browser] ${status.reason}`);
1923
- if (status.chromeBetaPath) {
1924
- logger.info(
1925
- `[WebGPU Browser] Chrome Beta found at: ${status.chromeBetaPath}`,
1926
- );
1927
- } else if (status.downloadUrl) {
1928
- logger.info(
1929
- `[WebGPU Browser] Download Chrome Beta: ${status.downloadUrl}`,
1930
- );
1931
- }
1932
- }
1933
-
1934
- // Push status to renderer after a short delay to allow window creation.
1935
- setTimeout(() => {
1936
- sendToActiveRenderer("webgpu:browserStatus", status);
1937
- }, 2000);
2310
+ function checkWebGpuBrowserSupport(rendererType: "native" | "cef"): void {
2311
+ const status = checkWebGpuSupport(rendererType);
2312
+ if (status.available) {
2313
+ logger.info(`[WebGPU Browser] ${status.reason}`);
2314
+ } else {
2315
+ logger.warn(`[WebGPU Browser] ${status.reason}`);
2316
+ if (status.chromeBetaPath) {
2317
+ logger.info(
2318
+ `[WebGPU Browser] Chrome Beta found at: ${status.chromeBetaPath}`,
2319
+ );
2320
+ } else if (status.downloadUrl) {
2321
+ logger.info(
2322
+ `[WebGPU Browser] Download Chrome Beta: ${status.downloadUrl}`,
2323
+ );
2324
+ }
2325
+ }
2326
+
2327
+ // Push status to renderer after a short delay to allow window creation.
2328
+ setTimeout(() => {
2329
+ sendToActiveRenderer("webgpu:browserStatus", status);
2330
+ }, 2000);
1938
2331
  }
1939
2332
 
1940
2333
  async function main(): Promise<void> {
1941
- recordStartupPhase("main_start", {
1942
- pid: process.pid,
1943
- exec_path: process.execPath,
1944
- bundle_path: resolveStartupBundlePath(process.execPath),
1945
- });
1946
- await loadTheAppEnvFilesForMain();
1947
- recordStartupPhase("env_loaded", {
1948
- pid: process.pid,
1949
- });
1950
- console.log(`[Main] Starting ${BRAND.appName} (Electrobun)`);
1951
- const normalizedModuleDir = import.meta.dir.replaceAll("\\", "/");
1952
- const runtimeResolution = resolveDesktopRuntimeMode(
1953
- process.env as Record<string, string | undefined>,
1954
- );
1955
- // Structured startup environment block — visible in CI logs and eliza-startup.log
1956
- console.log(
1957
- `[Env] platform=${process.platform} arch=${process.arch} bun=${Bun.version} ` +
1958
- `execPath=${process.execPath} cwd=${process.cwd()} moduleDir=${import.meta.dir} ` +
1959
- `packaged=${!normalizedModuleDir.includes("/src/")} argv=${process.argv.slice(1).join(" ")}`,
1960
- );
1961
- console.log(
1962
- `[Env] desktopRuntimeMode=${runtimeResolution.mode} externalApi=${runtimeResolution.externalApi.base ?? "none"}`,
1963
- );
1964
-
1965
- printElectrobunDevSettingsBanner(
1966
- process.env as Record<string, string | undefined>,
1967
- );
1968
-
1969
- await maybePromptStartupCrashReport();
1970
- recordStartupPhase("crash_prompt_checked", {
1971
- pid: process.pid,
1972
- });
1973
- // On Windows (CEF renderer), clear stale CEF profile data when the app
1974
- // version changes. A leftover Partitions/default profile from a previous
1975
- // install causes "Cannot create profile at path" errors that cascade into
1976
- // GPU process crashes, rendering the UI unusable. Clearing the CEF cache
1977
- // is safe — it only contains browser session state (cookies, caches,
1978
- // LevelDB stores) that CEF recreates on next launch.
1979
- if (process.platform === "win32") {
1980
- try {
1981
- const cefDir = path.join(Utils.paths.userData, "CEF");
1982
- const cefVersionMarker = path.join(
1983
- cefDir,
1984
- BRAND.cefVersionMarkerFileName,
1985
- );
1986
- const currentVersion =
1987
- resolveDesktopBundleVersion(import.meta.dir) ?? "unknown";
1988
- let previousVersion: string | null = null;
1989
- try {
1990
- previousVersion = fs.readFileSync(cefVersionMarker, "utf-8").trim();
1991
- } catch {
1992
- // No marker first run or pre-fix install.
1993
- }
1994
- if (
1995
- shouldResetWindowsCefProfile({
1996
- currentVersion,
1997
- previousVersion,
1998
- cefDirExists: fs.existsSync(cefDir),
1999
- })
2000
- ) {
2001
- logger.info(
2002
- `[Main] CEF version mismatch (${previousVersion ?? "none"} ${currentVersion}), clearing stale CEF profile`,
2003
- );
2004
- // Remove everything except the version marker we're about to write.
2005
- for (const entry of fs.readdirSync(cefDir)) {
2006
- if (entry === BRAND.cefVersionMarkerFileName) continue;
2007
- const entryPath = path.join(cefDir, entry);
2008
- try {
2009
- fs.rmSync(entryPath, { recursive: true, force: true });
2010
- } catch (err) {
2011
- logger.warn(
2012
- `[Main] Could not remove ${entryPath}: ${err instanceof Error ? err.message : String(err)}`,
2013
- );
2014
- }
2015
- }
2016
- }
2017
- // Write/update version marker so we don't clear again on next launch.
2018
- if (shouldWriteWindowsCefProfileMarker(currentVersion)) {
2019
- fs.mkdirSync(cefDir, { recursive: true });
2020
- fs.writeFileSync(cefVersionMarker, currentVersion);
2021
- }
2022
- } catch (err) {
2023
- logger.warn(
2024
- `[Main] CEF profile cleanup failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`,
2025
- );
2026
- }
2027
- }
2028
-
2029
- initializeBundledWebGPU();
2030
- recordStartupPhase("webgpu_initialized", {
2031
- pid: process.pid,
2032
- });
2033
- checkWebGpuBrowserSupport();
2034
- cleanupFns.length = 0;
2035
- cleanupFns.push(await startBrowserWorkspaceBridgeServer());
2036
- recordStartupPhase("browser_workspace_bridge_ready", {
2037
- pid: process.pid,
2038
- });
2039
- const stopDesktopTestBridgeServer = await startDesktopTestBridgeServer();
2040
- recordStartupPhase("desktop_test_bridge_ready", {
2041
- pid: process.pid,
2042
- });
2043
- if (stopDesktopTestBridgeServer) {
2044
- cleanupFns.push(stopDesktopTestBridgeServer);
2045
- }
2046
-
2047
- // WHY push API base on every status tick with a port: embedded startup can
2048
- // settle on a different loopback port than env/static HTML (allocation + stdout).
2049
- // Detached surfaces must not keep a stale __ELIZA_API_BASE__ while the main
2050
- // window was already updated—menu reset, chat, and settings each own a webview.
2051
- cleanupFns.push(
2052
- getAgentManager().onStatusChange((status) => {
2053
- if (status.port) {
2054
- // The agent rebound to a different loopback port (or recovered from a
2055
- // crash) the cookies we installed during _startAgent were scoped to
2056
- // the old origin. Re-prime so the renderer's next /api request stays
2057
- // authenticated.
2058
- markDesktopSessionStale();
2059
- const apiBase = `http://127.0.0.1:${status.port}`;
2060
- const rendererBase = resolveRendererFacingApiBase(
2061
- process.env as Record<string, string | undefined>,
2062
- status.port,
2063
- );
2064
- void primeDesktopSessionAuth(apiBase, rendererBase);
2065
- if (currentWindow) {
2066
- injectApiBase(currentWindow);
2067
- }
2068
- surfaceWindowManager?.forEachWindow((w) => {
2069
- injectApiBase(w as BrowserWindow);
2070
- });
2071
- }
2072
- }),
2073
- );
2074
-
2075
- // Create window first — on Windows (CEF) the UI message loop must be
2076
- // running before any synchronous FFI calls like setApplicationMenu().
2077
- // Calling setupApplicationMenu() before createMainWindow() deadlocks.
2078
- recordStartupPhase("creating_window", {
2079
- pid: process.pid,
2080
- });
2081
- const { rpc: mainRpc, sendToWebview: mainSendToWebview } =
2082
- createDesktopRpc("main");
2083
- const mainWin = attachMainWindow(
2084
- await createMainWindow(mainRpc),
2085
- mainRpc,
2086
- mainSendToWebview,
2087
- );
2088
- recordStartupPhase("window_ready", {
2089
- pid: process.pid,
2090
- });
2091
-
2092
- // Configure the floating chat manager now that the renderer URL is resolved.
2093
- // This must run after createMainWindow() so rendererUrlPromise is already set.
2094
- void resolveRendererUrl().then((url) => {
2095
- let preload = "";
2096
- try {
2097
- preload = readResolvedPreloadScript(import.meta.dir);
2098
- } catch {
2099
- /* non-fatal */
2100
- }
2101
- getFloatingChatManager().configure(url, preload);
2102
- });
2103
-
2104
- // Per-window RPC tracking: surface windows each get their own typed
2105
- // RPC built up front via createDesktopRpc, baked into the BrowserWindow
2106
- // constructor, then "wired" post-hoc by wireSettingsRpcAfterCreate.
2107
- const surfaceRpcs = new WeakMap<ManagedWindowLike, ElizaDesktopRpc>();
2108
-
2109
- surfaceWindowManager = new SurfaceWindowManager({
2110
- createWindow: (options) => {
2111
- const { rpc } = createDesktopRpc("surface");
2112
- const window = new BrowserWindow({
2113
- ...options,
2114
- rpc,
2115
- }) as BrowserWindow & ManagedWindowLike;
2116
- surfaceRpcs.set(window, rpc);
2117
- return window;
2118
- },
2119
- resolveRendererUrl,
2120
- readPreload: () => readResolvedPreloadScript(import.meta.dir),
2121
- wireRpc: (window) => {
2122
- const rpc = surfaceRpcs.get(window);
2123
- if (!rpc) {
2124
- logger.warn(
2125
- "[surface-windows] wireRpc called for window with no tracked rpc; skipping browser-workspace caller setup",
2126
- );
2127
- return;
2128
- }
2129
- wireSettingsRpcAfterCreate(rpc);
2130
- },
2131
- injectApiBase: (window) =>
2132
- injectApiBase(window as BrowserWindow & ManagedWindowLike),
2133
- onWindowFocused: (window) => {
2134
- lastFocusedWindow = window;
2135
- },
2136
- onRegistryChanged: () => {
2137
- sendManagedWindowsChanged();
2138
- setupApplicationMenu();
2139
- },
2140
- boundsStore: createAppWindowBoundsStore(),
2141
- });
2142
- // Set up app menu after the window (and its message loop) exists.
2143
- setupApplicationMenu();
2144
- const stopScreenshotDevServer = startScreenshotDevServer();
2145
- if (stopScreenshotDevServer) {
2146
- cleanupFns.push(stopScreenshotDevServer);
2147
- }
2148
-
2149
- // Wire detached window callbacks so menus and RPC can open them.
2150
- getDesktopManager().setOpenSettingsCallback((tabHint) => {
2151
- void createSettingsWindow(tabHint);
2152
- });
2153
- getDesktopManager().setRestoreMainWindowCallback(() => restoreWindow());
2154
- getDesktopManager().setRequestQuitCallback(() => {
2155
- requestAppQuit();
2156
- });
2157
- getDesktopManager().setOpenSurfaceWindowCallback(
2158
- (surface, browse, alwaysOnTop) => {
2159
- if (!surfaceWindowManager) {
2160
- throw new Error("Surface window manager is not ready.");
2161
- }
2162
- return surfaceWindowManager.openSurfaceWindow(
2163
- surface,
2164
- browse,
2165
- alwaysOnTop === true,
2166
- );
2167
- },
2168
- );
2169
- getDesktopManager().setOpenAppWindowCallback((options) => {
2170
- if (!surfaceWindowManager) {
2171
- throw new Error("Surface window manager is not ready.");
2172
- }
2173
- return surfaceWindowManager.openAppWindow(options);
2174
- });
2175
- getDesktopManager().setManagedWindowAlwaysOnTopCallback((id, flag) => {
2176
- return surfaceWindowManager?.setWindowAlwaysOnTop(id, flag) ?? false;
2177
- });
2178
-
2179
- // If launched with --hidden (e.g. auto-launch with openAsHidden), minimize immediately.
2180
- if (process.argv.includes("--hidden")) {
2181
- try {
2182
- mainWin.minimize();
2183
- } catch (err) {
2184
- logger.warn(
2185
- `[Main] Failed to minimize window on --hidden startup: ${err instanceof Error ? err.message : String(err)}`,
2186
- );
2187
- }
2188
- }
2189
-
2190
- setupDeepLinks();
2191
- setupDockReopen();
2192
-
2193
- const desktop = getDesktopManager();
2194
- try {
2195
- await desktop.createTray({
2196
- icon: resolveDesktopAppIconPath(),
2197
- tooltip: BRAND.appName,
2198
- title: BRAND.appName,
2199
- menu: [
2200
- { id: "tray-show-window", label: "Show Window", type: "normal" },
2201
- { id: "sep-apps", type: "separator" },
2202
- // One entry per known app — clicks dispatch to handleApplicationMenuAction
2203
- // via the bun-side `tray-clicked` listener below, which then opens (or
2204
- // focuses) the dedicated native window for that app.
2205
- ...getAppMenuEntries().map((entry) => ({
2206
- id: `tray-app-${entry.slug}`,
2207
- label: entry.displayName,
2208
- type: "normal" as const,
2209
- })),
2210
- { id: "sep-lifecycle", type: "separator" },
2211
- {
2212
- id: "tray-toggle-lifecycle",
2213
- label: "Start/Stop Agent",
2214
- type: "normal",
2215
- },
2216
- {
2217
- id: "tray-restart",
2218
- label: "Restart Agent",
2219
- type: "normal",
2220
- },
2221
- { id: "sep-quit", type: "separator" },
2222
- { id: "quit", label: "Quit", type: "normal" },
2223
- ],
2224
- });
2225
- } catch (err) {
2226
- logger.warn(
2227
- `[Main] Tray creation failed: ${err instanceof Error ? err.message : String(err)}`,
2228
- );
2229
- }
2230
-
2231
- // ── Steward sidecar startup (must happen BEFORE agent) ────────────
2232
- // When STEWARD_LOCAL=true, start the steward sidecar first so it can
2233
- // set STEWARD_API_URL / STEWARD_AGENT_TOKEN env vars. The the app agent's
2234
- // steward-bridge.ts reads these on boot to discover local steward.
2235
- if (isStewardLocalEnabled()) {
2236
- logger.info("[Main] STEWARD_LOCAL=true starting steward sidecar...");
2237
- cleanupFns.push(() => stopSteward());
2238
-
2239
- // Listen for steward status changes and push to renderer
2240
- cleanupFns.push(
2241
- onStewardStatusChange((status) => {
2242
- sendToActiveRenderer("stewardStatusUpdate", status);
2243
- }),
2244
- );
2245
-
2246
- try {
2247
- const stewardResult = await startSteward();
2248
- if (stewardResult.state === "running") {
2249
- logger.info(
2250
- `[Main] Steward sidecar ready on port ${stewardResult.port}, wallet: ${stewardResult.walletAddress ?? "pending"}`,
2251
- );
2252
- } else {
2253
- logger.warn(
2254
- `[Main] Steward sidecar in state "${stewardResult.state}": ${stewardResult.error ?? "unknown"}`,
2255
- );
2256
- sendToActiveRenderer("stewardStartupFailed", {
2257
- error: stewardResult.error ?? "Steward failed to start",
2258
- canRetry: true,
2259
- });
2260
- }
2261
- } catch (err) {
2262
- const error = err instanceof Error ? err.message : String(err);
2263
- logger.error(`[Main] Steward sidecar startup failed: ${error}`);
2264
- sendToActiveRenderer("stewardStartupFailed", {
2265
- error,
2266
- canRetry: true,
2267
- });
2268
- // Don't block agent startup — steward is optional
2269
- }
2270
- }
2271
-
2272
- // Agent startup: in external mode, push the API base via the
2273
- // api-base-owner (the agent is already running externally). In local
2274
- // mode, start the embedded agent first — apiBaseOwner.injectIntoHtml()
2275
- // already set the initial window.__ELIZA_API_BASE__ from the seed value
2276
- // in main(), but _startAgent will push the actual port once the agent
2277
- // reports it.
2278
- if (currentWindow) {
2279
- const rt = resolveDesktopRuntimeMode(
2280
- process.env as Record<string, string | undefined>,
2281
- );
2282
- if (rt.mode === "external") {
2283
- injectApiBase(currentWindow);
2284
- } else if (rt.mode === "local") {
2285
- logger.info("[Main] Starting embedded agent (local mode).");
2286
- _startAgent(currentWindow).catch((err) => {
2287
- logger.error(
2288
- `[Main] Agent auto-start failed: ${err instanceof Error ? err.message : String(err)}`,
2289
- );
2290
- const error = err instanceof Error ? err.message : String(err);
2291
- sendToActiveRenderer("agentStartupFailed", { error });
2292
- console.error(`title: "${BRAND.appName} startup failed"`);
2293
- });
2294
- }
2295
- }
2296
-
2297
- void setupUpdater();
2298
- cleanupFns.push(() => getAgentManager().stop());
2299
- setupShutdown();
2334
+ recordStartupPhase("main_start", {
2335
+ pid: process.pid,
2336
+ exec_path: process.execPath,
2337
+ bundle_path: resolveStartupBundlePath(process.execPath),
2338
+ });
2339
+ await loadTheAppEnvFilesForMain();
2340
+ recordStartupPhase("env_loaded", {
2341
+ pid: process.pid,
2342
+ });
2343
+ // Start the static renderer server in parallel with the rest of pre-window
2344
+ // work first paint needs the renderer URL, so kicking it off now overlaps
2345
+ // the server bind/port-scan with crash-prompt checks, WebGPU init, and bridge
2346
+ // startup below. resolveRendererUrl() is idempotent (memoises this promise),
2347
+ // so later callers reuse it. Errors surface when the promise is awaited.
2348
+ void resolveRendererUrl();
2349
+ console.log(`[Main] Starting ${BRAND.appName} (Electrobun)`);
2350
+ const normalizedModuleDir = import.meta.dir.replaceAll("\\", "/");
2351
+ const runtimeResolution = resolveDesktopRuntime();
2352
+ // Structured startup environment block — visible in CI logs and eliza-startup.log
2353
+ console.log(
2354
+ `[Env] platform=${process.platform} arch=${process.arch} bun=${Bun.version} ` +
2355
+ `execPath=${process.execPath} cwd=${process.cwd()} moduleDir=${import.meta.dir} ` +
2356
+ `packaged=${!normalizedModuleDir.includes("/src/")} argv=${process.argv.slice(1).join(" ")}`,
2357
+ );
2358
+ console.log(
2359
+ `[Env] desktopRuntimeMode=${runtimeResolution.mode} externalApi=${runtimeResolution.externalApi.base ?? "none"}`,
2360
+ );
2361
+
2362
+ printElectrobunDevSettingsBanner(
2363
+ process.env as Record<string, string | undefined>,
2364
+ );
2365
+
2366
+ // Don't block first paint on the crash-recovery prompt. The common path is a
2367
+ // couple of stat reads that early-return; the only blocking case is a modal
2368
+ // shown after a *prior* launch crashed, which can safely overlap the window.
2369
+ void maybePromptStartupCrashReport()
2370
+ .then(() => {
2371
+ recordStartupPhase("crash_prompt_checked", {
2372
+ pid: process.pid,
2373
+ });
2374
+ })
2375
+ .catch((err) => {
2376
+ logger.warn(
2377
+ `[Main] Startup crash prompt failed: ${err instanceof Error ? err.message : String(err)}`,
2378
+ );
2379
+ });
2380
+ // On Windows (CEF renderer), clear stale CEF profile data when the app
2381
+ // version changes. A leftover Partitions/default profile from a previous
2382
+ // install causes "Cannot create profile at path" errors that cascade into
2383
+ // GPU process crashes, rendering the UI unusable. Clearing the CEF cache
2384
+ // is safe — it only contains browser session state (cookies, caches,
2385
+ // LevelDB stores) that CEF recreates on next launch.
2386
+ if (process.platform === "win32") {
2387
+ try {
2388
+ const cefDir = path.join(Utils.paths.userData, "CEF");
2389
+ const cefVersionMarker = path.join(
2390
+ cefDir,
2391
+ BRAND.cefVersionMarkerFileName,
2392
+ );
2393
+ const currentVersion =
2394
+ resolveDesktopBundleVersion(import.meta.dir) ?? "unknown";
2395
+ let previousVersion: string | null = null;
2396
+ try {
2397
+ previousVersion = fs.readFileSync(cefVersionMarker, "utf-8").trim();
2398
+ } catch {
2399
+ // No marker — first run or pre-fix install.
2400
+ }
2401
+ if (
2402
+ shouldResetWindowsCefProfile({
2403
+ currentVersion,
2404
+ previousVersion,
2405
+ cefDirExists: fs.existsSync(cefDir),
2406
+ })
2407
+ ) {
2408
+ logger.info(
2409
+ `[Main] CEF version mismatch (${previousVersion ?? "none"} → ${currentVersion}), clearing stale CEF profile`,
2410
+ );
2411
+ // Remove everything except the version marker we're about to write.
2412
+ for (const entry of fs.readdirSync(cefDir)) {
2413
+ if (entry === BRAND.cefVersionMarkerFileName) continue;
2414
+ const entryPath = path.join(cefDir, entry);
2415
+ try {
2416
+ fs.rmSync(entryPath, { recursive: true, force: true });
2417
+ } catch (err) {
2418
+ logger.warn(
2419
+ `[Main] Could not remove ${entryPath}: ${err instanceof Error ? err.message : String(err)}`,
2420
+ );
2421
+ }
2422
+ }
2423
+ }
2424
+ // Write/update version marker so we don't clear again on next launch.
2425
+ if (shouldWriteWindowsCefProfileMarker(currentVersion)) {
2426
+ fs.mkdirSync(cefDir, { recursive: true });
2427
+ fs.writeFileSync(cefVersionMarker, currentVersion);
2428
+ }
2429
+ } catch (err) {
2430
+ logger.warn(
2431
+ `[Main] CEF profile cleanup failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`,
2432
+ );
2433
+ }
2434
+ }
2435
+
2436
+ initializeBundledWebGPU();
2437
+ recordStartupPhase("webgpu_initialized", {
2438
+ pid: process.pid,
2439
+ });
2440
+ const buildInfo = await BuildConfig.get();
2441
+ checkWebGpuBrowserSupport(buildInfo.defaultRenderer);
2442
+ cleanupFns.length = 0;
2443
+ // Start the browser-workspace bridge without blocking first paint. The
2444
+ // renderer reaches it lazily (browser-workspace RPC), so it does not need to
2445
+ // be listening before the window opens. Register a cleanup that awaits the
2446
+ // resolved stop fn so shutdown still tears it down.
2447
+ const browserWorkspaceBridgeStop = startBrowserWorkspaceBridgeServer()
2448
+ .then((stop) => {
2449
+ recordStartupPhase("browser_workspace_bridge_ready", {
2450
+ pid: process.pid,
2451
+ });
2452
+ return stop;
2453
+ })
2454
+ .catch((err) => {
2455
+ logger.warn(
2456
+ `[Main] Browser-workspace bridge startup failed: ${err instanceof Error ? err.message : String(err)}`,
2457
+ );
2458
+ return undefined;
2459
+ });
2460
+ cleanupFns.push(async () => {
2461
+ const stop = await browserWorkspaceBridgeStop;
2462
+ await stop?.();
2463
+ });
2464
+ const stopDesktopTestBridgeServer = await startDesktopTestBridgeServer();
2465
+ recordStartupPhase("desktop_test_bridge_ready", {
2466
+ pid: process.pid,
2467
+ });
2468
+ if (stopDesktopTestBridgeServer) {
2469
+ cleanupFns.push(stopDesktopTestBridgeServer);
2470
+ }
2471
+
2472
+ // WHY push API base on every status tick with a port: embedded startup can
2473
+ // settle on a different loopback port than env/static HTML (allocation + stdout).
2474
+ // Detached surfaces must not keep a stale __ELIZA_API_BASE__ while the main
2475
+ // window was already updated—menu reset, chat, and settings each own a webview.
2476
+ cleanupFns.push(
2477
+ getAgentManager().onStatusChange((status) => {
2478
+ if (status.port) {
2479
+ // The agent rebound to a different loopback port (or recovered from a
2480
+ // crash) — the cookies we installed during _startAgent were scoped to
2481
+ // the old origin. Re-prime so every renderer's next /api request stays
2482
+ // authenticated, including the OS-level pill window.
2483
+ markDesktopSessionStale();
2484
+ const apiBase = `http://127.0.0.1:${status.port}`;
2485
+ const rendererBase = resolveRendererFacingApiBase(
2486
+ process.env as Record<string, string | undefined>,
2487
+ status.port,
2488
+ );
2489
+ void primeDesktopSessionAuth(apiBase, rendererBase);
2490
+ injectApiBaseIntoOpenRendererWindows();
2491
+ }
2492
+ }),
2493
+ );
2494
+
2495
+ // Create window first — on Windows (CEF) the UI message loop must be
2496
+ // running before any synchronous FFI calls like setApplicationMenu().
2497
+ // Calling setupApplicationMenu() before createMainWindow() deadlocks.
2498
+ const onboardingOverlay = shouldStartOnboardingOverlay();
2499
+ const trayFirst = shouldStartTrayFirst();
2500
+ let mainWin: BrowserWindow | null = null;
2501
+ if (onboardingOverlay) {
2502
+ // First-run onboarding (macOS, opt-in): post a native macOS notification
2503
+ // with action buttons ("Local On-Device", "Local Cloud AI", "Eliza Cloud").
2504
+ // The user's choice is polled via FFI. Once selected, we wait for the
2505
+ // embedded API server to be ready, POST the first-run config, then open
2506
+ // the dashboard. Falls back to the overlay window on FFI failure.
2507
+ logger.info("[Main] Onboarding — posting native macOS notification");
2508
+ recordStartupPhase("creating_window", { pid: process.pid });
2509
+
2510
+ // Run notification flow async — don't block the event loop.
2511
+ // Once the user picks and the API is ready, we create the dashboard.
2512
+ void (async () => {
2513
+ try {
2514
+ const choice = await waitForOnboardingNotificationChoice();
2515
+ if (!choice) {
2516
+ // FFI failed or unsupported — fall back to overlay window.
2517
+ logger.warn(
2518
+ "[Main] Native notification unavailable falling back to overlay",
2519
+ );
2520
+ await openOnboardingOverlayWindow();
2521
+ return;
2522
+ }
2523
+
2524
+ // Wait for the embedded API server to be ready before submitting.
2525
+ // The agent manager may not have started yet, so poll until
2526
+ // resolveLoopbackApiBase() returns a valid URL.
2527
+ let apiBase: string | null = null;
2528
+ const apiBaseDeadline = Date.now() + 120_000;
2529
+ while (Date.now() < apiBaseDeadline) {
2530
+ apiBase = resolveLoopbackApiBase();
2531
+ if (apiBase) break;
2532
+ await new Promise((r) => setTimeout(r, 2000));
2533
+ }
2534
+ if (!apiBase) {
2535
+ logger.error(
2536
+ "[Main] Cannot resolve API base after 120s — falling back to overlay",
2537
+ );
2538
+ await openOnboardingOverlayWindow();
2539
+ return;
2540
+ }
2541
+
2542
+ logger.info(
2543
+ `[Main] Notification choice received — waiting for API at ${apiBase}`,
2544
+ );
2545
+ const apiReady = await waitForApiReady(apiBase, 120_000);
2546
+ if (!apiReady) {
2547
+ logger.error(
2548
+ "[Main] API server not ready after 120s — falling back to overlay",
2549
+ );
2550
+ await openOnboardingOverlayWindow();
2551
+ return;
2552
+ }
2553
+
2554
+ // Submit first-run config to the API.
2555
+ const submitted = await submitOnboardingFirstRun(apiBase, choice);
2556
+ if (!submitted) {
2557
+ logger.error(
2558
+ "[Main] First-run submission failed — falling back to overlay",
2559
+ );
2560
+ await openOnboardingOverlayWindow();
2561
+ return;
2562
+ }
2563
+
2564
+ // First-run complete close the overlay and let its close handler
2565
+ // create the dashboard (single handoff point). The win.on("close")
2566
+ // handler wired in openOnboardingOverlayWindow() transitions to the
2567
+ // main dashboard window, so we just need to trigger it.
2568
+ logger.info(
2569
+ "[Main] First-run complete closing overlay to transition to dashboard",
2570
+ );
2571
+ closeOnboardingOverlayWindow();
2572
+ } catch (err) {
2573
+ logger.error(
2574
+ `[Main] Native onboarding failed: ${err instanceof Error ? err.message : String(err)}`,
2575
+ );
2576
+ await openOnboardingOverlayWindow();
2577
+ }
2578
+ })();
2579
+
2580
+ recordStartupPhase("window_ready", {
2581
+ pid: process.pid,
2582
+ });
2583
+ } else if (trayFirst) {
2584
+ // Tray-first (macOS, opt-in): no window at launch. The tray icon is the
2585
+ // only surface; the main window is created lazily via restoreWindow() /
2586
+ // DesktopManager.showWindow() on tray "Show Window", Dock reopen, or a
2587
+ // deep link. setTrayFirstMode hides the Dock icon until a window is shown.
2588
+ logger.info("[Main] Tray-first startup — deferring main window creation");
2589
+ getDesktopManager().setTrayFirstMode(true);
2590
+ recordStartupPhase("window_ready", {
2591
+ pid: process.pid,
2592
+ });
2593
+ } else {
2594
+ recordStartupPhase("creating_window", {
2595
+ pid: process.pid,
2596
+ });
2597
+ const { rpc: mainRpc, sendToWebview: mainSendToWebview } =
2598
+ createDesktopRpc("main");
2599
+ mainWin = attachMainWindow(
2600
+ await createMainWindow(mainRpc),
2601
+ mainRpc,
2602
+ mainSendToWebview,
2603
+ );
2604
+ recordStartupPhase("window_ready", {
2605
+ pid: process.pid,
2606
+ });
2607
+ }
2608
+ seedFirstPartyRemotePluginsForStartup();
2609
+
2610
+ // Configure the floating chat manager now that the renderer URL is resolved.
2611
+ // This must run after createMainWindow() so rendererUrlPromise is already set.
2612
+ void resolveRendererUrl().then((url) => {
2613
+ let preload = "";
2614
+ try {
2615
+ preload = readResolvedPreloadScript(import.meta.dir);
2616
+ } catch {
2617
+ /* non-fatal */
2618
+ }
2619
+ getFloatingChatManager().configure(url, preload);
2620
+ // In kiosk mode the chat pill lives in-canvas on the KioskShell, so we
2621
+ // never spawn the separate native pill toplevel. Outside kiosk, the pill
2622
+ // loads the same renderer in chat-overlay shell mode so the assistant
2623
+ // lives in its own OS-level window instead of inside the app.
2624
+ if (!isKioskShellMode() && shouldCreateDesktopPill()) {
2625
+ try {
2626
+ createPillWindow({ rendererUrl: url, preload });
2627
+ } catch (err) {
2628
+ logger.warn(
2629
+ `[Main] Failed to spawn pill window: ${err instanceof Error ? err.message : String(err)}`,
2630
+ );
2631
+ }
2632
+ }
2633
+ });
2634
+
2635
+ // Per-window RPC tracking: surface windows each get their own typed
2636
+ // RPC built up front via createDesktopRpc, baked into the BrowserWindow
2637
+ // constructor, then "wired" post-hoc by wireSettingsRpcAfterCreate.
2638
+ const surfaceRpcs = new WeakMap<ManagedWindowLike, ElizaDesktopRpc>();
2639
+
2640
+ surfaceWindowManager = new SurfaceWindowManager({
2641
+ createWindow: (options) => {
2642
+ const { rpc } = createDesktopRpc("surface");
2643
+ const window = createElectrobunBrowserWindow({
2644
+ ...options,
2645
+ rpc,
2646
+ }) as BrowserWindow & ManagedWindowLike;
2647
+ surfaceRpcs.set(window, rpc);
2648
+ return window;
2649
+ },
2650
+ resolveRendererUrl,
2651
+ readPreload: () => readResolvedPreloadScript(import.meta.dir),
2652
+ wireRpc: (window) => {
2653
+ const rpc = surfaceRpcs.get(window);
2654
+ if (!rpc) {
2655
+ logger.warn(
2656
+ "[surface-windows] wireRpc called for window with no tracked rpc; skipping browser-workspace caller setup",
2657
+ );
2658
+ return;
2659
+ }
2660
+ wireSettingsRpcAfterCreate(rpc);
2661
+ },
2662
+ injectApiBase: (window) =>
2663
+ injectApiBase(window as BrowserWindow & ManagedWindowLike),
2664
+ onWindowFocused: (window) => {
2665
+ lastFocusedWindow = window;
2666
+ },
2667
+ onRegistryChanged: () => {
2668
+ sendManagedWindowsChanged();
2669
+ setupApplicationMenu();
2670
+ },
2671
+ boundsStore: createAppWindowBoundsStore(),
2672
+ });
2673
+ // Set up app menu after the window (and its message loop) exists.
2674
+ setupApplicationMenu();
2675
+ const stopScreenshotDevServer = startScreenshotDevServer();
2676
+ if (stopScreenshotDevServer) {
2677
+ cleanupFns.push(stopScreenshotDevServer);
2678
+ }
2679
+
2680
+ // Wire detached window callbacks so menus and RPC can open them.
2681
+ getDesktopManager().setOpenSettingsCallback((tabHint) => {
2682
+ void createSettingsWindow(tabHint);
2683
+ });
2684
+ getDesktopManager().setRestoreMainWindowCallback(() => restoreWindow());
2685
+ getDesktopManager().setRequestQuitCallback(() => {
2686
+ void requestAppQuit();
2687
+ });
2688
+ getDesktopManager().setOpenSurfaceWindowCallback(
2689
+ (surface, browse, alwaysOnTop) => {
2690
+ if (!surfaceWindowManager) {
2691
+ throw new Error("Surface window manager is not ready.");
2692
+ }
2693
+ return surfaceWindowManager.openSurfaceWindow(
2694
+ surface,
2695
+ browse,
2696
+ alwaysOnTop === true,
2697
+ );
2698
+ },
2699
+ );
2700
+ getDesktopManager().setOpenAppWindowCallback((options) => {
2701
+ if (!surfaceWindowManager) {
2702
+ throw new Error("Surface window manager is not ready.");
2703
+ }
2704
+ return surfaceWindowManager.openAppWindow(options);
2705
+ });
2706
+ getDesktopManager().setManagedWindowAlwaysOnTopCallback((id, flag) => {
2707
+ return surfaceWindowManager?.setWindowAlwaysOnTop(id, flag) ?? false;
2708
+ });
2709
+
2710
+ // If launched with --hidden (e.g. auto-launch with openAsHidden), minimize immediately.
2711
+ // In tray-first mode there is no window yet (mainWin is null) — nothing to minimize.
2712
+ if (mainWin && process.argv.includes("--hidden")) {
2713
+ try {
2714
+ mainWin.minimize();
2715
+ } catch (err) {
2716
+ logger.warn(
2717
+ `[Main] Failed to minimize window on --hidden startup: ${err instanceof Error ? err.message : String(err)}`,
2718
+ );
2719
+ }
2720
+ }
2721
+
2722
+ setupDeepLinks();
2723
+ setupDockReopen();
2724
+
2725
+ const desktop = getDesktopManager();
2726
+ if (shouldCreateDesktopTray(process.env)) {
2727
+ try {
2728
+ // Tray is created here so the icon appears at startup, but the menu is
2729
+ // owned by the renderer (DesktopTrayRuntime + main.tsx → Desktop.setTrayMenu).
2730
+ // That keeps a single source of truth for tray items and their handlers.
2731
+ await desktop.createTray({
2732
+ icon: resolveDesktopAppIconPath(),
2733
+ tooltip: BRAND.appName,
2734
+ title: BRAND.appName,
2735
+ });
2736
+ } catch (err) {
2737
+ logger.warn(
2738
+ `[Main] Tray creation failed: ${err instanceof Error ? err.message : String(err)}`,
2739
+ );
2740
+ }
2741
+ } else {
2742
+ logger.info("[Main] Desktop tray disabled by environment");
2743
+ }
2744
+
2745
+ // ── Steward sidecar startup (must happen BEFORE agent) ────────────
2746
+ // When STEWARD_LOCAL=true, start the steward sidecar first so it can
2747
+ // set STEWARD_API_URL / STEWARD_AGENT_TOKEN env vars. The the app agent's
2748
+ // steward-bridge.ts reads these on boot to discover local steward.
2749
+ if (isStewardLocalEnabled()) {
2750
+ logger.info("[Main] STEWARD_LOCAL=true — starting steward sidecar...");
2751
+ cleanupFns.push(() => stopSteward());
2752
+
2753
+ // Listen for steward status changes and push to renderer
2754
+ cleanupFns.push(
2755
+ onStewardStatusChange((status) => {
2756
+ sendToActiveRenderer("stewardStatusUpdate", status);
2757
+ }),
2758
+ );
2759
+
2760
+ try {
2761
+ const stewardResult = await startSteward();
2762
+ if (stewardResult.state === "running") {
2763
+ logger.info(
2764
+ `[Main] Steward sidecar ready on port ${stewardResult.port}, wallet: ${stewardResult.walletAddress ?? "pending"}`,
2765
+ );
2766
+ } else {
2767
+ logger.warn(
2768
+ `[Main] Steward sidecar in state "${stewardResult.state}": ${stewardResult.error ?? "unknown"}`,
2769
+ );
2770
+ sendToActiveRenderer("stewardStartupFailed", {
2771
+ error: stewardResult.error ?? "Steward failed to start",
2772
+ canRetry: true,
2773
+ });
2774
+ }
2775
+ } catch (err) {
2776
+ const error = err instanceof Error ? err.message : String(err);
2777
+ logger.error(`[Main] Steward sidecar startup failed: ${error}`);
2778
+ sendToActiveRenderer("stewardStartupFailed", {
2779
+ error,
2780
+ canRetry: true,
2781
+ });
2782
+ // Don't block agent startup — steward is optional
2783
+ }
2784
+ }
2785
+
2786
+ // Agent startup: in external mode, push the API base via the
2787
+ // api-base-owner (the agent is already running externally). In local
2788
+ // mode, start the embedded agent first — apiBaseOwner.injectIntoHtml()
2789
+ // already set the initial window.__ELIZA_API_BASE__ from the seed value
2790
+ // in main(), but _startAgent will push the actual port once the agent
2791
+ // reports it.
2792
+ const rt = resolveDesktopRuntime();
2793
+ if (rt.mode === "external") {
2794
+ injectApiBaseIntoOpenRendererWindows();
2795
+ } else if (rt.mode === "local") {
2796
+ logger.info("[Main] Starting embedded agent (local mode).");
2797
+ _startAgent().catch((err) => {
2798
+ logger.error(
2799
+ `[Main] Agent auto-start failed: ${err instanceof Error ? err.message : String(err)}`,
2800
+ );
2801
+ const error = err instanceof Error ? err.message : String(err);
2802
+ sendToActiveRenderer("agentStartupFailed", { error });
2803
+ console.error(`title: "${BRAND.appName} startup failed"`);
2804
+ });
2805
+ }
2806
+
2807
+ void setupUpdater();
2808
+ cleanupFns.push(() => getAgentManager().stop());
2809
+ setupShutdown();
2300
2810
  }
2301
2811
 
2302
2812
  function resolveStartupCrashReportPath(): string {
2303
- return path.join(
2304
- path.dirname(getDiagnosticLogPath()),
2305
- STARTUP_CRASH_REPORT_FILE,
2306
- );
2813
+ return path.join(
2814
+ path.dirname(getDiagnosticLogPath()),
2815
+ STARTUP_CRASH_REPORT_FILE,
2816
+ );
2307
2817
  }
2308
2818
 
2309
2819
  function resolveStartupCrashPromptMarkerPath(): string {
2310
- return path.join(
2311
- path.dirname(getDiagnosticLogPath()),
2312
- STARTUP_CRASH_PROMPT_MARKER_FILE,
2313
- );
2820
+ return path.join(
2821
+ path.dirname(getDiagnosticLogPath()),
2822
+ STARTUP_CRASH_PROMPT_MARKER_FILE,
2823
+ );
2314
2824
  }
2315
2825
 
2316
2826
  function buildStartupCrashDiscordReport(options: {
2317
- source: "startup-recovery" | "fatal-startup";
2318
- error: string | null;
2827
+ source: "startup-recovery" | "fatal-startup";
2828
+ error: string | null;
2319
2829
  }): string {
2320
- const diagnostics = getStartupDiagnosticsSnapshot();
2321
- const startupLogTail = getStartupDiagnosticLogTail(8_000).trim();
2322
- const appVersion = process.env.npm_package_version?.trim() || "unknown";
2323
- const appRuntime = `electrobun/${Bun.version}`;
2324
- const reportLines = [
2325
- `${BRAND.appName} startup crash report`,
2326
- "",
2327
- "Share this report in Discord and ping @iono.",
2328
- "",
2329
- `Source: ${options.source}`,
2330
- `Timestamp: ${new Date().toISOString()}`,
2331
- `App Version: ${appVersion}`,
2332
- `Runtime: ${appRuntime}`,
2333
- `Platform: ${process.platform} ${process.arch}`,
2334
- `State: ${diagnostics.state}`,
2335
- `Phase: ${diagnostics.phase}`,
2336
- `Last Error: ${options.error ?? diagnostics.lastError ?? "unknown"}`,
2337
- `Updated At: ${diagnostics.updatedAt}`,
2338
- `Log Path: ${diagnostics.logPath}`,
2339
- `Status Path: ${diagnostics.statusPath}`,
2340
- "",
2341
- startupLogTail ? "Startup Log Tail:" : "Startup Log Tail: unavailable",
2342
- ];
2343
-
2344
- if (startupLogTail) {
2345
- reportLines.push("```");
2346
- reportLines.push(startupLogTail);
2347
- reportLines.push("```");
2348
- }
2349
- return `${reportLines.join("\n")}\n`;
2830
+ const diagnostics = getStartupDiagnosticsSnapshot();
2831
+ const startupLogTail = getStartupDiagnosticLogTail(8_000).trim();
2832
+ const appVersion = process.env.npm_package_version?.trim() || "unknown";
2833
+ const appRuntime = `electrobun/${Bun.version}`;
2834
+ const reportLines = [
2835
+ `${BRAND.appName} startup crash report`,
2836
+ "",
2837
+ "Share this report in Discord and ping @iono.",
2838
+ "",
2839
+ `Source: ${options.source}`,
2840
+ `Timestamp: ${new Date().toISOString()}`,
2841
+ `App Version: ${appVersion}`,
2842
+ `Runtime: ${appRuntime}`,
2843
+ `Platform: ${process.platform} ${process.arch}`,
2844
+ `State: ${diagnostics.state}`,
2845
+ `Phase: ${diagnostics.phase}`,
2846
+ `Last Error: ${options.error ?? diagnostics.lastError ?? "unknown"}`,
2847
+ `Updated At: ${diagnostics.updatedAt}`,
2848
+ `Log Path: ${diagnostics.logPath}`,
2849
+ `Status Path: ${diagnostics.statusPath}`,
2850
+ "",
2851
+ startupLogTail ? "Startup Log Tail:" : "Startup Log Tail: unavailable",
2852
+ ];
2853
+
2854
+ if (startupLogTail) {
2855
+ reportLines.push("```");
2856
+ reportLines.push(startupLogTail);
2857
+ reportLines.push("```");
2858
+ }
2859
+ return `${reportLines.join("\n")}\n`;
2350
2860
  }
2351
2861
 
2352
2862
  function persistStartupCrashReport(options: {
2353
- source: "startup-recovery" | "fatal-startup";
2354
- error: string | null;
2863
+ source: "startup-recovery" | "fatal-startup";
2864
+ error: string | null;
2355
2865
  }): { report: string; reportPath: string } {
2356
- const report = buildStartupCrashDiscordReport(options);
2357
- const primaryReportPath = resolveStartupCrashReportPath();
2358
- const fallbackReportPath = path.join(os.tmpdir(), STARTUP_CRASH_REPORT_FILE);
2359
- let reportPath = primaryReportPath;
2360
- try {
2361
- fs.mkdirSync(path.dirname(primaryReportPath), { recursive: true });
2362
- fs.writeFileSync(primaryReportPath, report, "utf8");
2363
- } catch (err) {
2364
- logger.warn(
2365
- `[Main] Failed to write startup crash report: ${err instanceof Error ? err.message : String(err)}`,
2366
- );
2367
- try {
2368
- fs.mkdirSync(path.dirname(fallbackReportPath), { recursive: true });
2369
- fs.writeFileSync(fallbackReportPath, report, "utf8");
2370
- reportPath = fallbackReportPath;
2371
- } catch (fallbackErr) {
2372
- logger.warn(
2373
- `[Main] Failed to write fallback startup crash report: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`,
2374
- );
2375
- }
2376
- }
2377
- return { report, reportPath };
2866
+ const report = buildStartupCrashDiscordReport(options);
2867
+ const primaryReportPath = resolveStartupCrashReportPath();
2868
+ const fallbackReportPath = path.join(os.tmpdir(), STARTUP_CRASH_REPORT_FILE);
2869
+ let reportPath = primaryReportPath;
2870
+ try {
2871
+ fs.mkdirSync(path.dirname(primaryReportPath), { recursive: true });
2872
+ fs.writeFileSync(primaryReportPath, report, "utf8");
2873
+ } catch (err) {
2874
+ logger.warn(
2875
+ `[Main] Failed to write startup crash report: ${err instanceof Error ? err.message : String(err)}`,
2876
+ );
2877
+ try {
2878
+ fs.mkdirSync(path.dirname(fallbackReportPath), { recursive: true });
2879
+ fs.writeFileSync(fallbackReportPath, report, "utf8");
2880
+ reportPath = fallbackReportPath;
2881
+ } catch (fallbackErr) {
2882
+ logger.warn(
2883
+ `[Main] Failed to write fallback startup crash report: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`,
2884
+ );
2885
+ }
2886
+ }
2887
+ return { report, reportPath };
2378
2888
  }
2379
2889
 
2380
2890
  function wasStartupCrashAlreadyPrompted(updatedAt: string): boolean {
2381
- try {
2382
- const markerPath = resolveStartupCrashPromptMarkerPath();
2383
- return fs.readFileSync(markerPath, "utf8").trim() === updatedAt;
2384
- } catch {
2385
- return false;
2386
- }
2891
+ try {
2892
+ const markerPath = resolveStartupCrashPromptMarkerPath();
2893
+ return fs.readFileSync(markerPath, "utf8").trim() === updatedAt;
2894
+ } catch {
2895
+ return false;
2896
+ }
2387
2897
  }
2388
2898
 
2389
2899
  function markStartupCrashPrompted(updatedAt: string): void {
2390
- try {
2391
- fs.writeFileSync(resolveStartupCrashPromptMarkerPath(), updatedAt, "utf8");
2392
- } catch {}
2900
+ try {
2901
+ fs.writeFileSync(resolveStartupCrashPromptMarkerPath(), updatedAt, "utf8");
2902
+ } catch {}
2393
2903
  }
2394
2904
 
2395
2905
  async function maybePromptStartupCrashReport(): Promise<void> {
2396
- if (
2397
- process.env.ELIZA_DESKTOP_SKIP_STARTUP_CRASH_PROMPT === "1" ||
2398
- process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_DIALOGS === "1"
2399
- ) {
2400
- return;
2401
- }
2402
-
2403
- const diagnostics = getStartupDiagnosticsSnapshot();
2404
- const looksLikeStartupFailure =
2405
- diagnostics.state === "error" &&
2406
- diagnostics.phase !== "ready" &&
2407
- diagnostics.phase !== "stopped";
2408
- if (!looksLikeStartupFailure) {
2409
- return;
2410
- }
2411
- if (wasStartupCrashAlreadyPrompted(diagnostics.updatedAt)) {
2412
- return;
2413
- }
2414
-
2415
- const { report, reportPath } = persistStartupCrashReport({
2416
- source: "startup-recovery",
2417
- error: diagnostics.lastError,
2418
- });
2419
- markStartupCrashPrompted(diagnostics.updatedAt);
2420
-
2421
- const dialog = await Utils.showMessageBox({
2422
- type: "warning",
2423
- title: `${BRAND.appName} recovered after a startup failure`,
2424
- message:
2425
- "The previous launch failed. A crash report is ready to share with support.",
2426
- detail:
2427
- "Choose Copy Report, paste into Discord, and ping @iono. You can also open logs.",
2428
- buttons: ["Copy Report", "Open Logs Folder", "Continue"],
2429
- defaultId: 0,
2430
- cancelId: 2,
2431
- });
2432
- const response =
2433
- dialog && typeof dialog === "object" && "response" in dialog
2434
- ? (dialog as { response: number }).response
2435
- : typeof dialog === "number"
2436
- ? dialog
2437
- : 2;
2438
-
2439
- if (response === 0) {
2440
- try {
2441
- Utils.clipboardWriteText(report);
2442
- Utils.showNotification({
2443
- title: "Crash report copied",
2444
- body: "Paste in Discord and ping @iono.",
2445
- });
2446
- } catch (err) {
2447
- logger.warn(
2448
- `[Main] Failed to copy startup crash report: ${err instanceof Error ? err.message : String(err)}`,
2449
- );
2450
- }
2451
- } else if (response === 1) {
2452
- try {
2453
- Utils.openPath(path.dirname(reportPath));
2454
- } catch (err) {
2455
- logger.warn(
2456
- `[Main] Failed to open startup logs folder: ${err instanceof Error ? err.message : String(err)}`,
2457
- );
2458
- }
2459
- }
2906
+ if (
2907
+ process.env.ELIZA_DESKTOP_SKIP_STARTUP_CRASH_PROMPT === "1" ||
2908
+ process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_DIALOGS === "1"
2909
+ ) {
2910
+ return;
2911
+ }
2912
+
2913
+ const diagnostics = getStartupDiagnosticsSnapshot();
2914
+ const looksLikeStartupFailure =
2915
+ diagnostics.state === "error" &&
2916
+ diagnostics.phase !== "ready" &&
2917
+ diagnostics.phase !== "stopped";
2918
+ if (!looksLikeStartupFailure) {
2919
+ return;
2920
+ }
2921
+ if (wasStartupCrashAlreadyPrompted(diagnostics.updatedAt)) {
2922
+ return;
2923
+ }
2924
+
2925
+ const { report, reportPath } = persistStartupCrashReport({
2926
+ source: "startup-recovery",
2927
+ error: diagnostics.lastError,
2928
+ });
2929
+ markStartupCrashPrompted(diagnostics.updatedAt);
2930
+
2931
+ const dialog = await Utils.showMessageBox({
2932
+ type: "warning",
2933
+ title: `${BRAND.appName} recovered after a startup failure`,
2934
+ message:
2935
+ "The previous launch failed. A crash report is ready to share with support.",
2936
+ detail:
2937
+ "Choose Copy Report, paste into Discord, and ping @iono. You can also open logs.",
2938
+ buttons: ["Copy Report", "Open Logs Folder", "Continue"],
2939
+ defaultId: 0,
2940
+ cancelId: 2,
2941
+ });
2942
+ const response =
2943
+ dialog && typeof dialog === "object" && "response" in dialog
2944
+ ? (dialog as { response: number }).response
2945
+ : typeof dialog === "number"
2946
+ ? dialog
2947
+ : 2;
2948
+
2949
+ if (response === 0) {
2950
+ try {
2951
+ Utils.clipboardWriteText(report);
2952
+ Utils.showNotification({
2953
+ title: "Crash report copied",
2954
+ body: "Paste in Discord and ping @iono.",
2955
+ });
2956
+ } catch (err) {
2957
+ logger.warn(
2958
+ `[Main] Failed to copy startup crash report: ${err instanceof Error ? err.message : String(err)}`,
2959
+ );
2960
+ }
2961
+ } else if (response === 1) {
2962
+ try {
2963
+ Utils.openPath(path.dirname(reportPath));
2964
+ } catch (err) {
2965
+ logger.warn(
2966
+ `[Main] Failed to open startup logs folder: ${err instanceof Error ? err.message : String(err)}`,
2967
+ );
2968
+ }
2969
+ }
2460
2970
  }
2461
2971
 
2462
2972
  main().catch((err) => {
2463
- const msg = `[Main] Fatal error during startup: ${err?.stack ?? err}`;
2464
- console.error(msg);
2465
- recordStartupPhase("fatal", {
2466
- pid: process.pid,
2467
- exec_path: process.execPath,
2468
- bundle_path: resolveStartupBundlePath(process.execPath),
2469
- error: err instanceof Error ? err.stack || err.message : String(err),
2470
- });
2471
- persistStartupCrashReport({
2472
- source: "fatal-startup",
2473
- error: msg,
2474
- });
2475
- recordStartupPhase("fatal", {
2476
- pid: process.pid,
2477
- exec_path: process.execPath,
2478
- bundle_path: resolveStartupBundlePath(process.execPath),
2479
- error: err instanceof Error ? err.stack || err.message : String(err),
2480
- });
2481
- // Write to startup log so it's visible even without a console
2482
- try {
2483
- const logPath = getDiagnosticLogPath();
2484
- fs.mkdirSync(path.dirname(logPath), { recursive: true });
2485
- fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${msg}\n`);
2486
- fs.writeFileSync(
2487
- getStartupStatusPath(),
2488
- `${JSON.stringify(
2489
- {
2490
- state: "error",
2491
- phase: "fatal_startup",
2492
- updatedAt: new Date().toISOString(),
2493
- lastError: msg,
2494
- platform: process.platform,
2495
- arch: process.arch,
2496
- logPath,
2497
- statusPath: getStartupStatusPath(),
2498
- },
2499
- null,
2500
- 2,
2501
- )}\n`,
2502
- "utf8",
2503
- );
2504
- } catch {}
2505
- void runShutdownCleanup("fatal-startup").finally(shutdownAfterFatalError);
2973
+ const msg = `[Main] Fatal error during startup: ${err?.stack ?? err}`;
2974
+ console.error(msg);
2975
+ recordStartupPhase("fatal", {
2976
+ pid: process.pid,
2977
+ exec_path: process.execPath,
2978
+ bundle_path: resolveStartupBundlePath(process.execPath),
2979
+ error: err instanceof Error ? err.stack || err.message : String(err),
2980
+ });
2981
+ persistStartupCrashReport({
2982
+ source: "fatal-startup",
2983
+ error: msg,
2984
+ });
2985
+ recordStartupPhase("fatal", {
2986
+ pid: process.pid,
2987
+ exec_path: process.execPath,
2988
+ bundle_path: resolveStartupBundlePath(process.execPath),
2989
+ error: err instanceof Error ? err.stack || err.message : String(err),
2990
+ });
2991
+ // Write to startup log so it's visible even without a console
2992
+ try {
2993
+ const logPath = getDiagnosticLogPath();
2994
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
2995
+ fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${msg}\n`);
2996
+ fs.writeFileSync(
2997
+ getStartupStatusPath(),
2998
+ `${JSON.stringify(
2999
+ {
3000
+ state: "error",
3001
+ phase: "fatal_startup",
3002
+ updatedAt: new Date().toISOString(),
3003
+ lastError: msg,
3004
+ platform: process.platform,
3005
+ arch: process.arch,
3006
+ logPath,
3007
+ statusPath: getStartupStatusPath(),
3008
+ },
3009
+ null,
3010
+ 2,
3011
+ )}\n`,
3012
+ "utf8",
3013
+ );
3014
+ } catch {}
3015
+ void runShutdownCleanup("fatal-startup").finally(shutdownAfterFatalError);
2506
3016
  });
2507
3017
 
2508
3018
  import { shutdownAfterFatalError } from "./fatal-shutdown";