@elizaos/app-core 2.0.0-beta.2 → 2.0.11-beta.5

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