@elizaos/app-core 2.0.0-beta.3 → 2.0.11-beta.6

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 (1530) 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.js +5 -5
  88. package/api/server-cors.d.ts.map +1 -1
  89. package/api/server-cors.js +13 -2
  90. package/api/server-first-run-helpers.d.ts +26 -0
  91. package/api/server-first-run-helpers.d.ts.map +1 -0
  92. package/api/server-first-run-helpers.js +271 -0
  93. package/api/server-security.js +1 -1
  94. package/api/server-startup.d.ts.map +1 -1
  95. package/api/server-startup.js +3 -4
  96. package/api/server-wallet-trade.js +1 -1
  97. package/api/server.d.ts +4 -4
  98. package/api/server.d.ts.map +1 -1
  99. package/api/server.js +222 -88
  100. package/api/setup-contract.d.ts +63 -0
  101. package/api/setup-contract.d.ts.map +1 -0
  102. package/api/setup-contract.js +39 -0
  103. package/api/training-benchmarks.d.ts +97 -0
  104. package/api/training-benchmarks.d.ts.map +1 -0
  105. package/api/training-benchmarks.js +307 -0
  106. package/api/workbench-compat-routes.js +2 -2
  107. package/benchmark/cerebras-autowire.d.ts +28 -0
  108. package/benchmark/cerebras-autowire.d.ts.map +1 -0
  109. package/benchmark/cerebras-autowire.js +62 -0
  110. package/benchmark/lifeops-bench-handler.d.ts +36 -0
  111. package/benchmark/lifeops-bench-handler.d.ts.map +1 -1
  112. package/benchmark/lifeops-bench-handler.js +63 -1
  113. package/benchmark/lifeops-fake-backend.d.ts +39 -0
  114. package/benchmark/lifeops-fake-backend.d.ts.map +1 -1
  115. package/benchmark/lifeops-fake-backend.js +993 -21
  116. package/benchmark/mock-plugin.d.ts.map +1 -1
  117. package/benchmark/mock-plugin.js +0 -24
  118. package/benchmark/plugin.d.ts +2 -1
  119. package/benchmark/plugin.d.ts.map +1 -1
  120. package/benchmark/plugin.js +989 -68
  121. package/benchmark/replay-capture.d.ts +2 -2
  122. package/benchmark/replay-capture.d.ts.map +1 -1
  123. package/benchmark/replay-capture.js +3 -3
  124. package/benchmark/server-utils.d.ts +162 -9
  125. package/benchmark/server-utils.d.ts.map +1 -1
  126. package/benchmark/server-utils.js +625 -62
  127. package/benchmark/server.d.ts.map +1 -1
  128. package/benchmark/server.js +1962 -118
  129. package/boot-profile.d.ts +3 -0
  130. package/boot-profile.d.ts.map +1 -0
  131. package/boot-profile.js +30 -0
  132. package/browser.d.ts +23 -1
  133. package/browser.d.ts.map +1 -1
  134. package/browser.js +20 -1
  135. package/cli/argv.js +1 -1
  136. package/cli/banner.js +1 -1
  137. package/cli/command-format.js +2 -2
  138. package/cli/doctor/checks.d.ts.map +1 -1
  139. package/cli/doctor/checks.js +6 -6
  140. package/cli/plugins-cli.d.ts.map +1 -1
  141. package/cli/plugins-cli.js +77 -32
  142. package/cli/profile.d.ts.map +1 -1
  143. package/cli/profile.js +5 -4
  144. package/cli/program/build-program.js +4 -4
  145. package/cli/program/command-registry.d.ts.map +1 -1
  146. package/cli/program/command-registry.js +13 -11
  147. package/cli/program/help.js +5 -5
  148. package/cli/program/preaction.js +5 -5
  149. package/cli/program/register.auth.d.ts.map +1 -1
  150. package/cli/program/register.auth.js +6 -12
  151. package/cli/program/register.capability-router.d.ts +29 -0
  152. package/cli/program/register.capability-router.d.ts.map +1 -0
  153. package/cli/program/register.capability-router.js +568 -0
  154. package/cli/program/register.config.js +1 -1
  155. package/cli/program/register.configure.d.ts.map +1 -1
  156. package/cli/program/register.configure.js +1 -1
  157. package/cli/program/register.dashboard.d.ts.map +1 -1
  158. package/cli/program/register.dashboard.js +6 -7
  159. package/cli/program/register.db.d.ts.map +1 -1
  160. package/cli/program/register.db.js +3 -4
  161. package/cli/program/register.doctor.js +7 -7
  162. package/cli/program/register.setup.d.ts.map +1 -1
  163. package/cli/program/register.setup.js +14 -10
  164. package/cli/program/register.start.d.ts.map +1 -1
  165. package/cli/program/register.start.js +5 -3
  166. package/cli/program/register.subclis.js +3 -3
  167. package/cli/program/register.update.d.ts +6 -0
  168. package/cli/program/register.update.d.ts.map +1 -1
  169. package/cli/program/register.update.js +58 -6
  170. package/cli/program.js +1 -1
  171. package/cli/run-main.js +4 -4
  172. package/config/app-config.d.ts +2 -0
  173. package/config/app-config.d.ts.map +1 -0
  174. package/config/app-config.js +1 -0
  175. package/connectors/capacitor-jsc.d.ts.map +1 -1
  176. package/connectors/capacitor-jsc.js +16 -10
  177. package/connectors/capacitor-quickjs.d.ts.map +1 -1
  178. package/connectors/capacitor-quickjs.js +18 -13
  179. package/connectors/capacitor-sqlite.d.ts.map +1 -1
  180. package/connectors/capacitor-sqlite.js +27 -12
  181. package/dispatch/approval-queue.d.ts +37 -0
  182. package/dispatch/approval-queue.d.ts.map +1 -0
  183. package/dispatch/approval-queue.js +25 -0
  184. package/dispatch/channel-registry.d.ts +30 -0
  185. package/dispatch/channel-registry.d.ts.map +1 -0
  186. package/dispatch/channel-registry.js +22 -0
  187. package/dispatch/connector-registry.d.ts +39 -0
  188. package/dispatch/connector-registry.d.ts.map +1 -0
  189. package/dispatch/connector-registry.js +24 -0
  190. package/dispatch/index.d.ts +14 -0
  191. package/dispatch/index.d.ts.map +1 -0
  192. package/dispatch/index.js +13 -0
  193. package/dispatch/send-policy.d.ts +36 -0
  194. package/dispatch/send-policy.d.ts.map +1 -0
  195. package/dispatch/send-policy.js +16 -0
  196. package/entry.js +28 -11
  197. package/first-run/first-run-config.d.ts +55 -0
  198. package/first-run/first-run-config.d.ts.map +1 -0
  199. package/first-run/first-run-config.js +178 -0
  200. package/first-run/runtime-target.d.ts +4 -0
  201. package/first-run/runtime-target.d.ts.map +1 -0
  202. package/first-run/runtime-target.js +13 -0
  203. package/index.d.ts +16 -3
  204. package/index.d.ts.map +1 -1
  205. package/index.js +57 -33
  206. package/package.json +159 -50
  207. package/packaging/debian/apt-repo-config/README.md +18 -0
  208. package/packaging/debian/apt-repo-config/conf/distributions +11 -0
  209. package/packaging/flatpak/README.md +26 -16
  210. package/packaging/flatpak/ai.elizaos.App.metainfo.xml +17 -12
  211. package/packaging/flatpak/ai.elizaos.App.store.yml +5 -5
  212. package/packaging/flatpak/ai.elizaos.App.yml +10 -24
  213. package/packaging/flatpak/elizaos-app-wrapper.store.sh +2 -2
  214. package/packaging/flatpak/generate-sources.sh +74 -0
  215. package/packaging/flatpak/node-sources.json +7930 -0
  216. package/packaging/inno/build-inno.ps1 +34 -9
  217. package/packaging/msix/AppxManifest.store.xml +1 -1
  218. package/packaging/msix/README.md +39 -19
  219. package/packaging/msix/build-msix.ps1 +44 -14
  220. package/packaging/snap/snapcraft.yaml +22 -21
  221. package/packaging/test-packaging.sh +2 -2
  222. package/permissions/types.d.ts +1 -1
  223. package/permissions/types.js +1 -1
  224. package/platform/elizaos-agent-browser-stub.d.ts +144 -0
  225. package/platform/elizaos-agent-browser-stub.d.ts.map +1 -0
  226. package/platform/elizaos-agent-browser-stub.js +158 -0
  227. package/platform/elizaos-plugin-elizacloud-browser-stub.d.ts +34 -0
  228. package/platform/elizaos-plugin-elizacloud-browser-stub.d.ts.map +1 -0
  229. package/platform/elizaos-plugin-elizacloud-browser-stub.js +51 -0
  230. package/platform/empty-node-module.d.ts +148 -0
  231. package/platform/empty-node-module.d.ts.map +1 -1
  232. package/platform/empty-node-module.js +140 -3
  233. package/platform/ios-runtime-backends.d.ts +83 -0
  234. package/platform/ios-runtime-backends.d.ts.map +1 -0
  235. package/platform/ios-runtime-backends.js +133 -0
  236. package/platform/ios-runtime-bridge.d.ts +15 -0
  237. package/platform/ios-runtime-bridge.d.ts.map +1 -0
  238. package/platform/ios-runtime-bridge.js +527 -0
  239. package/platform/native-library-policy.d.ts +23 -0
  240. package/platform/native-library-policy.d.ts.map +1 -0
  241. package/platform/native-library-policy.js +112 -0
  242. package/platform/native-plugin-entrypoints.d.ts +19 -0
  243. package/platform/native-plugin-entrypoints.d.ts.map +1 -0
  244. package/platform/native-plugin-entrypoints.js +29 -0
  245. package/platforms/android/README.md +68 -10
  246. package/platforms/android/app/build.gradle +268 -3
  247. package/platforms/android/app/capacitor.build.gradle +18 -1
  248. package/platforms/android/app/proguard-rules.pro +17 -2
  249. package/platforms/android/app/src/androidTest/java/ai/elizaos/app/ElizaOsInstrumentedTest.java +1 -1
  250. package/platforms/android/app/src/main/AndroidManifest.xml +334 -17
  251. package/platforms/android/app/src/main/assets/runners/eliza-tasks.js +177 -0
  252. package/platforms/android/app/src/main/elizavoice-jni/CMakeLists.txt +100 -0
  253. package/platforms/android/app/src/main/elizavoice-jni/elizavoice-jni.cpp +1349 -0
  254. package/platforms/android/app/src/main/java/ai/elizaos/app/AgentPlugin.java +111 -171
  255. package/platforms/android/app/src/main/java/ai/elizaos/app/AndroidVirtualizationBridge.java +284 -0
  256. package/platforms/android/app/src/main/java/ai/elizaos/app/BatteryOptimizationPlugin.java +95 -0
  257. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAccessibilityService.java +55 -0
  258. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAgentService.java +1198 -141
  259. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAndroidSystemBridge.java +83 -0
  260. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAssistActivity.java +50 -1
  261. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBootReceiver.java +90 -8
  262. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBrowserActivity.java +2 -2
  263. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaCalendarActivity.java +1 -1
  264. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaCameraActivity.java +1 -1
  265. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaClockActivity.java +2 -2
  266. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaContactsActivity.java +1 -1
  267. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaDialActivity.java +1 -1
  268. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaInCallService.java +1 -1
  269. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaMmsReceiver.java +1 -1
  270. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaNativeBridge.java +22 -0
  271. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaNotificationListenerService.java +45 -0
  272. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaQuickActionsWidgetProvider.java +68 -0
  273. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaShareActivity.java +132 -0
  274. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsComposeActivity.java +1 -1
  275. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsGatewayService.java +268 -0
  276. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsReceiver.java +12 -1
  277. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaTasksWorker.java +194 -0
  278. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceCaptureService.java +198 -0
  279. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceNative.java +205 -0
  280. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoicePlugin.java +498 -0
  281. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceTileService.java +39 -0
  282. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaWorkScheduler.java +60 -0
  283. package/platforms/android/app/src/main/java/ai/elizaos/app/GatewayConnectionService.java +53 -19
  284. package/platforms/android/app/src/main/java/ai/elizaos/app/MainActivity.java +160 -33
  285. package/platforms/android/app/src/main/java/ai/elizaos/app/ResourceProbePlugin.java +169 -0
  286. package/platforms/android/app/src/main/java/ai/elizaos/app/VoiceCapturePlugin.java +119 -0
  287. package/platforms/android/app/src/main/res/drawable/eliza_widget_background.xml +10 -0
  288. package/platforms/android/app/src/main/res/drawable/eliza_widget_button_background.xml +13 -0
  289. package/platforms/android/app/src/main/res/drawable/splash.png +0 -0
  290. package/platforms/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  291. package/platforms/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  292. package/platforms/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  293. package/platforms/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  294. package/platforms/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  295. package/platforms/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  296. package/platforms/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  297. package/platforms/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  298. package/platforms/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  299. package/platforms/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  300. package/platforms/android/app/src/main/res/layout/eliza_quick_actions_widget.xml +86 -0
  301. package/platforms/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +2 -1
  302. package/platforms/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +2 -1
  303. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  304. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  305. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
  306. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  307. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  308. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  309. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
  310. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  311. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  312. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  313. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
  314. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  315. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  316. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  317. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
  318. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  319. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  320. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  321. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
  322. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  323. package/platforms/android/app/src/main/res/values/android_app_actions.xml +48 -0
  324. package/platforms/android/app/src/main/res/values/colors.xml +8 -0
  325. package/platforms/android/app/src/main/res/values/ic_launcher_background.xml +2 -2
  326. package/platforms/android/app/src/main/res/values/strings.xml +2 -2
  327. package/platforms/android/app/src/main/res/values/styles.xml +25 -1
  328. package/platforms/android/app/src/main/res/xml/eliza_accessibility_service.xml +9 -0
  329. package/platforms/android/app/src/main/res/xml/eliza_quick_actions_widget.xml +13 -0
  330. package/platforms/android/app/src/main/res/xml/shortcuts.xml +121 -0
  331. package/platforms/android/build.gradle +2 -2
  332. package/platforms/android/capacitor-cordova-android-plugins/build.gradle +9 -3
  333. package/platforms/android/capacitor-cordova-android-plugins/cordova.variables.gradle +6 -2
  334. package/platforms/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml +7 -2
  335. package/platforms/android/capacitor-cordova-android-plugins/src/main/java/.gitkeep +0 -1
  336. package/platforms/android/capacitor.settings.gradle +66 -16
  337. package/platforms/android/gradle.properties +1 -0
  338. package/platforms/android/update-manifest/generate-manifest.mjs +97 -0
  339. package/platforms/android/update-manifest/schema.json +26 -0
  340. package/platforms/apple-store-entitlements.reviewed.json +155 -0
  341. package/platforms/electrobun/.generated/brand-config.json +3 -2
  342. package/platforms/electrobun/LICENSE +21 -0
  343. package/platforms/electrobun/README.md +15 -1
  344. package/platforms/electrobun/assets/appIcon.icns +0 -0
  345. package/platforms/electrobun/assets/appIcon.ico +0 -0
  346. package/platforms/electrobun/assets/appIcon.iconset/icon_128x128.png +0 -0
  347. package/platforms/electrobun/assets/appIcon.iconset/icon_128x128@2x.png +0 -0
  348. package/platforms/electrobun/assets/appIcon.iconset/icon_16x16.png +0 -0
  349. package/platforms/electrobun/assets/appIcon.iconset/icon_16x16@2x.png +0 -0
  350. package/platforms/electrobun/assets/appIcon.iconset/icon_256x256.png +0 -0
  351. package/platforms/electrobun/assets/appIcon.iconset/icon_256x256@2x.png +0 -0
  352. package/platforms/electrobun/assets/appIcon.iconset/icon_32x32.png +0 -0
  353. package/platforms/electrobun/assets/appIcon.iconset/icon_32x32@2x.png +0 -0
  354. package/platforms/electrobun/assets/appIcon.iconset/icon_512x512.png +0 -0
  355. package/platforms/electrobun/assets/brand-config.json +6 -6
  356. package/platforms/electrobun/biome.json +9 -9
  357. package/platforms/electrobun/docs/capability-collapse-matrix.json +318 -0
  358. package/platforms/electrobun/docs/capability-collapse-matrix.md +129 -0
  359. package/platforms/electrobun/docs/capability-routing.md +86 -0
  360. package/platforms/electrobun/docs/convergence-audit.json +3505 -0
  361. package/platforms/electrobun/docs/convergence-audit.md +694 -0
  362. package/platforms/electrobun/docs/database-boot-policy.md +90 -0
  363. package/platforms/electrobun/docs/riscv64-port.md +175 -0
  364. package/platforms/electrobun/docs/startup-first-run-cleanup.md +18 -0
  365. package/platforms/electrobun/docs/trace-first-annotations.md +52 -0
  366. package/platforms/electrobun/docs/ui-boundary-audit.json +580 -0
  367. package/platforms/electrobun/docs/ui-boundary-audit.md +257 -0
  368. package/platforms/electrobun/electrobun.config.ts +592 -364
  369. package/platforms/electrobun/entitlements/JUSTIFICATIONS.md +141 -0
  370. package/platforms/electrobun/entitlements/README.md +34 -6
  371. package/platforms/electrobun/entitlements/mas-bun.entitlements +15 -0
  372. package/platforms/electrobun/entitlements/mas.entitlements +6 -4
  373. package/platforms/electrobun/native/macos/window-effects.mm +1522 -0
  374. package/platforms/electrobun/package.json +18 -12
  375. package/platforms/electrobun/remotes/fs/README.md +70 -0
  376. package/platforms/electrobun/remotes/fs/electrobun.config.ts +38 -0
  377. package/platforms/electrobun/remotes/fs/package.json +12 -0
  378. package/platforms/electrobun/remotes/fs/plugin.json +25 -0
  379. package/platforms/electrobun/remotes/fs/src/bun/errors.ts +57 -0
  380. package/platforms/electrobun/remotes/fs/src/bun/file-limits.ts +50 -0
  381. package/platforms/electrobun/remotes/fs/src/bun/fs-service.ts +389 -0
  382. package/platforms/electrobun/remotes/fs/src/bun/path-guard.ts +270 -0
  383. package/platforms/electrobun/remotes/fs/src/bun/protocol.ts +149 -0
  384. package/platforms/electrobun/remotes/fs/src/bun/worker.ts +174 -0
  385. package/platforms/electrobun/remotes/fs/src/dev/phase5-smoke.ts +171 -0
  386. package/platforms/electrobun/remotes/fs/src/web/index.html +8 -0
  387. package/platforms/electrobun/remotes/git/README.md +75 -0
  388. package/platforms/electrobun/remotes/git/electrobun.config.ts +44 -0
  389. package/platforms/electrobun/remotes/git/package.json +12 -0
  390. package/platforms/electrobun/remotes/git/plugin.json +31 -0
  391. package/platforms/electrobun/remotes/git/src/bun/errors.ts +69 -0
  392. package/platforms/electrobun/remotes/git/src/bun/git-command.ts +156 -0
  393. package/platforms/electrobun/remotes/git/src/bun/git-service.ts +446 -0
  394. package/platforms/electrobun/remotes/git/src/bun/operation-history.ts +124 -0
  395. package/platforms/electrobun/remotes/git/src/bun/protocol.ts +252 -0
  396. package/platforms/electrobun/remotes/git/src/bun/worker.ts +316 -0
  397. package/platforms/electrobun/remotes/git/src/dev/phase7-smoke.ts +141 -0
  398. package/platforms/electrobun/remotes/git/src/web/index.html +8 -0
  399. package/platforms/electrobun/remotes/local-model/README.md +138 -0
  400. package/platforms/electrobun/remotes/local-model/electrobun.config.ts +46 -0
  401. package/platforms/electrobun/remotes/local-model/package.json +12 -0
  402. package/platforms/electrobun/remotes/local-model/plugin.json +33 -0
  403. package/platforms/electrobun/remotes/local-model/src/bun/download-state.ts +115 -0
  404. package/platforms/electrobun/remotes/local-model/src/bun/eliza1-catalog.ts +425 -0
  405. package/platforms/electrobun/remotes/local-model/src/bun/errors.ts +74 -0
  406. package/platforms/electrobun/remotes/local-model/src/bun/hf-eliza1-client.ts +169 -0
  407. package/platforms/electrobun/remotes/local-model/src/bun/local-inference-api-client.ts +245 -0
  408. package/platforms/electrobun/remotes/local-model/src/bun/model-service.ts +490 -0
  409. package/platforms/electrobun/remotes/local-model/src/bun/protocol.ts +301 -0
  410. package/platforms/electrobun/remotes/local-model/src/bun/worker.ts +248 -0
  411. package/platforms/electrobun/remotes/local-model/src/dev/phase8-smoke.ts +117 -0
  412. package/platforms/electrobun/remotes/local-model/src/web/index.html +13 -0
  413. package/platforms/electrobun/remotes/pty/README.md +65 -0
  414. package/platforms/electrobun/remotes/pty/electrobun.config.ts +47 -0
  415. package/platforms/electrobun/remotes/pty/package.json +12 -0
  416. package/platforms/electrobun/remotes/pty/plugin.json +34 -0
  417. package/platforms/electrobun/remotes/pty/src/bun/errors.ts +57 -0
  418. package/platforms/electrobun/remotes/pty/src/bun/output-buffer.ts +127 -0
  419. package/platforms/electrobun/remotes/pty/src/bun/protocol.ts +192 -0
  420. package/platforms/electrobun/remotes/pty/src/bun/pty-service.ts +562 -0
  421. package/platforms/electrobun/remotes/pty/src/bun/worker.ts +218 -0
  422. package/platforms/electrobun/remotes/pty/src/dev/phase6-smoke.ts +127 -0
  423. package/platforms/electrobun/remotes/pty/src/web/index.html +8 -0
  424. package/platforms/electrobun/remotes/runtime/README.md +370 -0
  425. package/platforms/electrobun/remotes/runtime/electrobun.config.ts +48 -0
  426. package/platforms/electrobun/remotes/runtime/package.json +14 -0
  427. package/platforms/electrobun/remotes/runtime/plugin.json +30 -0
  428. package/platforms/electrobun/remotes/runtime/src/bun/api-client.ts +620 -0
  429. package/platforms/electrobun/remotes/runtime/src/bun/errors.ts +45 -0
  430. package/platforms/electrobun/remotes/runtime/src/bun/log-buffer.ts +33 -0
  431. package/platforms/electrobun/remotes/runtime/src/bun/protocol.ts +366 -0
  432. package/platforms/electrobun/remotes/runtime/src/bun/route-discovery.ts +419 -0
  433. package/platforms/electrobun/remotes/runtime/src/bun/runtime-manager.ts +423 -0
  434. package/platforms/electrobun/remotes/runtime/src/bun/sse-parser.ts +99 -0
  435. package/platforms/electrobun/remotes/runtime/src/bun/stream-manager.ts +887 -0
  436. package/platforms/electrobun/remotes/runtime/src/bun/worker.ts +1231 -0
  437. package/platforms/electrobun/remotes/runtime/src/dev/phase1-smoke.ts +34 -0
  438. package/platforms/electrobun/remotes/runtime/src/dev/phase2-smoke.ts +86 -0
  439. package/platforms/electrobun/remotes/runtime/src/dev/phase3-smoke.ts +141 -0
  440. package/platforms/electrobun/remotes/runtime/src/web/index.css +187 -0
  441. package/platforms/electrobun/remotes/runtime/src/web/index.html +76 -0
  442. package/platforms/electrobun/remotes/runtime/src/web/index.ts +192 -0
  443. package/platforms/electrobun/remotes/surface/README.md +201 -0
  444. package/platforms/electrobun/remotes/surface/electrobun.config.ts +38 -0
  445. package/platforms/electrobun/remotes/surface/package.json +12 -0
  446. package/platforms/electrobun/remotes/surface/plugin.json +28 -0
  447. package/platforms/electrobun/remotes/surface/src/bun/worker.ts +132 -0
  448. package/platforms/electrobun/remotes/surface/src/dev/phase4-smoke.ts +566 -0
  449. package/platforms/electrobun/remotes/surface/src/protocol/event-types.ts +84 -0
  450. package/platforms/electrobun/remotes/surface/src/protocol/runtime-client.ts +673 -0
  451. package/platforms/electrobun/remotes/surface/src/web/app.ts +595 -0
  452. package/platforms/electrobun/remotes/surface/src/web/index.css +460 -0
  453. package/platforms/electrobun/remotes/surface/src/web/index.html +466 -0
  454. package/platforms/electrobun/remotes/surface/src/web/index.ts +5 -0
  455. package/platforms/electrobun/remotes/surface/src/web/render.ts +455 -0
  456. package/platforms/electrobun/remotes/surface/src/web/state.ts +427 -0
  457. package/platforms/electrobun/scripts/build-macos-effects.sh +4 -0
  458. package/platforms/electrobun/scripts/ensure-build-folder.ts +28 -0
  459. package/platforms/electrobun/scripts/ensure-whisper-gguf.sh +55 -0
  460. package/platforms/electrobun/scripts/ensure-whisper-model.sh +22 -80
  461. package/platforms/electrobun/scripts/generate-convergence-audit.ts +1203 -0
  462. package/platforms/electrobun/scripts/local-adhoc-sign-macos.ts +159 -159
  463. package/platforms/electrobun/scripts/postwrap-diagnostics.ts +424 -339
  464. package/platforms/electrobun/scripts/postwrap-sign-runtime-macos.ts +302 -271
  465. package/platforms/electrobun/scripts/smoke-test-windows.ps1 +17 -16
  466. package/platforms/electrobun/scripts/smoke-test.sh +5 -7
  467. package/platforms/electrobun/scripts/sync-web-assets.mjs +13 -13
  468. package/platforms/electrobun/scripts/verify-rpc-handlers.ts +109 -110
  469. package/platforms/electrobun/scripts/verify-windows-installer-proof.ps1 +3 -8
  470. package/platforms/electrobun/src/__stubs__/bun-ffi.ts +31 -31
  471. package/platforms/electrobun/src/__stubs__/electrobun-bun.ts +1 -1
  472. package/platforms/electrobun/src/agent-ready-state.ts +8 -8
  473. package/platforms/electrobun/src/agent-reset-from-main.test.ts +162 -0
  474. package/platforms/electrobun/src/agent-reset-from-main.ts +62 -62
  475. package/platforms/electrobun/src/agent-status-rpc.test.ts +95 -0
  476. package/platforms/electrobun/src/agent-status-rpc.ts +156 -0
  477. package/platforms/electrobun/src/api-base.test.ts +247 -0
  478. package/platforms/electrobun/src/api-base.ts +202 -93
  479. package/platforms/electrobun/src/application-menu-action-registry.ts +9 -9
  480. package/platforms/electrobun/src/application-menu.ts +348 -348
  481. package/platforms/electrobun/src/background-notice.ts +36 -36
  482. package/platforms/electrobun/src/boot-progress.test.ts +188 -0
  483. package/platforms/electrobun/src/boot-progress.ts +111 -0
  484. package/platforms/electrobun/src/brand-config.test.ts +39 -0
  485. package/platforms/electrobun/src/brand-config.ts +141 -129
  486. package/platforms/electrobun/src/bridge/browser-tabs-renderer-registry.ts +28 -28
  487. package/platforms/electrobun/src/bridge/electrobun-boot-config.ts +42 -0
  488. package/platforms/electrobun/src/bridge/electrobun-crypto-ready.ts +120 -0
  489. package/platforms/electrobun/src/bridge/electrobun-direct-rpc.ts +342 -357
  490. package/platforms/electrobun/src/bridge/electrobun-stub.ts +13 -13
  491. package/platforms/electrobun/src/browser-workspace-bridge-server.ts +285 -243
  492. package/platforms/electrobun/src/cloud-auth-window.ts +136 -136
  493. package/platforms/electrobun/src/cloud-disconnect-from-main.ts +90 -90
  494. package/platforms/electrobun/src/config-and-auth-rpc.test.ts +256 -0
  495. package/platforms/electrobun/src/config-and-auth-rpc.ts +302 -0
  496. package/platforms/electrobun/src/conversations-and-character-rpc.test.ts +185 -0
  497. package/platforms/electrobun/src/conversations-and-character-rpc.ts +131 -0
  498. package/platforms/electrobun/src/dashboard-rpc.test.ts +200 -0
  499. package/platforms/electrobun/src/dashboard-rpc.ts +344 -0
  500. package/platforms/electrobun/src/database/database-lock.ts +141 -0
  501. package/platforms/electrobun/src/database/database-mode.ts +149 -0
  502. package/platforms/electrobun/src/database/database-recovery.ts +72 -0
  503. package/platforms/electrobun/src/database/database-snapshot.ts +190 -0
  504. package/platforms/electrobun/src/database/database.test.ts +196 -0
  505. package/platforms/electrobun/src/database/index.ts +5 -0
  506. package/platforms/electrobun/src/database/pglite-paths.ts +100 -0
  507. package/platforms/electrobun/src/desktop-deep-link-events.test.ts +30 -0
  508. package/platforms/electrobun/src/desktop-deep-link-events.ts +17 -0
  509. package/platforms/electrobun/src/desktop-http-request.test.ts +73 -73
  510. package/platforms/electrobun/src/desktop-http-request.ts +85 -85
  511. package/platforms/electrobun/src/desktop-pill-config.test.ts +27 -0
  512. package/platforms/electrobun/src/desktop-pill-config.ts +40 -0
  513. package/platforms/electrobun/src/desktop-test-bridge-server.ts +204 -204
  514. package/platforms/electrobun/src/desktop-tray-config.test.ts +87 -0
  515. package/platforms/electrobun/src/desktop-tray-config.ts +84 -0
  516. package/platforms/electrobun/src/devtools-layout.ts +41 -41
  517. package/platforms/electrobun/src/diagnostic-format.test.ts +71 -0
  518. package/platforms/electrobun/src/diagnostic-format.ts +75 -36
  519. package/platforms/electrobun/src/dynamic-view-rpc-schema.test.ts +37 -0
  520. package/platforms/electrobun/src/dynamic-views/README.md +44 -0
  521. package/platforms/electrobun/src/dynamic-views/demo/agent-run-trace.html +135 -0
  522. package/platforms/electrobun/src/dynamic-views/errors.ts +29 -0
  523. package/platforms/electrobun/src/dynamic-views/host.test.ts +353 -0
  524. package/platforms/electrobun/src/dynamic-views/host.ts +332 -0
  525. package/platforms/electrobun/src/dynamic-views/index.ts +57 -0
  526. package/platforms/electrobun/src/dynamic-views/kiosk-canvas.ts +89 -0
  527. package/platforms/electrobun/src/dynamic-views/registry.test.ts +139 -0
  528. package/platforms/electrobun/src/dynamic-views/registry.ts +196 -0
  529. package/platforms/electrobun/src/dynamic-views/session-manager.test.ts +355 -0
  530. package/platforms/electrobun/src/dynamic-views/session-manager.ts +348 -0
  531. package/platforms/electrobun/src/dynamic-views/types.ts +105 -0
  532. package/platforms/electrobun/src/electrobun-boot-config.test.ts +50 -0
  533. package/platforms/electrobun/src/electrobun-config.test.ts +62 -0
  534. package/platforms/electrobun/src/electrobun-crypto-ready.test.ts +65 -0
  535. package/platforms/electrobun/src/electrobun-window-options.ts +25 -0
  536. package/platforms/electrobun/src/extension-rpc.test.ts +88 -0
  537. package/platforms/electrobun/src/extension-rpc.ts +102 -0
  538. package/platforms/electrobun/src/fatal-shutdown.test.ts +10 -10
  539. package/platforms/electrobun/src/fatal-shutdown.ts +1 -1
  540. package/platforms/electrobun/src/first-party-remotes.test.ts +169 -0
  541. package/platforms/electrobun/src/first-party-remotes.ts +297 -0
  542. package/platforms/electrobun/src/first-run-rpc.test.ts +192 -0
  543. package/platforms/electrobun/src/first-run-rpc.ts +146 -0
  544. package/platforms/electrobun/src/floating-chat-window.ts +181 -181
  545. package/platforms/electrobun/src/inbox-rpc.test.ts +123 -0
  546. package/platforms/electrobun/src/inbox-rpc.ts +158 -0
  547. package/platforms/electrobun/src/index.ts +2555 -2096
  548. package/platforms/electrobun/src/kiosk-mode.ts +50 -0
  549. package/platforms/electrobun/src/launch/index.ts +4 -0
  550. package/platforms/electrobun/src/launch/launch-dynamic-view.ts +37 -0
  551. package/platforms/electrobun/src/launch/launch-orchestrator.test.ts +224 -0
  552. package/platforms/electrobun/src/launch/launch-orchestrator.ts +456 -0
  553. package/platforms/electrobun/src/launch/launch-store.test.ts +97 -0
  554. package/platforms/electrobun/src/launch/launch-store.ts +134 -0
  555. package/platforms/electrobun/src/launch/types.ts +103 -0
  556. package/platforms/electrobun/src/launch/views/launch-diagnostics.html +205 -0
  557. package/platforms/electrobun/src/lifecycle/agent-ready-publish.test.ts +50 -0
  558. package/platforms/electrobun/src/lifecycle/agent-ready-publish.ts +27 -0
  559. package/platforms/electrobun/src/lifecycle/api-base-owner.ts +42 -31
  560. package/platforms/electrobun/src/lifecycle/desktop-session-prime.ts +44 -44
  561. package/platforms/electrobun/src/logger.ts +14 -14
  562. package/platforms/electrobun/src/main-window-runtime.ts +83 -83
  563. package/platforms/electrobun/src/main-window-session.test.ts +109 -0
  564. package/platforms/electrobun/src/main-window-session.ts +87 -51
  565. package/platforms/electrobun/src/menu-reset-from-main.ts +158 -158
  566. package/platforms/electrobun/src/native/agent-env.test.ts +52 -0
  567. package/platforms/electrobun/src/native/agent-runtime-layout.test.ts +42 -0
  568. package/platforms/electrobun/src/native/agent-state-dir.test.ts +91 -0
  569. package/platforms/electrobun/src/native/agent.ts +2122 -1682
  570. package/platforms/electrobun/src/native/auth-bridge.test.ts +67 -0
  571. package/platforms/electrobun/src/native/auth-bridge.ts +464 -360
  572. package/platforms/electrobun/src/native/browser-workspace.ts +723 -471
  573. package/platforms/electrobun/src/native/camera.ts +50 -50
  574. package/platforms/electrobun/src/native/canvas.ts +444 -445
  575. package/platforms/electrobun/src/native/credentials.ts +673 -616
  576. package/platforms/electrobun/src/native/desktop-window.test.ts +300 -0
  577. package/platforms/electrobun/src/native/desktop.ts +2196 -2156
  578. package/platforms/electrobun/src/native/editor-bridge.ts +201 -201
  579. package/platforms/electrobun/src/native/file-watcher.ts +154 -154
  580. package/platforms/electrobun/src/native/gateway.ts +179 -180
  581. package/platforms/electrobun/src/native/gpu-window.ts +256 -256
  582. package/platforms/electrobun/src/native/index.ts +76 -74
  583. package/platforms/electrobun/src/native/location.test.ts +44 -0
  584. package/platforms/electrobun/src/native/location.ts +90 -80
  585. package/platforms/electrobun/src/native/loopback-port.ts +60 -60
  586. package/platforms/electrobun/src/native/mac-window-effects.ts +166 -104
  587. package/platforms/electrobun/src/native/music-player.ts +38 -38
  588. package/platforms/electrobun/src/native/permissions-shared.ts +249 -150
  589. package/platforms/electrobun/src/native/permissions.ts +301 -208
  590. package/platforms/electrobun/src/native/power-state.ts +129 -129
  591. package/platforms/electrobun/src/native/remote-plugin-host.test.ts +1394 -0
  592. package/platforms/electrobun/src/native/remote-plugin-host.ts +1531 -0
  593. package/platforms/electrobun/src/native/screencapture.ts +667 -573
  594. package/platforms/electrobun/src/native/steward.ts +207 -204
  595. package/platforms/electrobun/src/native/swabble.ts +68 -324
  596. package/platforms/electrobun/src/native/talkmode.ts +253 -422
  597. package/platforms/electrobun/src/native/webgpu-browser-support.test.ts +18 -0
  598. package/platforms/electrobun/src/native/webgpu-browser-support.ts +165 -147
  599. package/platforms/electrobun/src/native/whisper-env.test.ts +71 -0
  600. package/platforms/electrobun/src/native/whisper-env.ts +68 -0
  601. package/platforms/electrobun/src/native-onboarding.ts +270 -0
  602. package/platforms/electrobun/src/onboarding-overlay-window.ts +141 -0
  603. package/platforms/electrobun/src/persisted-deployment.ts +91 -0
  604. package/platforms/electrobun/src/pill-window.test.ts +91 -0
  605. package/platforms/electrobun/src/pill-window.ts +99 -0
  606. package/platforms/electrobun/src/preload-validation.ts +44 -44
  607. package/platforms/electrobun/src/preload.js +1 -1
  608. package/platforms/electrobun/src/print-electrobun-dev-settings-banner.ts +120 -120
  609. package/platforms/electrobun/src/renderer-api-proxy.test.ts +73 -0
  610. package/platforms/electrobun/src/renderer-api-proxy.ts +86 -0
  611. package/platforms/electrobun/src/renderer-static.test.ts +53 -0
  612. package/platforms/electrobun/src/renderer-static.ts +144 -57
  613. package/platforms/electrobun/src/rpc-handler-slices.ts +121 -0
  614. package/platforms/electrobun/src/rpc-handlers.test.ts +267 -0
  615. package/platforms/electrobun/src/rpc-handlers.ts +1306 -913
  616. package/platforms/electrobun/src/rpc-parse-utils.ts +57 -0
  617. package/platforms/electrobun/src/rpc-port-resolver.test.ts +45 -0
  618. package/platforms/electrobun/src/rpc-port-resolver.ts +31 -0
  619. package/platforms/electrobun/src/rpc-schema.ts +2556 -1619
  620. package/platforms/electrobun/src/runtime-layout.ts +105 -105
  621. package/platforms/electrobun/src/runtime-permissions.ts +95 -95
  622. package/platforms/electrobun/src/runtime-rpc.test.ts +126 -0
  623. package/platforms/electrobun/src/runtime-rpc.ts +237 -0
  624. package/platforms/electrobun/src/screenshot-dev-server.ts +87 -87
  625. package/platforms/electrobun/src/settings-mutations-rpc.test.ts +193 -0
  626. package/platforms/electrobun/src/settings-mutations-rpc.ts +220 -0
  627. package/platforms/electrobun/src/startup-trace.ts +274 -270
  628. package/platforms/electrobun/src/subscription-rpc.test.ts +89 -0
  629. package/platforms/electrobun/src/subscription-rpc.ts +192 -0
  630. package/platforms/electrobun/src/surface-windows.test.ts +355 -0
  631. package/platforms/electrobun/src/surface-windows.ts +410 -410
  632. package/platforms/electrobun/src/trace/README.md +73 -0
  633. package/platforms/electrobun/src/trace/errors.ts +21 -0
  634. package/platforms/electrobun/src/trace/index.ts +40 -0
  635. package/platforms/electrobun/src/trace/trace-dynamic-view.ts +40 -0
  636. package/platforms/electrobun/src/trace/trace-host-requests.ts +473 -0
  637. package/platforms/electrobun/src/trace/trace-service.test.ts +186 -0
  638. package/platforms/electrobun/src/trace/trace-service.ts +324 -0
  639. package/platforms/electrobun/src/trace/trace-store.test.ts +141 -0
  640. package/platforms/electrobun/src/trace/trace-store.ts +551 -0
  641. package/platforms/electrobun/src/trace/types.ts +250 -0
  642. package/platforms/electrobun/src/trace/views/agent-run-trace.html +311 -0
  643. package/platforms/electrobun/src/types/web-speech.d.ts +28 -28
  644. package/platforms/electrobun/src/types.ts +5 -5
  645. package/platforms/electrobun/src/update-availability.test.ts +72 -0
  646. package/platforms/electrobun/src/update-availability.ts +90 -0
  647. package/platforms/electrobun/src/update-rpc.test.ts +83 -0
  648. package/platforms/electrobun/src/update-rpc.ts +123 -0
  649. package/platforms/electrobun/src/voice/README.md +184 -0
  650. package/platforms/electrobun/src/voice/errors.ts +42 -0
  651. package/platforms/electrobun/src/voice/index.ts +78 -0
  652. package/platforms/electrobun/src/voice/types.ts +316 -0
  653. package/platforms/electrobun/src/voice/voice-host-requests.ts +259 -0
  654. package/platforms/electrobun/src/voice/voice-latency-budget.test.ts +66 -0
  655. package/platforms/electrobun/src/voice/voice-latency-budget.ts +243 -0
  656. package/platforms/electrobun/src/voice/voice-live-validation.test.ts +352 -0
  657. package/platforms/electrobun/src/voice/voice-live-validation.ts +838 -0
  658. package/platforms/electrobun/src/voice/voice-pipeline.ts +250 -0
  659. package/platforms/electrobun/src/voice/voice-playback-adapter.ts +31 -0
  660. package/platforms/electrobun/src/voice/voice-runtime-adapter.test.ts +213 -0
  661. package/platforms/electrobun/src/voice/voice-runtime-adapter.ts +686 -0
  662. package/platforms/electrobun/src/voice/voice-service.test.ts +561 -0
  663. package/platforms/electrobun/src/voice/voice-service.ts +1027 -0
  664. package/platforms/electrobun/src/voice/voice-stream-coordinator.test.ts +115 -0
  665. package/platforms/electrobun/src/voice/voice-stream-coordinator.ts +270 -0
  666. package/platforms/electrobun/src/voice/voice-trace.ts +97 -0
  667. package/platforms/electrobun/src/voice/voice-tts-chunker.test.ts +91 -0
  668. package/platforms/electrobun/src/voice/voice-tts-chunker.ts +194 -0
  669. package/platforms/electrobun/src/windows-cef-profile.ts +88 -88
  670. package/platforms/electrobun/tsconfig.json +73 -13
  671. package/platforms/electrobun/update-channels.json +22 -0
  672. package/platforms/electrobun/vitest.electrobun.config.ts +72 -42
  673. package/platforms/ios/App/App/App.entitlements +4 -0
  674. package/platforms/ios/App/App/AppDelegate.swift +80 -18
  675. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ios-marketing-1024.png +0 -0
  676. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-20x20@1x.png +0 -0
  677. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-20x20@2x.png +0 -0
  678. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-29x29@1x.png +0 -0
  679. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-29x29@2x.png +0 -0
  680. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-40x40@1x.png +0 -0
  681. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-40x40@2x.png +0 -0
  682. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-76x76@1x.png +0 -0
  683. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-76x76@2x.png +0 -0
  684. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-83_5x83_5@2x.png +0 -0
  685. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-20x20@2x.png +0 -0
  686. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-20x20@3x.png +0 -0
  687. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-29x29@2x.png +0 -0
  688. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-29x29@3x.png +0 -0
  689. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-40x40@2x.png +0 -0
  690. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-40x40@3x.png +0 -0
  691. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-60x60@2x.png +0 -0
  692. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-60x60@3x.png +0 -0
  693. package/platforms/ios/App/App/Base.lproj/LaunchScreen.storyboard +1 -4
  694. package/platforms/ios/App/App/ComputerUseBridge.swift +589 -0
  695. package/platforms/ios/App/App/DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.entitlements +12 -0
  696. package/platforms/ios/App/App/DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.swift +34 -0
  697. package/platforms/ios/App/App/DeviceActivityMonitorExtension/Info.plist +29 -0
  698. package/platforms/ios/App/App/DeviceActivityReportExtension/DeviceActivityReportExtension.entitlements +12 -0
  699. package/platforms/ios/App/App/DeviceActivityReportExtension/DeviceActivityReportExtension.swift +53 -0
  700. package/platforms/ios/App/App/DeviceActivityReportExtension/Info.plist +27 -0
  701. package/platforms/ios/App/App/ElizaAppIntents.swift +183 -0
  702. package/platforms/ios/App/App/ElizaIntentPlugin.swift +342 -5
  703. package/platforms/ios/App/App/Info.plist +17 -1
  704. package/platforms/ios/App/App/runners/eliza-tasks.js +177 -0
  705. package/platforms/ios/App/App.xcodeproj/project.pbxproj +262 -6
  706. package/platforms/ios/App/BroadcastExtension/SampleHandler.swift +100 -0
  707. package/platforms/ios/App/Podfile +5 -0
  708. package/platforms/ios/App/Podfile.lock +83 -59
  709. package/register-runtime-hooks.js +11 -5
  710. package/registry/app-registry.d.ts +14 -0
  711. package/registry/app-registry.d.ts.map +1 -0
  712. package/registry/app-registry.js +29 -0
  713. package/registry/entries/apps/app-polymarket.json +31 -0
  714. package/registry/entries/apps/clawville.json +27 -0
  715. package/registry/entries/apps/companion.json +28 -0
  716. package/registry/entries/apps/database-viewer.json +27 -0
  717. package/registry/entries/apps/defense-of-the-agents.json +27 -0
  718. package/registry/entries/apps/documents.json +30 -0
  719. package/registry/entries/apps/feed.json +27 -0
  720. package/registry/entries/apps/hyperliquid.json +31 -0
  721. package/registry/entries/apps/log-viewer.json +27 -0
  722. package/registry/entries/apps/memory-viewer.json +27 -0
  723. package/registry/entries/apps/model-tester.json +31 -0
  724. package/registry/entries/apps/plugin-viewer.json +27 -0
  725. package/registry/entries/apps/relationship-viewer.json +27 -0
  726. package/registry/entries/apps/runtime-debugger.json +27 -0
  727. package/registry/entries/apps/shopify.json +31 -0
  728. package/registry/entries/apps/skills-viewer.json +27 -0
  729. package/registry/entries/apps/steward.json +31 -0
  730. package/registry/entries/apps/training.json +54 -0
  731. package/registry/entries/apps/trajectory-viewer.json +27 -0
  732. package/registry/entries/apps/vincent.json +31 -0
  733. package/registry/entries/connectors/bluebubbles.json +99 -0
  734. package/registry/entries/connectors/bluesky.json +173 -0
  735. package/registry/entries/connectors/discord.json +119 -0
  736. package/registry/entries/connectors/farcaster.json +174 -0
  737. package/registry/entries/connectors/feishu.json +79 -0
  738. package/registry/entries/connectors/google-chat.json +120 -0
  739. package/registry/entries/connectors/google.json +82 -0
  740. package/registry/entries/connectors/imessage.json +96 -0
  741. package/registry/entries/connectors/instagram.json +64 -0
  742. package/registry/entries/connectors/line.json +86 -0
  743. package/registry/entries/connectors/matrix.json +94 -0
  744. package/registry/entries/connectors/mattermost.json +110 -0
  745. package/registry/entries/connectors/msteams.json +104 -0
  746. package/registry/entries/connectors/nextcloud-talk.json +104 -0
  747. package/registry/entries/connectors/nostr.json +70 -0
  748. package/registry/entries/connectors/signal.json +81 -0
  749. package/registry/entries/connectors/slack.json +102 -0
  750. package/registry/entries/connectors/telegram.json +71 -0
  751. package/registry/entries/connectors/tlon.json +94 -0
  752. package/registry/entries/connectors/twitch.json +110 -0
  753. package/registry/entries/connectors/whatsapp.json +113 -0
  754. package/registry/entries/connectors/x.json +231 -0
  755. package/registry/entries/connectors/zalo.json +112 -0
  756. package/registry/entries/connectors/zalouser.json +122 -0
  757. package/registry/entries/plugins/agent-orchestrator.json +33 -0
  758. package/registry/entries/plugins/agent-skills.json +72 -0
  759. package/registry/entries/plugins/anthropic.json +73 -0
  760. package/registry/entries/plugins/app-control.json +23 -0
  761. package/registry/entries/plugins/auto-trader.json +203 -0
  762. package/registry/entries/plugins/background-runner.json +26 -0
  763. package/registry/entries/plugins/blooio.json +102 -0
  764. package/registry/entries/plugins/browser.json +75 -0
  765. package/registry/entries/plugins/cli.json +40 -0
  766. package/registry/entries/plugins/clipboard.json +44 -0
  767. package/registry/entries/plugins/coding-tools.json +71 -0
  768. package/registry/entries/plugins/commands.json +63 -0
  769. package/registry/entries/plugins/computeruse.json +74 -0
  770. package/registry/entries/plugins/copilot-proxy.json +93 -0
  771. package/registry/entries/plugins/directives.json +63 -0
  772. package/registry/entries/plugins/edge-tts.json +97 -0
  773. package/registry/entries/plugins/elevenlabs.json +169 -0
  774. package/registry/entries/plugins/elizacloud.json +208 -0
  775. package/registry/entries/plugins/evm.json +134 -0
  776. package/registry/entries/plugins/experience.json +34 -0
  777. package/registry/entries/plugins/facewear.json +131 -0
  778. package/registry/entries/plugins/form.json +26 -0
  779. package/registry/entries/plugins/github.json +93 -0
  780. package/registry/entries/plugins/gmail-watch.json +25 -0
  781. package/registry/entries/plugins/goals.json +77 -0
  782. package/registry/entries/plugins/google-genai.json +106 -0
  783. package/registry/entries/plugins/groq.json +93 -0
  784. package/registry/entries/plugins/hedera.json +48 -0
  785. package/registry/entries/plugins/inmemorydb.json +25 -0
  786. package/registry/entries/plugins/linear.json +51 -0
  787. package/registry/entries/plugins/local-inference.json +142 -0
  788. package/registry/entries/plugins/local-storage.json +36 -0
  789. package/registry/entries/plugins/localdb.json +25 -0
  790. package/registry/entries/plugins/mcp.json +44 -0
  791. package/registry/entries/plugins/memory.json +124 -0
  792. package/registry/entries/plugins/minecraft.json +79 -0
  793. package/registry/entries/plugins/moltbook.json +83 -0
  794. package/registry/entries/plugins/music.json +155 -0
  795. package/registry/entries/plugins/mysticism.json +48 -0
  796. package/registry/entries/plugins/nearai.json +82 -0
  797. package/registry/entries/plugins/ngrok.json +69 -0
  798. package/registry/entries/plugins/ollama.json +96 -0
  799. package/registry/entries/plugins/openai.json +189 -0
  800. package/registry/entries/plugins/openrouter.json +188 -0
  801. package/registry/entries/plugins/pdf.json +26 -0
  802. package/registry/entries/plugins/plugin-manager.json +23 -0
  803. package/registry/entries/plugins/prose.json +48 -0
  804. package/registry/entries/plugins/rlm.json +26 -0
  805. package/registry/entries/plugins/roblox.json +88 -0
  806. package/registry/entries/plugins/rss.json +64 -0
  807. package/registry/entries/plugins/s3-storage.json +91 -0
  808. package/registry/entries/plugins/scheduling.json +35 -0
  809. package/registry/entries/plugins/shell.json +94 -0
  810. package/registry/entries/plugins/social-alpha.json +72 -0
  811. package/registry/entries/plugins/tailscale.json +81 -0
  812. package/registry/entries/plugins/tee.json +53 -0
  813. package/registry/entries/plugins/todos.json +26 -0
  814. package/registry/entries/plugins/trajectory-logger.json +33 -0
  815. package/registry/entries/plugins/trust.json +39 -0
  816. package/registry/entries/plugins/tts.json +71 -0
  817. package/registry/entries/plugins/tunnel.json +45 -0
  818. package/registry/entries/plugins/twilio.json +168 -0
  819. package/registry/entries/plugins/vercel-ai-gateway.json +128 -0
  820. package/registry/entries/plugins/video.json +23 -0
  821. package/registry/entries/plugins/vision.json +43 -0
  822. package/registry/entries/plugins/webhooks.json +23 -0
  823. package/registry/entries/plugins/workflow.json +25 -0
  824. package/registry/entries/plugins/xai.json +75 -0
  825. package/registry/index.d.ts +2 -1
  826. package/registry/index.d.ts.map +1 -1
  827. package/registry/index.js +46 -12
  828. package/registry/loader.d.ts +2 -1
  829. package/registry/loader.d.ts.map +1 -1
  830. package/registry/loader.js +49 -2
  831. package/registry/schema.d.ts +244 -34
  832. package/registry/schema.d.ts.map +1 -1
  833. package/registry/schema.js +36 -0
  834. package/runtime/android-avf-microdroid-bridge.d.ts +29 -0
  835. package/runtime/android-avf-microdroid-bridge.d.ts.map +1 -0
  836. package/runtime/android-avf-microdroid-bridge.js +149 -0
  837. package/runtime/api-dev-settings-banner.d.ts.map +1 -1
  838. package/runtime/api-dev-settings-banner.js +5 -13
  839. package/runtime/app-core-runtime-hooks.d.ts +21 -0
  840. package/runtime/app-core-runtime-hooks.d.ts.map +1 -0
  841. package/runtime/app-core-runtime-hooks.js +10 -0
  842. package/runtime/autonomy-policy.d.ts +2 -0
  843. package/runtime/autonomy-policy.d.ts.map +1 -0
  844. package/runtime/autonomy-policy.js +4 -0
  845. package/runtime/desktop/AppWindowRenderer.d.ts +17 -0
  846. package/runtime/desktop/AppWindowRenderer.d.ts.map +1 -0
  847. package/runtime/desktop/AppWindowRenderer.js +360 -0
  848. package/runtime/desktop/DesktopSurfaceNavigationRuntime.d.ts +2 -0
  849. package/runtime/desktop/DesktopSurfaceNavigationRuntime.d.ts.map +1 -0
  850. package/runtime/desktop/DesktopSurfaceNavigationRuntime.js +41 -0
  851. package/runtime/desktop/DesktopTrayRuntime.d.ts +2 -0
  852. package/runtime/desktop/DesktopTrayRuntime.d.ts.map +1 -0
  853. package/runtime/desktop/DesktopTrayRuntime.js +174 -0
  854. package/runtime/desktop/DetachedShellRoot.d.ts +10 -0
  855. package/runtime/desktop/DetachedShellRoot.d.ts.map +1 -0
  856. package/runtime/desktop/DetachedShellRoot.js +111 -0
  857. package/runtime/desktop/index.d.ts +6 -0
  858. package/runtime/desktop/index.d.ts.map +1 -0
  859. package/runtime/desktop/index.js +5 -0
  860. package/runtime/desktop/tray-menu.d.ts +20 -0
  861. package/runtime/desktop/tray-menu.d.ts.map +1 -0
  862. package/runtime/desktop/tray-menu.js +143 -0
  863. package/runtime/dev-server.d.ts +1 -1
  864. package/runtime/dev-server.d.ts.map +1 -1
  865. package/runtime/dev-server.js +93 -17
  866. package/runtime/eliza.d.ts +75 -1
  867. package/runtime/eliza.d.ts.map +1 -1
  868. package/runtime/eliza.js +596 -122
  869. package/runtime/ensure-text-to-speech-handler.d.ts.map +1 -1
  870. package/runtime/ensure-text-to-speech-handler.js +10 -3
  871. package/runtime/mobile-safe-runtime.d.ts +181 -2
  872. package/runtime/mobile-safe-runtime.d.ts.map +1 -1
  873. package/runtime/mobile-safe-runtime.js +1019 -12
  874. package/runtime/mode/remote-forwarder.d.ts.map +1 -1
  875. package/runtime/mode/remote-forwarder.js +2 -2
  876. package/runtime/mode/route-mode-guard.d.ts +1 -2
  877. package/runtime/mode/route-mode-guard.d.ts.map +1 -1
  878. package/runtime/mode/route-mode-guard.js +4 -5
  879. package/runtime/mode/route-mode-matrix.d.ts.map +1 -1
  880. package/runtime/mode/route-mode-matrix.js +14 -1
  881. package/runtime/mode/runtime-mode.d.ts +1 -1
  882. package/runtime/mode/runtime-mode.js +1 -1
  883. package/runtime/runtime-bootstrap-policy.d.ts.map +1 -1
  884. package/runtime/runtime-bootstrap-policy.js +14 -2
  885. package/runtime/telegram-standalone-handler.d.ts.map +1 -1
  886. package/runtime/telegram-standalone-handler.js +10 -9
  887. package/runtime/tts-cache-wiring.d.ts +29 -0
  888. package/runtime/tts-cache-wiring.d.ts.map +1 -0
  889. package/runtime/tts-cache-wiring.js +114 -0
  890. package/runtime/voice-warmup.d.ts +81 -0
  891. package/runtime/voice-warmup.d.ts.map +1 -0
  892. package/runtime/voice-warmup.js +111 -0
  893. package/scripts/android-sms-gateway-template.test.mjs +1014 -0
  894. package/scripts/aosp/README.md +19 -15
  895. package/scripts/aosp/compile-libllama.mjs +1344 -248
  896. package/scripts/aosp/compile-shim.mjs +47 -18
  897. package/scripts/aosp/deploy-pixel.mjs +405 -0
  898. package/scripts/aosp/lib/load-variant-config.mjs +3 -3
  899. package/scripts/aosp/llama-cpp-patches/README.md +8 -8
  900. package/scripts/aosp/llama-cpp-patches/apply-patches.mjs +23 -6
  901. package/scripts/aosp/llama-cpp-patches/polarquant/README.md +37 -0
  902. package/scripts/aosp/llama-cpp-patches/qjl/README.md +37 -0
  903. package/scripts/aosp/seccomp-shim/sigsys-handler-arm64.c +169 -0
  904. package/scripts/aosp/seccomp-shim/sigsys-handler-riscv64.c +217 -0
  905. package/scripts/aosp/smoke-cuttlefish.mjs +34 -4
  906. package/scripts/aosp/stage-default-models.mjs +18 -18
  907. package/scripts/aosp/variant-config-schema.ts +2 -2
  908. package/scripts/assert-required-bundled-packages.test.ts +534 -0
  909. package/scripts/audit-apple-store-sandbox.mjs +146 -0
  910. package/scripts/audit-live-test-surface.mjs +5 -2
  911. package/scripts/build-capacitor-app.mjs +21 -0
  912. package/scripts/build-flatpak.mjs +5 -5
  913. package/scripts/build-helpers/arm64-simd.mjs +72 -0
  914. package/scripts/build-helpers/omnivoice-merged.mjs +87 -0
  915. package/scripts/build-helpers/verify-fused-symbols.mjs +567 -0
  916. package/scripts/build-image.sh +1 -1
  917. package/scripts/build-llama-cpp-mtp.mjs +487 -0
  918. package/scripts/build-native-plugins.mjs +230 -18
  919. package/scripts/build-patched-electrobun-cli.mjs +68 -10
  920. package/scripts/build-win.mjs +1 -1
  921. package/scripts/bun-riscv64/Dockerfile +418 -0
  922. package/scripts/bun-riscv64/README.md +316 -0
  923. package/scripts/bun-riscv64/build.sh +469 -0
  924. package/scripts/bun-riscv64/bun-patches/0001-config-add-riscv64-arch.patch +74 -0
  925. package/scripts/bun-riscv64/bun-patches/0002-flags-add-riscv64-march-mabi.patch +16 -0
  926. package/scripts/bun-riscv64/bun-patches/0003-zig-add-riscv64-target-triple-and-cpu.patch +26 -0
  927. package/scripts/bun-riscv64/bun-patches/0004-webkit-force-local-mode-on-riscv64.patch +33 -0
  928. package/scripts/bun-riscv64/bun-patches/0005-tinycc-disable-on-riscv64.patch +16 -0
  929. package/scripts/bun-riscv64/bun-patches/0006-build-add-riscv64-cli-validation.patch +15 -0
  930. package/scripts/bun-riscv64/bun-patches/0007-deps-per-dep-riscv64-checks.patch +24 -0
  931. package/scripts/bun-riscv64/bun-patches/0008-source-stabilize-riscv64-musl-build.patch +226 -0
  932. package/scripts/bun-riscv64/bun-patches/0009-disable-wasm-streaming-hooks-for-c-loop.patch +162 -0
  933. package/scripts/bun-riscv64/bun-patches/0010-disable-inspector-profiler-for-riscv64-c-loop.patch +80 -0
  934. package/scripts/bun-riscv64/bun-patches/0011-process-arch-add-riscv64.patch +23 -0
  935. package/scripts/bun-riscv64/bun-patches/0012-cpu-features-add-riscv64-fallback.patch +13 -0
  936. package/scripts/bun-riscv64/bun-patches/0013-disable-console-inspector-hooks-for-riscv64-c-loop.patch +43 -0
  937. package/scripts/bun-riscv64/bun-patches/0014-disable-custom-inspector-dispatchers-on-riscv64.patch +127 -0
  938. package/scripts/bun-riscv64/bun-patches/0015-disable-jsc-profiler-builtins-on-riscv64.patch +75 -0
  939. package/scripts/bun-riscv64/bun-patches/0016-node-vm-disable-jit-cached-data-on-riscv64-c-loop.patch +96 -0
  940. package/scripts/bun-riscv64/bun-patches/0017-disable-performance-domjit-signature-on-riscv64-c-loop.patch +34 -0
  941. package/scripts/bun-riscv64/bun-patches/0018-fix-serialized-script-identifier-big-endian-path.patch +19 -0
  942. package/scripts/bun-riscv64/bun-patches/0019-add-wtf-timer-fire-bridge-for-c-loop.patch +24 -0
  943. package/scripts/bun-riscv64/bun-patches/0020-run-riscv64-smoke-test-under-qemu.patch +13 -0
  944. package/scripts/bun-riscv64/bun-patches/0021-fix-riscv64-linux-open-flags.patch +25 -0
  945. package/scripts/bun-riscv64/bun-patches/0022-zlib-riscv64-generic-kernels.patch +25 -0
  946. package/scripts/bun-riscv64/bun-patches/README.md +127 -0
  947. package/scripts/bun-riscv64/bun-version.json +202 -0
  948. package/scripts/bun-riscv64/run-build.sh +162 -0
  949. package/scripts/bun-riscv64/rust-core/0001-riscv64-rust-core-port.patch +868 -0
  950. package/scripts/bun-riscv64/rust-core/0002-second-wave-riscv64-source-gaps.patch +130 -0
  951. package/scripts/bun-riscv64/rust-core/0003-third-wave-riscv64-crash-handler-gaps.patch +78 -0
  952. package/scripts/bun-riscv64/rust-core/0004-rust-target-cpu-riscv64.patch +39 -0
  953. package/scripts/bun-riscv64/rust-core/0005-fifth-wave-riscv64-source-gaps.patch +96 -0
  954. package/scripts/bun-riscv64/rust-core/0006-cpp-wasm-and-inspector-guards-riscv64.patch +91 -0
  955. package/scripts/bun-riscv64/rust-core/0007-bun-alloc-max-align-t-riscv64.patch +36 -0
  956. package/scripts/bun-riscv64/rust-core/0008-workspace-lints-warn-not-deny-riscv64.patch +75 -0
  957. package/scripts/bun-riscv64/rust-core/0009-zigglobalobject-wasm-streaming-guards-riscv64.patch +109 -0
  958. package/scripts/bun-riscv64/rust-core/0010-tcc-externs-stub-on-riscv64.patch +62 -0
  959. package/scripts/bun-riscv64/rust-core/0011-clippy-ptr-cast-lints-warn-riscv64.patch +61 -0
  960. package/scripts/bun-riscv64/rust-core/README.md +80 -0
  961. package/scripts/bun-riscv64/rust-core/webkit-patches/0003-disable-dfg-ftl-on-riscv64.patch +60 -0
  962. package/scripts/bun-riscv64/rust-core/webkit-patches/0004-riscv64-do-not-force-wasm-in-c-loop.patch +31 -0
  963. package/scripts/bun-riscv64/rust-core/webkit-patches/0005-domjit-effect-allow-no-dfg-c-loop.patch +40 -0
  964. package/scripts/bun-riscv64/rust-core/webkit-patches/0006-disable-usewasm-when-webassembly-compiled-out.patch +33 -0
  965. package/scripts/bun-riscv64/rust-core/webkit-patches/0007-restore-dropped-includes-and-llint-fwd-decl.patch +31 -0
  966. package/scripts/bun-riscv64/validate.sh +264 -0
  967. package/scripts/bun-riscv64/webkit-patches/0001-cherry-pick-llint-riscv64.recipe +155 -0
  968. package/scripts/bun-riscv64/webkit-patches/0002-cherry-pick-baseline-jit-riscv64.recipe +40 -0
  969. package/scripts/bun-riscv64/webkit-patches/0003-disable-dfg-ftl-on-riscv64.patch +60 -0
  970. package/scripts/bun-riscv64/webkit-patches/0004-riscv64-do-not-force-wasm-in-c-loop.patch +31 -0
  971. package/scripts/bun-riscv64/webkit-patches/0005-domjit-effect-allow-no-dfg-c-loop.patch +40 -0
  972. package/scripts/bun-riscv64/webkit-patches/0006-disable-usewasm-when-webassembly-compiled-out.patch +33 -0
  973. package/scripts/bun-riscv64/webkit-patches/0007-restore-dropped-includes-and-llint-fwd-decl.patch +72 -0
  974. package/scripts/bun-riscv64/webkit-patches/README.md +146 -0
  975. package/scripts/check-homepage-public-readiness.mjs +353 -0
  976. package/scripts/check-homepage-release-data.mjs +110 -0
  977. package/scripts/check-i18n.mjs +2 -1
  978. package/scripts/check-real-local-chat.ts +147 -0
  979. package/scripts/check-real-local-provisioning.ts +104 -0
  980. package/scripts/check-real-local-reset.ts +249 -0
  981. package/scripts/check-sms-gateway-completion-audit.mjs +428 -0
  982. package/scripts/check-sms-gateway-readiness.mjs +266 -0
  983. package/scripts/clean-repo.mjs +5 -5
  984. package/scripts/codesign-mas.mjs +222 -16
  985. package/scripts/collect-docker-runtime-deps.mjs +229 -0
  986. package/scripts/continue-sms-gateway-work.mjs +121 -0
  987. package/scripts/copy-runtime-node-modules.ts +903 -195
  988. package/scripts/deploy-cloud-api-production-gateway.mjs +52 -0
  989. package/scripts/desktop-build.mjs +655 -101
  990. package/scripts/dev-platform.mjs +346 -102
  991. package/scripts/dev-startup-smoke.mjs +248 -0
  992. package/scripts/dev-ui.mjs +418 -176
  993. package/scripts/disable-local-eliza-workspace.mjs +35 -0
  994. package/scripts/docker-ci-smoke.sh +298 -96
  995. package/scripts/docker-entrypoint.sh +62 -1
  996. package/scripts/docker-entrypoint.test.ts +283 -0
  997. package/scripts/ensure-avatars.mjs +2 -2
  998. package/scripts/ensure-electrobun-core.mjs +1 -1
  999. package/scripts/ensure-generated-core-proto-js.mjs +1 -1
  1000. package/scripts/ensure-type-package-aliases.mjs +62 -5
  1001. package/scripts/ensure-vision-deps.mjs +20 -1
  1002. package/scripts/entry.ts +1 -1
  1003. package/scripts/ffi-stub/Makefile +64 -0
  1004. package/scripts/ffi-stub/README.md +391 -0
  1005. package/scripts/ffi-stub/asr-ffi-smoke.ts +139 -0
  1006. package/scripts/ffi-stub/ffi-stub.c +539 -0
  1007. package/scripts/ffi-stub/ffi.h +538 -0
  1008. package/scripts/ffi-stub/libelizainference_stub.so +0 -0
  1009. package/scripts/ffi-stub/tts-stream-ffi-smoke.ts +349 -0
  1010. package/scripts/generate-first-run-voicelines.mjs +194 -0
  1011. package/scripts/generate-plugin-index.js +4 -3
  1012. package/scripts/generate-static-asset-manifest.mjs +1 -1
  1013. package/scripts/i18n-dynamic-keys.json +5 -5
  1014. package/scripts/init-submodules.mjs +2 -2
  1015. package/scripts/install-android-sms-gateway.md +177 -0
  1016. package/scripts/install-android-sms-gateway.mjs +1088 -0
  1017. package/scripts/ios-xcframework/README.md +74 -72
  1018. package/scripts/ios-xcframework/build-xcframework.mjs +204 -43
  1019. package/scripts/ios-xcframework/run-physical-device-smoke.mjs +1943 -0
  1020. package/scripts/ios-xcframework/runtime-symbol-shim.c +450 -0
  1021. package/scripts/kernel-patches/cpu-polar-kernels.mjs +441 -0
  1022. package/scripts/kernel-patches/cpu-simd-kernels.mjs +253 -0
  1023. package/scripts/kernel-patches/cpu-thread-parallelism.mjs +368 -0
  1024. package/scripts/kernel-patches/cuda-kernels.mjs +117 -0
  1025. package/scripts/kernel-patches/metal-kernels.mjs +1698 -109
  1026. package/scripts/kernel-patches/server-omnivoice-route.mjs +718 -0
  1027. package/scripts/kernel-patches/server-structured-output.mjs +279 -0
  1028. package/scripts/kernel-patches/vulkan-dispatch-log.mjs +166 -0
  1029. package/scripts/kernel-patches/vulkan-dispatch-log.test.mjs +50 -0
  1030. package/scripts/kernel-patches/vulkan-dispatch-patches/01-vulkan-shaders-gen.patch +30 -16
  1031. package/scripts/kernel-patches/vulkan-dispatch-patches/02-ggml-vulkan-pipelines.patch +75 -30
  1032. package/scripts/kernel-patches/vulkan-kernels.mjs +800 -49
  1033. package/scripts/lib/agent-source-watcher.mjs +174 -0
  1034. package/scripts/lib/agent-source-watcher.test.mjs +184 -0
  1035. package/scripts/lib/api-supervisor.mjs +78 -9
  1036. package/scripts/lib/api-supervisor.test.mjs +121 -0
  1037. package/scripts/lib/app-dir.mjs +2 -16
  1038. package/scripts/lib/apple-entitlement-audit.mjs +655 -0
  1039. package/scripts/lib/apple-entitlement-audit.test.mjs +144 -0
  1040. package/scripts/lib/bun-version-guard.mjs +13 -13
  1041. package/scripts/lib/capacitor-plugin-build-needed.mjs +4 -3
  1042. package/scripts/lib/capacitor-plugin-names.mjs +30 -14
  1043. package/scripts/lib/desktop-preflight.mjs +9 -5
  1044. package/scripts/lib/desktop-startup-embedding-warmup-policy.mjs +51 -0
  1045. package/scripts/lib/desktop-startup-embedding-warmup-policy.test.mjs +55 -0
  1046. package/scripts/lib/duet-bridge.d.mts +63 -0
  1047. package/scripts/lib/duet-bridge.mjs +193 -0
  1048. package/scripts/lib/node-path-env.mjs +4 -2
  1049. package/scripts/lib/orchestrator-desktop-dev-banner.mjs +12 -3
  1050. package/scripts/lib/patch-bun-exports.mjs +90 -27
  1051. package/scripts/lib/patch-bun-exports.test.mjs +79 -0
  1052. package/scripts/lib/renderer-build-action.mjs +35 -0
  1053. package/scripts/lib/renderer-build-action.test.mjs +70 -0
  1054. package/scripts/lib/stage-android-agent.mjs +748 -99
  1055. package/scripts/lib/sync-eliza-env-aliases.mjs +3 -25
  1056. package/scripts/lib/ui-smoke-stub-decision.mjs +33 -0
  1057. package/scripts/lib/ui-smoke-stub-decision.test.mjs +46 -0
  1058. package/scripts/lib/vite-renderer-dist-stale.mjs +5 -0
  1059. package/scripts/lib/voice-latency-report.mjs +154 -0
  1060. package/scripts/lifeops-prompt-benchmark.ts +21 -12
  1061. package/scripts/link-docker-local-app-packages.mjs +89 -36
  1062. package/scripts/local-stt-bench.ts +192 -0
  1063. package/scripts/maintain-cloud-api-production-gateway.mjs +54 -0
  1064. package/scripts/mas-smoke.mjs +459 -0
  1065. package/scripts/mas-smoke.test.mjs +220 -0
  1066. package/scripts/mobile-auth-simulator-smoke.mjs +0 -1
  1067. package/scripts/normalize-eliza-capture.ts +97 -0
  1068. package/scripts/omnivoice-fuse/prepare.mjs +2543 -23
  1069. package/scripts/pack-upstreams.mjs +65 -5
  1070. package/scripts/package-electrobun-linux.mjs +303 -0
  1071. package/scripts/patch-deps.mjs +5 -3
  1072. package/scripts/patches/llama-mobile-kokoro-tts.patch +480 -0
  1073. package/scripts/playwright-ui-live-stack.ts +194 -49
  1074. package/scripts/playwright-ui-smoke-api-stub.mjs +3501 -109
  1075. package/scripts/pre-review-local.mjs +2 -2
  1076. package/scripts/prepare-ios-cocoapods.sh +41 -3
  1077. package/scripts/release-check.ts +180 -84
  1078. package/scripts/release-workflow-drift.test.ts +57 -0
  1079. package/scripts/relink-workspace-packages-to-dist.mjs +21 -4
  1080. package/scripts/rt.mjs +16 -1
  1081. package/scripts/run-biome-check.mjs +1 -1
  1082. package/scripts/run-coding-agent-e2e.mjs +3 -3
  1083. package/scripts/run-eliza-app-core-script.mjs +34 -0
  1084. package/scripts/run-local-plugin-live-smoke.mjs +71 -2
  1085. package/scripts/run-mobile-build-android-app-actions.test.mjs +426 -0
  1086. package/scripts/run-mobile-build.mjs +4757 -607
  1087. package/scripts/run-node-runtime.mjs +184 -7
  1088. package/scripts/run-node-runtime.test.mjs +167 -0
  1089. package/scripts/run-node-tsx.mjs +80 -33
  1090. package/scripts/run-node.mjs +41 -1
  1091. package/scripts/run-production-build.mjs +34 -27
  1092. package/scripts/run-release-check.mjs +19 -0
  1093. package/scripts/run-release-contract-suite.mjs +107 -14
  1094. package/scripts/run-ui-smoke-playwright-suite.mjs +0 -2
  1095. package/scripts/runtime-package-manifest.ts +21 -3
  1096. package/scripts/setup-upstreams.mjs +42 -1
  1097. package/scripts/sms-gateway-status.mjs +194 -0
  1098. package/scripts/stage-android-agent.test.mjs +97 -0
  1099. package/scripts/stage-elizavoice-lib.mjs +203 -0
  1100. package/scripts/startup-integration-script-drift.test.ts +82 -4
  1101. package/scripts/streaming-pipeline-bench.ts +543 -0
  1102. package/scripts/sync-homepage-porkbun-dns.mjs +262 -0
  1103. package/scripts/test-sms-gateway-software.mjs +100 -0
  1104. package/scripts/type-audit.mjs +1 -1
  1105. package/scripts/validate-bluebubbles-outbound.mjs +293 -0
  1106. package/scripts/validate-cdn-assets.mjs +15 -7
  1107. package/scripts/validate-regression-matrix.mjs +109 -8
  1108. package/scripts/verify-android-sms-gateway-e2e.mjs +362 -0
  1109. package/scripts/verify-bluebubbles-gateway-e2e.mjs +191 -0
  1110. package/scripts/verify-bluebubbles-inbound-readiness.mjs +88 -0
  1111. package/scripts/verify-cloud-api-production-deploy.mjs +87 -0
  1112. package/scripts/verify-cloud-sms-onboarding-flow.mjs +336 -0
  1113. package/scripts/voice/freeze-voice.mjs +521 -0
  1114. package/scripts/voice-attribution-smoke.ts +538 -0
  1115. package/scripts/voice-create-profile.mjs +379 -0
  1116. package/scripts/voice-duet.mjs +1355 -0
  1117. package/scripts/voice-e2e-hardware.ts +871 -0
  1118. package/scripts/voice-interactive.mjs +1750 -0
  1119. package/scripts/voice-latency-report.mjs +96 -0
  1120. package/scripts/voice-latency-report.test.ts +176 -0
  1121. package/scripts/voice-preset/build-default-voice-preset.mjs +249 -0
  1122. package/scripts/voice-preset/build-onboarding-voice.mjs +281 -0
  1123. package/scripts/watch-sms-gateway-readiness.mjs +303 -0
  1124. package/scripts/write-homepage-release-data.mjs +458 -26
  1125. package/security/agent-vault-id.d.ts +1 -1
  1126. package/security/agent-vault-id.js +1 -1
  1127. package/security/hydrate-wallet-keys-from-platform-store.d.ts.map +1 -1
  1128. package/security/hydrate-wallet-keys-from-platform-store.js +23 -14
  1129. package/security/platform-secure-store-node.d.ts +2 -2
  1130. package/security/platform-secure-store-node.js +3 -3
  1131. package/security/wallet-os-store-actions.d.ts +0 -9
  1132. package/security/wallet-os-store-actions.d.ts.map +1 -1
  1133. package/security/wallet-os-store-actions.js +3 -10
  1134. package/services/account-pool.d.ts +23 -14
  1135. package/services/account-pool.d.ts.map +1 -1
  1136. package/services/account-pool.js +86 -24
  1137. package/services/account-usage.d.ts.map +1 -1
  1138. package/services/account-usage.js +2 -5
  1139. package/services/ambient-audio/consent.d.ts +9 -0
  1140. package/services/ambient-audio/consent.d.ts.map +1 -0
  1141. package/services/ambient-audio/consent.js +28 -0
  1142. package/services/ambient-audio/index.d.ts +7 -0
  1143. package/services/ambient-audio/index.d.ts.map +1 -0
  1144. package/services/ambient-audio/index.js +4 -0
  1145. package/services/ambient-audio/replay-buffer.d.ts +14 -0
  1146. package/services/ambient-audio/replay-buffer.d.ts.map +1 -0
  1147. package/services/ambient-audio/replay-buffer.js +66 -0
  1148. package/services/ambient-audio/response-gate.d.ts +3 -0
  1149. package/services/ambient-audio/response-gate.d.ts.map +1 -0
  1150. package/services/ambient-audio/response-gate.js +33 -0
  1151. package/services/ambient-audio/service.d.ts +22 -0
  1152. package/services/ambient-audio/service.d.ts.map +1 -0
  1153. package/services/ambient-audio/service.js +47 -0
  1154. package/services/ambient-audio/types.d.ts +42 -0
  1155. package/services/ambient-audio/types.d.ts.map +1 -0
  1156. package/services/app-updates/update-policy.d.ts +64 -0
  1157. package/services/app-updates/update-policy.d.ts.map +1 -0
  1158. package/services/app-updates/update-policy.js +228 -0
  1159. package/services/auth-store.d.ts +37 -1
  1160. package/services/auth-store.d.ts.map +1 -1
  1161. package/services/auth-store.js +59 -26
  1162. package/services/cloud-jwks-store.d.ts +3 -3
  1163. package/services/cloud-jwks-store.d.ts.map +1 -1
  1164. package/services/cloud-jwks-store.js +5 -8
  1165. package/services/coding-account-bridge.d.ts +71 -0
  1166. package/services/coding-account-bridge.d.ts.map +1 -0
  1167. package/services/coding-account-bridge.js +267 -0
  1168. package/services/connector-target-catalog.d.ts +10 -3
  1169. package/services/connector-target-catalog.d.ts.map +1 -1
  1170. package/services/connector-target-catalog.js +7 -4
  1171. package/services/credential-tunnel-service.d.ts +66 -0
  1172. package/services/credential-tunnel-service.d.ts.map +1 -0
  1173. package/services/credential-tunnel-service.js +227 -0
  1174. package/services/github-credentials.d.ts +1 -1
  1175. package/services/github-credentials.js +1 -1
  1176. package/services/inference-abort.d.ts +47 -0
  1177. package/services/inference-abort.d.ts.map +1 -0
  1178. package/services/inference-abort.js +76 -0
  1179. package/services/persistence.d.ts +2 -3
  1180. package/services/persistence.d.ts.map +1 -1
  1181. package/services/persistence.js +2 -3
  1182. package/services/phrase-chunked-tts.d.ts +136 -0
  1183. package/services/phrase-chunked-tts.d.ts.map +1 -0
  1184. package/services/phrase-chunked-tts.js +208 -0
  1185. package/services/sandbox-registry.d.ts +78 -0
  1186. package/services/sandbox-registry.d.ts.map +1 -0
  1187. package/services/sandbox-registry.js +323 -0
  1188. package/services/secrets-manager-installer.d.ts +8 -1
  1189. package/services/secrets-manager-installer.d.ts.map +1 -1
  1190. package/services/secrets-manager-installer.js +27 -2
  1191. package/services/sensitive-requests/cloud-link-adapter.d.ts +15 -0
  1192. package/services/sensitive-requests/cloud-link-adapter.d.ts.map +1 -0
  1193. package/services/sensitive-requests/cloud-link-adapter.js +73 -0
  1194. package/services/sensitive-requests/index.d.ts +27 -0
  1195. package/services/sensitive-requests/index.d.ts.map +1 -0
  1196. package/services/sensitive-requests/index.js +51 -0
  1197. package/services/sensitive-requests/instruct-dm-only-adapter.d.ts +14 -0
  1198. package/services/sensitive-requests/instruct-dm-only-adapter.d.ts.map +1 -0
  1199. package/services/sensitive-requests/instruct-dm-only-adapter.js +22 -0
  1200. package/services/sensitive-requests/owner-app-inline-adapter.d.ts +3 -0
  1201. package/services/sensitive-requests/owner-app-inline-adapter.d.ts.map +1 -0
  1202. package/services/sensitive-requests/owner-app-inline-adapter.js +146 -0
  1203. package/services/sensitive-requests/owner-app-oauth-adapter.d.ts +3 -0
  1204. package/services/sensitive-requests/owner-app-oauth-adapter.d.ts.map +1 -0
  1205. package/services/sensitive-requests/owner-app-oauth-adapter.js +156 -0
  1206. package/services/sensitive-requests/public-link-adapter.d.ts +14 -0
  1207. package/services/sensitive-requests/public-link-adapter.d.ts.map +1 -0
  1208. package/services/sensitive-requests/public-link-adapter.js +86 -0
  1209. package/services/sensitive-requests/tunnel-link-adapter.d.ts +17 -0
  1210. package/services/sensitive-requests/tunnel-link-adapter.d.ts.map +1 -0
  1211. package/services/sensitive-requests/tunnel-link-adapter.js +38 -0
  1212. package/services/steward-credentials.d.ts +1 -1
  1213. package/services/steward-credentials.d.ts.map +1 -1
  1214. package/services/steward-credentials.js +10 -6
  1215. package/services/steward-sidecar/health-check.d.ts.map +1 -1
  1216. package/services/steward-sidecar/health-check.js +4 -3
  1217. package/services/steward-sidecar/process-management.d.ts +1 -1
  1218. package/services/steward-sidecar/process-management.d.ts.map +1 -1
  1219. package/services/steward-sidecar/process-management.js +9 -3
  1220. package/services/steward-sidecar/types.d.ts +1 -1
  1221. package/services/steward-sidecar/types.d.ts.map +1 -1
  1222. package/services/steward-sidecar/wallet-setup.d.ts.map +1 -1
  1223. package/services/steward-sidecar/wallet-setup.js +8 -7
  1224. package/services/steward-sidecar.d.ts +2 -2
  1225. package/services/steward-sidecar.d.ts.map +1 -1
  1226. package/services/steward-sidecar.js +27 -19
  1227. package/services/task-host-capabilities.d.ts +60 -0
  1228. package/services/task-host-capabilities.d.ts.map +1 -0
  1229. package/services/task-host-capabilities.js +122 -0
  1230. package/services/tool-call-cache/index.d.ts +2 -2
  1231. package/services/tool-call-cache/index.d.ts.map +1 -1
  1232. package/services/tool-call-cache/index.js +1 -1
  1233. package/services/trigger-event-bridge.js +1 -1
  1234. package/services/tunnel-to-mobile/index.d.ts +2 -0
  1235. package/services/tunnel-to-mobile/index.d.ts.map +1 -0
  1236. package/services/tunnel-to-mobile/index.js +1 -0
  1237. package/services/tunnel-to-mobile/tunnel-to-mobile-client.d.ts +105 -0
  1238. package/services/tunnel-to-mobile/tunnel-to-mobile-client.d.ts.map +1 -0
  1239. package/services/tunnel-to-mobile/tunnel-to-mobile-client.js +190 -0
  1240. package/services/vault-bootstrap.d.ts.map +1 -1
  1241. package/services/vault-bootstrap.js +48 -21
  1242. package/services/vault-mirror.d.ts +1 -1
  1243. package/services/vault-mirror.d.ts.map +1 -1
  1244. package/services/vault-mirror.js +29 -6
  1245. package/services/voice-profiles/diarization-pipeline.d.ts +6 -0
  1246. package/services/voice-profiles/diarization-pipeline.d.ts.map +1 -0
  1247. package/services/voice-profiles/diarization-pipeline.js +20 -0
  1248. package/services/voice-profiles/index.d.ts +12 -0
  1249. package/services/voice-profiles/index.d.ts.map +1 -0
  1250. package/services/voice-profiles/index.js +5 -0
  1251. package/services/voice-profiles/nickname-evaluator.d.ts +14 -0
  1252. package/services/voice-profiles/nickname-evaluator.d.ts.map +1 -0
  1253. package/services/voice-profiles/nickname-evaluator.js +46 -0
  1254. package/services/voice-profiles/owner-confidence.d.ts +10 -0
  1255. package/services/voice-profiles/owner-confidence.d.ts.map +1 -0
  1256. package/services/voice-profiles/owner-confidence.js +38 -0
  1257. package/services/voice-profiles/private-challenge.d.ts +20 -0
  1258. package/services/voice-profiles/private-challenge.d.ts.map +1 -0
  1259. package/services/voice-profiles/private-challenge.js +44 -0
  1260. package/services/voice-profiles/store.d.ts +21 -0
  1261. package/services/voice-profiles/store.d.ts.map +1 -0
  1262. package/services/voice-profiles/store.js +50 -0
  1263. package/services/voice-profiles/types.d.ts +38 -0
  1264. package/services/voice-profiles/types.d.ts.map +1 -0
  1265. package/services/voice-profiles/types.js +1 -0
  1266. package/styles/electrobun-mac-window-drag.css +4 -4
  1267. package/test/helpers/__tests__/live-agent-test.smoke.test.ts +43 -70
  1268. package/test/helpers/browser-mocks.ts +2 -2
  1269. package/test/helpers/conditional-tests.ts +2 -2
  1270. package/test/helpers/i18n.ts +1 -1
  1271. package/test/helpers/live-agent-test.ts +537 -551
  1272. package/test/helpers/live-provider.test.ts +4 -4
  1273. package/test/helpers/live-provider.ts +41 -7
  1274. package/test/helpers/live-runtime-server.ts +4 -4
  1275. package/test/helpers/pglite-runtime.ts +1 -1
  1276. package/test/helpers/real-runtime.ts +54 -15
  1277. package/test/helpers/trajectory-harness.ts +11 -7
  1278. package/test/scripts/start-eliza-live.ts +9 -0
  1279. package/test/scripts/test-parallel.mjs +1 -1
  1280. package/test/scripts/test-root-unit.mjs +6 -7
  1281. package/ui-compat.d.ts +13 -2
  1282. package/ui-compat.d.ts.map +1 -1
  1283. package/ui-compat.js +19 -3
  1284. package/api/auth-pairing-compat-routes.d.ts +0 -17
  1285. package/api/auth-pairing-compat-routes.d.ts.map +0 -1
  1286. package/api/auth-pairing-compat-routes.js +0 -301
  1287. package/api/local-inference-compat-routes.d.ts +0 -16
  1288. package/api/local-inference-compat-routes.d.ts.map +0 -1
  1289. package/api/local-inference-compat-routes.js +0 -617
  1290. package/api/onboarding-compat-routes.d.ts +0 -4
  1291. package/api/onboarding-compat-routes.d.ts.map +0 -1
  1292. package/api/onboarding-compat-routes.js +0 -207
  1293. package/api/plugins-compat-routes.d.ts +0 -103
  1294. package/api/plugins-compat-routes.d.ts.map +0 -1
  1295. package/api/plugins-compat-routes.js +0 -1181
  1296. package/api/server-onboarding-compat.d.ts +0 -31
  1297. package/api/server-onboarding-compat.d.ts.map +0 -1
  1298. package/api/server-onboarding-compat.js +0 -283
  1299. package/benchmark/cua-routes.d.ts +0 -10
  1300. package/benchmark/cua-routes.d.ts.map +0 -1
  1301. package/benchmark/cua-routes.js +0 -179
  1302. package/benchmark/mock-plugin-base.d.ts +0 -9
  1303. package/benchmark/mock-plugin-base.d.ts.map +0 -1
  1304. package/benchmark/mock-plugin-base.js +0 -325
  1305. package/cli/parse-duration.d.ts +0 -5
  1306. package/cli/parse-duration.d.ts.map +0 -1
  1307. package/cli/parse-duration.js +0 -27
  1308. package/patches/llama-cpp-capacitor@0.1.5.patch +0 -2387
  1309. package/platform/agent-browser-stub.d.ts +0 -27
  1310. package/platform/agent-browser-stub.d.ts.map +0 -1
  1311. package/platform/agent-browser-stub.js +0 -16
  1312. package/platforms/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +0 -26
  1313. package/platforms/android/app/src/main/res/drawable/ic_launcher_background.xml +0 -170
  1314. package/platforms/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -34
  1315. package/platforms/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +0 -18
  1316. package/platforms/electrobun/assets/appIcon.iconset/icon_512x512@2x.png +0 -0
  1317. package/platforms/electrobun/assets/appIcon.png +0 -0
  1318. package/platforms/electrobun/scripts/build-whisper-universal.sh +0 -137
  1319. package/platforms/electrobun/scripts/build-whisper.sh +0 -95
  1320. package/platforms/electrobun/src/libMacWindowEffects.dylib +0 -0
  1321. package/platforms/electrobun/src/native/whisper.ts +0 -280
  1322. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png +0 -0
  1323. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png +0 -0
  1324. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png +0 -0
  1325. package/registry/generate-apps.d.ts +0 -2
  1326. package/registry/generate-apps.d.ts.map +0 -1
  1327. package/registry/generate-apps.js +0 -338
  1328. package/registry/generate.d.ts +0 -2
  1329. package/registry/generate.d.ts.map +0 -1
  1330. package/registry/generate.js +0 -506
  1331. package/runtime/embedding-manager-support.d.ts +0 -77
  1332. package/runtime/embedding-manager-support.d.ts.map +0 -1
  1333. package/runtime/embedding-manager-support.js +0 -309
  1334. package/runtime/embedding-presets.d.ts +0 -5
  1335. package/runtime/embedding-presets.d.ts.map +0 -1
  1336. package/runtime/embedding-presets.js +0 -47
  1337. package/runtime/embedding-warmup-policy.d.ts +0 -13
  1338. package/runtime/embedding-warmup-policy.d.ts.map +0 -1
  1339. package/runtime/embedding-warmup-policy.js +0 -33
  1340. package/runtime/ensure-local-inference-handler.d.ts +0 -25
  1341. package/runtime/ensure-local-inference-handler.d.ts.map +0 -1
  1342. package/runtime/ensure-local-inference-handler.js +0 -389
  1343. package/runtime/mobile-local-inference-gate.d.ts +0 -21
  1344. package/runtime/mobile-local-inference-gate.d.ts.map +0 -1
  1345. package/runtime/mobile-local-inference-gate.js +0 -24
  1346. package/scripts/aosp/avd-test.mjs +0 -403
  1347. package/scripts/aosp/boot-validate.mjs +0 -536
  1348. package/scripts/aosp/build-aosp.mjs +0 -448
  1349. package/scripts/aosp/build-bootanimation.mjs +0 -178
  1350. package/scripts/aosp/capture-screens.mjs +0 -325
  1351. package/scripts/aosp/e2e-validate.mjs +0 -225
  1352. package/scripts/aosp/lint-init-rc.mjs +0 -258
  1353. package/scripts/aosp/llama-shim/eliza_llama_shim.c +0 -276
  1354. package/scripts/aosp/sim.mjs +0 -277
  1355. package/scripts/aosp/sync-to-aosp.mjs +0 -134
  1356. package/scripts/aosp/validate.mjs +0 -1273
  1357. package/scripts/build-llama-cpp-dflash.mjs +0 -1866
  1358. package/scripts/generate-onboarding-voicelines.mjs +0 -194
  1359. package/scripts/generated/static-asset-manifest.json +0 -4
  1360. package/scripts/normalize-parallax-capture.ts +0 -97
  1361. package/scripts/omnivoice-fuse/Makefile +0 -44
  1362. package/scripts/omnivoice-fuse/README.md +0 -266
  1363. package/scripts/omnivoice-fuse/cmake-graft.mjs +0 -180
  1364. package/scripts/omnivoice-fuse/ffi-stub.c +0 -222
  1365. package/scripts/omnivoice-fuse/ffi.h +0 -158
  1366. package/scripts/omnivoice-fuse/libelizainference_stub.dylib +0 -0
  1367. package/scripts/omnivoice-fuse/verify-symbols.mjs +0 -138
  1368. package/security/cloud-secret-store.d.ts +0 -34
  1369. package/security/cloud-secret-store.d.ts.map +0 -1
  1370. package/security/cloud-secret-store.js +0 -65
  1371. package/security/export-guard.d.ts +0 -34
  1372. package/security/export-guard.d.ts.map +0 -1
  1373. package/security/export-guard.js +0 -127
  1374. package/services/local-inference/__stress__/cache-stress-helpers.d.ts +0 -76
  1375. package/services/local-inference/__stress__/cache-stress-helpers.d.ts.map +0 -1
  1376. package/services/local-inference/__stress__/cache-stress-helpers.js +0 -238
  1377. package/services/local-inference/active-model.d.ts +0 -180
  1378. package/services/local-inference/active-model.d.ts.map +0 -1
  1379. package/services/local-inference/active-model.js +0 -362
  1380. package/services/local-inference/assignments.d.ts +0 -58
  1381. package/services/local-inference/assignments.d.ts.map +0 -1
  1382. package/services/local-inference/assignments.js +0 -179
  1383. package/services/local-inference/backend.d.ts +0 -200
  1384. package/services/local-inference/backend.d.ts.map +0 -1
  1385. package/services/local-inference/backend.js +0 -242
  1386. package/services/local-inference/bundled-models.d.ts +0 -34
  1387. package/services/local-inference/bundled-models.d.ts.map +0 -1
  1388. package/services/local-inference/bundled-models.js +0 -104
  1389. package/services/local-inference/cache-bridge.d.ts +0 -184
  1390. package/services/local-inference/cache-bridge.d.ts.map +0 -1
  1391. package/services/local-inference/cache-bridge.js +0 -333
  1392. package/services/local-inference/catalog.d.ts +0 -57
  1393. package/services/local-inference/catalog.d.ts.map +0 -1
  1394. package/services/local-inference/catalog.js +0 -262
  1395. package/services/local-inference/conversation-registry.d.ts +0 -122
  1396. package/services/local-inference/conversation-registry.d.ts.map +0 -1
  1397. package/services/local-inference/conversation-registry.js +0 -182
  1398. package/services/local-inference/device-bridge.d.ts +0 -139
  1399. package/services/local-inference/device-bridge.d.ts.map +0 -1
  1400. package/services/local-inference/device-bridge.js +0 -774
  1401. package/services/local-inference/dflash-doctor.d.ts +0 -27
  1402. package/services/local-inference/dflash-doctor.d.ts.map +0 -1
  1403. package/services/local-inference/dflash-doctor.js +0 -149
  1404. package/services/local-inference/dflash-server.d.ts +0 -248
  1405. package/services/local-inference/dflash-server.d.ts.map +0 -1
  1406. package/services/local-inference/dflash-server.js +0 -1076
  1407. package/services/local-inference/downloader.d.ts +0 -48
  1408. package/services/local-inference/downloader.d.ts.map +0 -1
  1409. package/services/local-inference/downloader.js +0 -688
  1410. package/services/local-inference/engine.d.ts +0 -282
  1411. package/services/local-inference/engine.d.ts.map +0 -1
  1412. package/services/local-inference/engine.js +0 -743
  1413. package/services/local-inference/external-scanner.d.ts +0 -17
  1414. package/services/local-inference/external-scanner.d.ts.map +0 -1
  1415. package/services/local-inference/external-scanner.js +0 -261
  1416. package/services/local-inference/handler-registry.d.ts +0 -72
  1417. package/services/local-inference/handler-registry.d.ts.map +0 -1
  1418. package/services/local-inference/handler-registry.js +0 -159
  1419. package/services/local-inference/hardware.d.ts +0 -26
  1420. package/services/local-inference/hardware.d.ts.map +0 -1
  1421. package/services/local-inference/hardware.js +0 -139
  1422. package/services/local-inference/hf-search.d.ts +0 -19
  1423. package/services/local-inference/hf-search.d.ts.map +0 -1
  1424. package/services/local-inference/hf-search.js +0 -169
  1425. package/services/local-inference/index.d.ts +0 -10
  1426. package/services/local-inference/index.d.ts.map +0 -1
  1427. package/services/local-inference/index.js +0 -7
  1428. package/services/local-inference/llama-server-metrics.d.ts +0 -108
  1429. package/services/local-inference/llama-server-metrics.d.ts.map +0 -1
  1430. package/services/local-inference/llama-server-metrics.js +0 -175
  1431. package/services/local-inference/manifest/index.d.ts +0 -4
  1432. package/services/local-inference/manifest/index.d.ts.map +0 -1
  1433. package/services/local-inference/manifest/index.js +0 -5
  1434. package/services/local-inference/manifest/schema.d.ts +0 -419
  1435. package/services/local-inference/manifest/schema.d.ts.map +0 -1
  1436. package/services/local-inference/manifest/schema.js +0 -227
  1437. package/services/local-inference/manifest/types.d.ts +0 -23
  1438. package/services/local-inference/manifest/types.d.ts.map +0 -1
  1439. package/services/local-inference/manifest/types.js +0 -5
  1440. package/services/local-inference/manifest/validator.d.ts +0 -43
  1441. package/services/local-inference/manifest/validator.d.ts.map +0 -1
  1442. package/services/local-inference/manifest/validator.js +0 -187
  1443. package/services/local-inference/paths.d.ts +0 -8
  1444. package/services/local-inference/paths.d.ts.map +0 -1
  1445. package/services/local-inference/paths.js +0 -7
  1446. package/services/local-inference/providers.d.ts +0 -61
  1447. package/services/local-inference/providers.d.ts.map +0 -1
  1448. package/services/local-inference/providers.js +0 -334
  1449. package/services/local-inference/ram-budget.d.ts +0 -57
  1450. package/services/local-inference/ram-budget.d.ts.map +0 -1
  1451. package/services/local-inference/ram-budget.js +0 -107
  1452. package/services/local-inference/readiness.d.ts +0 -9
  1453. package/services/local-inference/readiness.d.ts.map +0 -1
  1454. package/services/local-inference/readiness.js +0 -153
  1455. package/services/local-inference/recommendation.d.ts +0 -62
  1456. package/services/local-inference/recommendation.d.ts.map +0 -1
  1457. package/services/local-inference/recommendation.js +0 -309
  1458. package/services/local-inference/registry.d.ts +0 -35
  1459. package/services/local-inference/registry.d.ts.map +0 -1
  1460. package/services/local-inference/registry.js +0 -117
  1461. package/services/local-inference/router-handler.d.ts +0 -51
  1462. package/services/local-inference/router-handler.d.ts.map +0 -1
  1463. package/services/local-inference/router-handler.js +0 -165
  1464. package/services/local-inference/routing-policy.d.ts +0 -55
  1465. package/services/local-inference/routing-policy.d.ts.map +0 -1
  1466. package/services/local-inference/routing-policy.js +0 -195
  1467. package/services/local-inference/routing-preferences.d.ts +0 -8
  1468. package/services/local-inference/routing-preferences.d.ts.map +0 -1
  1469. package/services/local-inference/routing-preferences.js +0 -7
  1470. package/services/local-inference/service.d.ts +0 -88
  1471. package/services/local-inference/service.d.ts.map +0 -1
  1472. package/services/local-inference/service.js +0 -210
  1473. package/services/local-inference/session-pool.d.ts +0 -72
  1474. package/services/local-inference/session-pool.d.ts.map +0 -1
  1475. package/services/local-inference/session-pool.js +0 -125
  1476. package/services/local-inference/types.d.ts +0 -309
  1477. package/services/local-inference/types.d.ts.map +0 -1
  1478. package/services/local-inference/types.js +0 -23
  1479. package/services/local-inference/verify.d.ts +0 -8
  1480. package/services/local-inference/verify.d.ts.map +0 -1
  1481. package/services/local-inference/verify.js +0 -7
  1482. package/services/local-inference/voice/barge-in.d.ts +0 -15
  1483. package/services/local-inference/voice/barge-in.d.ts.map +0 -1
  1484. package/services/local-inference/voice/barge-in.js +0 -20
  1485. package/services/local-inference/voice/engine-bridge.d.ts +0 -256
  1486. package/services/local-inference/voice/engine-bridge.d.ts.map +0 -1
  1487. package/services/local-inference/voice/engine-bridge.js +0 -398
  1488. package/services/local-inference/voice/ffi-bindings.d.ts +0 -114
  1489. package/services/local-inference/voice/ffi-bindings.d.ts.map +0 -1
  1490. package/services/local-inference/voice/ffi-bindings.js +0 -281
  1491. package/services/local-inference/voice/index.d.ts +0 -51
  1492. package/services/local-inference/voice/index.d.ts.map +0 -1
  1493. package/services/local-inference/voice/index.js +0 -50
  1494. package/services/local-inference/voice/lifecycle.d.ts +0 -135
  1495. package/services/local-inference/voice/lifecycle.d.ts.map +0 -1
  1496. package/services/local-inference/voice/lifecycle.js +0 -189
  1497. package/services/local-inference/voice/phoneme-tokenizer.d.ts +0 -58
  1498. package/services/local-inference/voice/phoneme-tokenizer.d.ts.map +0 -1
  1499. package/services/local-inference/voice/phoneme-tokenizer.js +0 -53
  1500. package/services/local-inference/voice/phrase-cache.d.ts +0 -24
  1501. package/services/local-inference/voice/phrase-cache.d.ts.map +0 -1
  1502. package/services/local-inference/voice/phrase-cache.js +0 -32
  1503. package/services/local-inference/voice/phrase-chunker.d.ts +0 -20
  1504. package/services/local-inference/voice/phrase-chunker.d.ts.map +0 -1
  1505. package/services/local-inference/voice/phrase-chunker.js +0 -85
  1506. package/services/local-inference/voice/ring-buffer.d.ts +0 -40
  1507. package/services/local-inference/voice/ring-buffer.d.ts.map +0 -1
  1508. package/services/local-inference/voice/ring-buffer.js +0 -85
  1509. package/services/local-inference/voice/rollback-queue.d.ts +0 -24
  1510. package/services/local-inference/voice/rollback-queue.d.ts.map +0 -1
  1511. package/services/local-inference/voice/rollback-queue.js +0 -49
  1512. package/services/local-inference/voice/scheduler.d.ts +0 -47
  1513. package/services/local-inference/voice/scheduler.d.ts.map +0 -1
  1514. package/services/local-inference/voice/scheduler.js +0 -123
  1515. package/services/local-inference/voice/shared-resources.d.ts +0 -119
  1516. package/services/local-inference/voice/shared-resources.d.ts.map +0 -1
  1517. package/services/local-inference/voice/shared-resources.js +0 -83
  1518. package/services/local-inference/voice/speaker-preset-cache.d.ts +0 -28
  1519. package/services/local-inference/voice/speaker-preset-cache.d.ts.map +0 -1
  1520. package/services/local-inference/voice/speaker-preset-cache.js +0 -44
  1521. package/services/local-inference/voice/types.d.ts +0 -80
  1522. package/services/local-inference/voice/types.d.ts.map +0 -1
  1523. package/services/local-inference/voice/voice-preset-format.d.ts +0 -56
  1524. package/services/local-inference/voice/voice-preset-format.d.ts.map +0 -1
  1525. package/services/local-inference/voice/voice-preset-format.js +0 -184
  1526. package/services/plugin-installer.d.ts +0 -22
  1527. package/services/plugin-installer.d.ts.map +0 -1
  1528. package/services/plugin-installer.js +0 -41
  1529. package/test/scripts/task-agent-live-smoke.ts +0 -1335
  1530. /package/services/{local-inference/voice → ambient-audio}/types.js +0 -0
@@ -24,100 +24,111 @@
24
24
  import * as fs from "node:fs";
25
25
  import os from "node:os";
26
26
  import path from "node:path";
27
+ import {
28
+ clearWorkspaceFolderConfig,
29
+ writeWorkspaceFolderConfig,
30
+ } from "@elizaos/core";
27
31
  import Electrobun, {
28
- type ApplicationMenuItemConfig,
29
- BrowserView,
30
- type BrowserWindow,
31
- BuildConfig,
32
- ContextMenu,
33
- GlobalShortcut,
34
- type MenuItemConfig,
35
- Screen,
36
- Session,
37
- Tray,
38
- Updater,
39
- Utils,
32
+ type ApplicationMenuItemConfig,
33
+ BrowserView,
34
+ type BrowserWindow,
35
+ BuildConfig,
36
+ ContextMenu,
37
+ GlobalShortcut,
38
+ type MenuItemConfig,
39
+ Screen,
40
+ Session,
41
+ Tray,
42
+ Updater,
43
+ Utils,
40
44
  } from "electrobun/bun";
41
45
  import { getBrandConfig } from "../brand-config";
46
+ import type { DatabaseSnapshot } from "../database";
42
47
  import { logger } from "../logger";
43
48
  import type {
44
- ClipboardReadResult,
45
- ClipboardWriteOptions,
46
- CursorPosition,
47
- DesktopBuildInfo,
48
- DesktopManagedWindowSnapshot,
49
- DesktopReleaseNotesWindowInfo,
50
- DesktopSessionSnapshot,
51
- DesktopSessionStorageType,
52
- DesktopUpdaterSnapshot,
53
- DisplayInfo,
54
- FileDialogOptions,
55
- FileDialogResult,
56
- MessageBoxOptions,
57
- MessageBoxResult,
58
- NotificationOptions,
59
- PowerState,
60
- ShortcutOptions,
61
- TrayMenuItem,
62
- TrayOptions,
63
- VersionInfo,
64
- WindowBounds,
65
- WindowOptions,
49
+ ClipboardReadResult,
50
+ ClipboardWriteOptions,
51
+ CursorPosition,
52
+ DesktopBuildInfo,
53
+ DesktopManagedWindowSnapshot,
54
+ DesktopReleaseNotesWindowInfo,
55
+ DesktopSessionSnapshot,
56
+ DesktopSessionStorageType,
57
+ DesktopUpdaterSnapshot,
58
+ DisplayInfo,
59
+ FileDialogOptions,
60
+ FileDialogResult,
61
+ MessageBoxOptions,
62
+ MessageBoxResult,
63
+ NotificationOptions,
64
+ PowerState,
65
+ ShortcutOptions,
66
+ TrayMenuItem,
67
+ TrayOptions,
68
+ VersionInfo,
69
+ WindowBounds,
70
+ WindowOptions,
66
71
  } from "../rpc-schema";
67
72
  import type { SendToWebview } from "../types.js";
73
+ import { resolveDesktopUpdateAvailability } from "../update-availability";
68
74
  import {
69
- createBugReportBundle,
70
- getStartupDiagnosticLogTail,
71
- getStartupDiagnosticsSnapshot,
75
+ createBugReportBundle,
76
+ getStartupDiagnosticLogTail,
77
+ getStartupDiagnosticsSnapshot,
72
78
  } from "./agent";
73
79
  import {
74
- createSecurityScopedBookmark,
75
- isAppActive,
76
- isKeyWindow,
77
- makeKeyAndOrderFront,
78
- orderOut,
79
- startAccessingSecurityScopedBookmark,
80
- stopAccessingSecurityScopedBookmarks,
80
+ createSecurityScopedBookmark,
81
+ isAppActive,
82
+ isKeyWindow,
83
+ makeKeyAndOrderFront,
84
+ orderOut,
85
+ startAccessingSecurityScopedBookmark,
86
+ stopAccessingSecurityScopedBookmarks,
81
87
  } from "./mac-window-effects";
82
88
  import {
83
- linuxSysfsOnBattery,
84
- parseLinuxLockedHintOutput,
85
- parseMacOsHidIdleTimeOutput,
86
- parseMacOsPowerSourceOutput,
87
- parseMacOsSessionLockedOutput,
88
- parseWindowsIdleTimeOutput,
89
- parseWindowsLockStateOutput,
90
- parseWindowsPowerLineOutput,
91
- parseXprintidleOutput,
89
+ linuxSysfsOnBattery,
90
+ parseLinuxLockedHintOutput,
91
+ parseMacOsHidIdleTimeOutput,
92
+ parseMacOsPowerSourceOutput,
93
+ parseMacOsSessionLockedOutput,
94
+ parseWindowsIdleTimeOutput,
95
+ parseWindowsLockStateOutput,
96
+ parseWindowsPowerLineOutput,
97
+ parseXprintidleOutput,
92
98
  } from "./power-state";
93
99
  import { checkWebGpuSupport } from "./webgpu-browser-support";
94
100
 
95
101
  interface SetAlwaysOnTopOptions {
96
- flag: boolean;
97
- level?: string;
102
+ flag: boolean;
103
+ level?: string;
98
104
  }
99
105
 
100
106
  interface SetFullscreenOptions {
101
- flag: boolean;
107
+ flag: boolean;
102
108
  }
103
109
 
104
110
  interface SetOpacityOptions {
105
- opacity: number;
111
+ opacity: number;
106
112
  }
107
113
 
108
114
  interface OpenExternalOptions {
109
- url: string;
115
+ url: string;
110
116
  }
111
117
 
112
118
  interface ShowItemInFolderOptions {
113
- path: string;
119
+ path: string;
114
120
  }
115
121
 
122
+ const FALLBACK_TRAY_MENU_ITEMS: TrayMenuItem[] = [
123
+ { id: "tray-show-window", label: "Show Window" },
124
+ { id: "quit", label: "Quit" },
125
+ ];
126
+
116
127
  type ElectrobunEventHandler = (...args: unknown[]) => void;
117
128
 
118
129
  interface ElectrobunEventTarget {
119
- off?: (event: string, handler: ElectrobunEventHandler) => void;
120
- removeListener?: (event: string, handler: ElectrobunEventHandler) => void;
130
+ off?: (event: string, handler: ElectrobunEventHandler) => void;
131
+ removeListener?: (event: string, handler: ElectrobunEventHandler) => void;
121
132
  }
122
133
 
123
134
  // ============================================================================
@@ -125,28 +136,28 @@ interface ElectrobunEventTarget {
125
136
  // ============================================================================
126
137
 
127
138
  const PATH_NAME_MAP: Record<string, string | (() => string)> = {
128
- home: Utils.paths.home,
129
- appData: Utils.paths.appData,
130
- userData: Utils.paths.userData,
131
- userCache: Utils.paths.userCache,
132
- userLogs: Utils.paths.userLogs,
133
- temp: Utils.paths.temp,
134
- cache: Utils.paths.cache,
135
- logs: Utils.paths.logs,
136
- config: Utils.paths.config,
137
- documents: Utils.paths.documents,
138
- downloads: Utils.paths.downloads,
139
- desktop: Utils.paths.desktop,
140
- pictures: Utils.paths.pictures,
141
- music: Utils.paths.music,
142
- videos: Utils.paths.videos,
139
+ home: Utils.paths.home,
140
+ appData: Utils.paths.appData,
141
+ userData: Utils.paths.userData,
142
+ userCache: Utils.paths.userCache,
143
+ userLogs: Utils.paths.userLogs,
144
+ temp: Utils.paths.temp,
145
+ cache: Utils.paths.cache,
146
+ logs: Utils.paths.logs,
147
+ config: Utils.paths.config,
148
+ documents: Utils.paths.documents,
149
+ downloads: Utils.paths.downloads,
150
+ desktop: Utils.paths.desktop,
151
+ pictures: Utils.paths.pictures,
152
+ music: Utils.paths.music,
153
+ videos: Utils.paths.videos,
143
154
  };
144
155
 
145
156
  const DEFAULT_RELEASE_NOTES_URL = getBrandConfig().releaseUrl;
146
157
  const RELEASE_NOTES_PARTITION = getBrandConfig().releaseNotesPartition;
147
158
  const MACOS_IDLE_THRESHOLD_SECONDS = 60;
148
159
  const MACOS_CGSESSION_PATH =
149
- "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession";
160
+ "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession";
150
161
  const LINUX_IDLE_THRESHOLD_SECONDS = 60;
151
162
  const WINDOWS_IDLE_THRESHOLD_SECONDS = 60;
152
163
  const POWER_STATE_PROBE_TIMEOUT_MS = 1_500;
@@ -155,8 +166,8 @@ let activeDesktopManager: DesktopManager | null = null;
155
166
  let nativeContextMenuEventsInstalled = false;
156
167
 
157
168
  export function resetDesktopManagerForTesting(): void {
158
- activeDesktopManager = null;
159
- nativeContextMenuEventsInstalled = false;
169
+ activeDesktopManager = null;
170
+ nativeContextMenuEventsInstalled = false;
160
171
  }
161
172
 
162
173
  /**
@@ -167,44 +178,44 @@ export function resetDesktopManagerForTesting(): void {
167
178
  * unit / integration runs.
168
179
  */
169
180
  export interface NativeShellRunner {
170
- read(argv: string[]): Promise<string>;
171
- readSafe(argv: string[], timeoutMs?: number): Promise<string | null>;
181
+ read(argv: string[]): Promise<string>;
182
+ readSafe(argv: string[], timeoutMs?: number): Promise<string | null>;
172
183
  }
173
184
 
174
185
  const realNativeShellRunner: NativeShellRunner = {
175
- async read(argv) {
176
- const proc = Bun.spawn(argv, {
177
- stdout: "pipe",
178
- stderr: "ignore",
179
- });
180
- const text = await new Response(proc.stdout).text();
181
- await proc.exited;
182
- return text;
183
- },
184
- async readSafe(argv, timeoutMs = POWER_STATE_PROBE_TIMEOUT_MS) {
185
- try {
186
- const proc = Bun.spawn(argv, {
187
- stdout: "pipe",
188
- stderr: "ignore",
189
- });
190
- const timer = setTimeout(() => {
191
- try {
192
- proc.kill();
193
- } catch {
194
- // already exited
195
- }
196
- }, timeoutMs);
197
- const text = await new Response(proc.stdout).text();
198
- await proc.exited;
199
- clearTimeout(timer);
200
- if (typeof proc.exitCode === "number" && proc.exitCode !== 0) {
201
- return null;
202
- }
203
- return text;
204
- } catch {
205
- return null;
206
- }
207
- },
186
+ async read(argv) {
187
+ const proc = Bun.spawn(argv, {
188
+ stdout: "pipe",
189
+ stderr: "ignore",
190
+ });
191
+ const text = await new Response(proc.stdout).text();
192
+ await proc.exited;
193
+ return text;
194
+ },
195
+ async readSafe(argv, timeoutMs = POWER_STATE_PROBE_TIMEOUT_MS) {
196
+ try {
197
+ const proc = Bun.spawn(argv, {
198
+ stdout: "pipe",
199
+ stderr: "ignore",
200
+ });
201
+ const timer = setTimeout(() => {
202
+ try {
203
+ proc.kill();
204
+ } catch {
205
+ // already exited
206
+ }
207
+ }, timeoutMs);
208
+ const text = await new Response(proc.stdout).text();
209
+ await proc.exited;
210
+ clearTimeout(timer);
211
+ if (typeof proc.exitCode === "number" && proc.exitCode !== 0) {
212
+ return null;
213
+ }
214
+ return text;
215
+ } catch {
216
+ return null;
217
+ }
218
+ },
208
219
  };
209
220
 
210
221
  let activeNativeShellRunner: NativeShellRunner = realNativeShellRunner;
@@ -214,49 +225,49 @@ let activeNativeShellRunner: NativeShellRunner = realNativeShellRunner;
214
225
  * Pass `null` to restore the real `Bun.spawn`-backed implementation.
215
226
  */
216
227
  export function setNativeShellRunnerForTesting(
217
- runner: NativeShellRunner | null,
228
+ runner: NativeShellRunner | null,
218
229
  ): void {
219
- activeNativeShellRunner = runner ?? realNativeShellRunner;
230
+ activeNativeShellRunner = runner ?? realNativeShellRunner;
220
231
  }
221
232
 
222
233
  function readProcessStdout(argv: string[]): Promise<string> {
223
- return activeNativeShellRunner.read(argv);
234
+ return activeNativeShellRunner.read(argv);
224
235
  }
225
236
 
226
237
  function readProcessStdoutSafe(
227
- argv: string[],
228
- timeoutMs = POWER_STATE_PROBE_TIMEOUT_MS,
238
+ argv: string[],
239
+ timeoutMs = POWER_STATE_PROBE_TIMEOUT_MS,
229
240
  ): Promise<string | null> {
230
- return activeNativeShellRunner.readSafe(argv, timeoutMs);
241
+ return activeNativeShellRunner.readSafe(argv, timeoutMs);
231
242
  }
232
243
 
233
244
  async function readLinuxLockedHint(): Promise<boolean | null> {
234
- const sessionId = process.env.XDG_SESSION_ID?.trim();
235
- if (!sessionId) {
236
- return null;
237
- }
238
- const output = await readProcessStdoutSafe([
239
- "loginctl",
240
- "show-session",
241
- sessionId,
242
- "-p",
243
- "LockedHint",
244
- ]);
245
- return output ? parseLinuxLockedHintOutput(output) : null;
245
+ const sessionId = process.env.XDG_SESSION_ID?.trim();
246
+ if (!sessionId) {
247
+ return null;
248
+ }
249
+ const output = await readProcessStdoutSafe([
250
+ "loginctl",
251
+ "show-session",
252
+ sessionId,
253
+ "-p",
254
+ "LockedHint",
255
+ ]);
256
+ return output ? parseLinuxLockedHintOutput(output) : null;
246
257
  }
247
258
 
248
259
  const WINDOWS_IDLE_POWERSHELL_SCRIPT = [
249
- "Add-Type -Namespace W -Name U32 -MemberDefinition @'",
250
- ' [DllImport("user32.dll")] public static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);',
251
- " [StructLayout(LayoutKind.Sequential)] public struct LASTINPUTINFO {",
252
- " public uint cbSize; public uint dwTime;",
253
- " }",
254
- "'@;",
255
- "$info = New-Object W.U32+LASTINPUTINFO;",
256
- "$info.cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf($info);",
257
- "[void][W.U32]::GetLastInputInfo([ref]$info);",
258
- "[int]($env:COMPUTERNAME | Out-Null);",
259
- "[int][System.Environment]::TickCount - [int]$info.dwTime",
260
+ "Add-Type -Namespace W -Name U32 -MemberDefinition @'",
261
+ ' [DllImport("user32.dll")] public static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);',
262
+ " [StructLayout(LayoutKind.Sequential)] public struct LASTINPUTINFO {",
263
+ " public uint cbSize; public uint dwTime;",
264
+ " }",
265
+ "'@;",
266
+ "$info = New-Object W.U32+LASTINPUTINFO;",
267
+ "$info.cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf($info);",
268
+ "[void][W.U32]::GetLastInputInfo([ref]$info);",
269
+ "[int]($env:COMPUTERNAME | Out-Null);",
270
+ "[int][System.Environment]::TickCount - [int]$info.dwTime",
260
271
  ].join(" ");
261
272
 
262
273
  // ============================================================================
@@ -271,620 +282,624 @@ const WINDOWS_IDLE_POWERSHELL_SCRIPT = [
271
282
  * webview are sent via the sendToWebview callback.
272
283
  */
273
284
  export class DesktopManager {
274
- private mainWindow: BrowserWindow | null = null;
275
- private tray: Tray | null = null;
276
- private releaseNotesWindow: BrowserWindow | null = null;
277
- private releaseNotesView: BrowserView | null = null;
278
- private shortcuts: Map<string, ShortcutOptions> = new Map();
279
- private notificationCounter = 0;
280
- private sendToWebview: SendToWebview | null = null;
281
- private _windowFocused = true;
282
- private _windowHidden = false;
283
- private _focusPoller: ReturnType<typeof setInterval> | null = null;
284
- private _appActive = false;
285
-
286
- // Callback to open the settings window (set by index.ts)
287
- private openSettingsCallback: ((tabHint?: string) => void) | null = null;
288
- private openSurfaceWindowCallback:
289
- | ((
290
- surface:
291
- | "chat"
292
- | "browser"
293
- | "release"
294
- | "triggers"
295
- | "plugins"
296
- | "connectors"
297
- | "cloud",
298
- browse?: string,
299
- alwaysOnTop?: boolean,
300
- ) => Promise<DesktopManagedWindowSnapshot> | DesktopManagedWindowSnapshot)
301
- | null = null;
302
- private openAppWindowCallback:
303
- | ((options: {
304
- slug?: string;
305
- title: string;
306
- path: string;
307
- alwaysOnTop?: boolean;
308
- }) =>
309
- | Promise<DesktopManagedWindowSnapshot>
310
- | DesktopManagedWindowSnapshot)
311
- | null = null;
312
- private managedWindowAlwaysOnTopCallback:
313
- | ((id: string, flag: boolean) => boolean)
314
- | null = null;
315
- private openExternalHandler:
316
- | ((url: string) => boolean | Promise<boolean>)
317
- | null = null;
318
- private requestQuitCallback: (() => void | Promise<void>) | null = null;
319
- private restoreMainWindowCallback: (() => void | Promise<void>) | null = null;
320
-
321
- // Track menu items for context-menu-clicked matching
322
- private trayMenuItems: Map<string, TrayMenuItem> = new Map();
323
- private trayClickHandler: (() => void) | null = null;
324
- private contextMenuHandler: ElectrobunEventHandler | null = null;
325
- private windowEventHandlers: Partial<
326
- Record<"focus" | "blur" | "close" | "resize" | "move", () => void>
327
- > = {};
328
- private appExitStarted = false;
329
-
330
- constructor() {
331
- activeDesktopManager = this;
332
- this.ensureNativeContextMenuEvents();
333
- }
334
-
335
- // MARK: - Configuration
336
-
337
- /**
338
- * Set the main BrowserWindow reference and wire up window events.
339
- */
340
- setMainWindow(window: BrowserWindow): void {
341
- if (this.mainWindow === window) {
342
- return;
343
- }
344
-
345
- this.teardownWindowEvents(this.mainWindow);
346
- this.mainWindow = window;
347
- this.setupWindowEvents();
348
- }
349
-
350
- async getShellDiagnosticsState(): Promise<{
351
- trayPresent: boolean;
352
- mainWindowPresent: boolean;
353
- windowVisible: boolean;
354
- windowFocused: boolean;
355
- }> {
356
- return {
357
- trayPresent: Boolean(this.tray),
358
- mainWindowPresent: Boolean(this.mainWindow),
359
- windowVisible: (await this.isWindowVisible()).visible,
360
- windowFocused: this._windowFocused,
361
- };
362
- }
363
-
364
- /**
365
- * Set the callback used to push messages to the webview renderer.
366
- */
367
- setSendToWebview(fn: SendToWebview): void {
368
- this.sendToWebview = fn;
369
- }
370
-
371
- /**
372
- * Set the callback used to open the settings window from menus.
373
- */
374
- setOpenSettingsCallback(cb: (tabHint?: string) => void): void {
375
- this.openSettingsCallback = cb;
376
- }
377
-
378
- /**
379
- * Set the callback used to open detached surface windows from RPC or menus.
380
- */
381
- setOpenSurfaceWindowCallback(
382
- cb: (
383
- surface:
384
- | "chat"
385
- | "browser"
386
- | "release"
387
- | "triggers"
388
- | "plugins"
389
- | "connectors"
390
- | "cloud",
391
- browse?: string,
392
- alwaysOnTop?: boolean,
393
- ) => Promise<DesktopManagedWindowSnapshot> | DesktopManagedWindowSnapshot,
394
- ): void {
395
- this.openSurfaceWindowCallback = cb;
396
- }
397
-
398
- setOpenAppWindowCallback(
399
- cb:
400
- | ((options: {
401
- slug?: string;
402
- title: string;
403
- path: string;
404
- alwaysOnTop?: boolean;
405
- }) =>
406
- | Promise<DesktopManagedWindowSnapshot>
407
- | DesktopManagedWindowSnapshot)
408
- | null,
409
- ): void {
410
- this.openAppWindowCallback = cb;
411
- }
412
-
413
- setManagedWindowAlwaysOnTopCallback(
414
- cb: ((id: string, flag: boolean) => boolean) | null,
415
- ): void {
416
- this.managedWindowAlwaysOnTopCallback = cb;
417
- }
418
-
419
- /**
420
- * Optionally handle trusted external URLs inside an app-managed window.
421
- */
422
- setOpenExternalHandler(
423
- cb: ((url: string) => boolean | Promise<boolean>) | null,
424
- ): void {
425
- this.openExternalHandler = cb;
426
- }
427
-
428
- setRequestQuitCallback(cb: (() => void | Promise<void>) | null): void {
429
- this.requestQuitCallback = cb;
430
- }
431
-
432
- setRestoreMainWindowCallback(cb: (() => void | Promise<void>) | null): void {
433
- this.restoreMainWindowCallback = cb;
434
- }
435
-
436
- clearMainWindow(window?: BrowserWindow | null): void {
437
- if (!window || this.mainWindow === window) {
438
- this.teardownWindowEvents(this.mainWindow);
439
- this.mainWindow = null;
440
- }
441
- }
442
-
443
- /**
444
- * Open the settings window via the registered callback.
445
- */
446
- openSettings(tabHint?: string): void {
447
- this.openSettingsCallback?.(tabHint);
448
- }
449
-
450
- /**
451
- * Open a detached surface window via the registered callback.
452
- */
453
- openSurfaceWindow(
454
- surface:
455
- | "chat"
456
- | "browser"
457
- | "release"
458
- | "triggers"
459
- | "plugins"
460
- | "connectors"
461
- | "cloud",
462
- browse?: string,
463
- alwaysOnTop?: boolean,
464
- ): Promise<DesktopManagedWindowSnapshot | null> {
465
- return Promise.resolve(
466
- this.openSurfaceWindowCallback?.(surface, browse, alwaysOnTop) ?? null,
467
- );
468
- }
469
-
470
- async openAppWindow(options: {
471
- slug?: string;
472
- title: string;
473
- path: string;
474
- alwaysOnTop?: boolean;
475
- }): Promise<DesktopManagedWindowSnapshot | null> {
476
- return this.openAppWindowCallback?.(options) ?? null;
477
- }
478
-
479
- setManagedWindowAlwaysOnTop(id: string, flag: boolean): boolean {
480
- return this.managedWindowAlwaysOnTopCallback?.(id, flag) ?? false;
481
- }
482
-
483
- private getWindow(): BrowserWindow | null {
484
- return this.mainWindow ?? null;
485
- }
486
-
487
- private send(message: string, payload?: unknown): void {
488
- if (this.sendToWebview) {
489
- this.sendToWebview(message, payload);
490
- }
491
- }
492
-
493
- private ensureNativeContextMenuEvents(): void {
494
- activeDesktopManager = this;
495
- if (nativeContextMenuEventsInstalled) {
496
- return;
497
- }
498
-
499
- nativeContextMenuEventsInstalled = true;
500
- ContextMenu.on("context-menu-clicked", (event) => {
501
- activeDesktopManager?.handleNativeContextMenuClick(
502
- event as {
503
- data?: {
504
- action?: string;
505
- data?: { text?: string };
506
- };
507
- },
508
- );
509
- });
510
- }
511
-
512
- private handleNativeContextMenuClick(event: {
513
- data?: { action?: string; data?: { text?: string } };
514
- }): void {
515
- const action = event.data?.action;
516
- const text = event.data?.data?.text?.trim();
517
-
518
- if (!action) {
519
- return;
520
- }
521
-
522
- if (action === "copy-selection") {
523
- if (text) {
524
- Utils.clipboardWriteText(text);
525
- }
526
- return;
527
- }
528
-
529
- if (!text) {
530
- return;
531
- }
532
-
533
- if (action === "ask-agent") {
534
- this.send("contextMenuAskAgent", { text });
535
- return;
536
- }
537
-
538
- if (action === "quote-in-chat") {
539
- this.send("contextMenuQuoteInChat", { text });
540
- return;
541
- }
542
-
543
- if (action === "create-skill") {
544
- this.send("contextMenuCreateSkill", { text });
545
- return;
546
- }
547
-
548
- if (action === "save-as-command") {
549
- this.send("contextMenuSaveAsCommand", { text });
550
- }
551
- }
552
-
553
- // MARK: - System Tray
554
-
555
- async createTray(options: TrayOptions): Promise<void> {
556
- if (this.tray) {
557
- await this.destroyTray();
558
- }
559
-
560
- const iconPath = this.resolveIconPath(options.icon);
561
-
562
- this.tray = new Tray({
563
- title: options.tooltip ?? options.title ?? "",
564
- image: iconPath,
565
- });
566
-
567
- if (options.title && process.platform === "darwin") {
568
- this.tray.setTitle(options.title);
569
- }
570
-
571
- if (options.menu) {
572
- this.setTrayMenu({ menu: options.menu });
573
- }
574
-
575
- this.setupTrayEvents();
576
- }
577
-
578
- async updateTray(options: Partial<TrayOptions>): Promise<void> {
579
- if (!this.tray) return;
580
-
581
- if (options.icon) {
582
- const iconPath = this.resolveIconPath(options.icon);
583
- this.tray.setImage(iconPath);
584
- }
585
-
586
- if (options.title !== undefined && process.platform === "darwin") {
587
- this.tray.setTitle(options.title);
588
- }
589
-
590
- if (options.menu) {
591
- this.setTrayMenu({ menu: options.menu });
592
- }
593
- }
594
-
595
- async destroyTray(): Promise<void> {
596
- this.teardownTrayEvents();
597
- if (this.tray) {
598
- this.tray.remove();
599
- this.tray = null;
600
- }
601
- this.trayMenuItems.clear();
602
- }
603
-
604
- setTrayMenu(options: { menu: TrayMenuItem[] }): void {
605
- if (!this.tray) return;
606
-
607
- // Store menu items for action matching
608
- this.trayMenuItems.clear();
609
- this.indexMenuItems(options.menu);
610
-
611
- const template = this.buildMenuTemplate(options.menu);
612
- this.tray.setMenu(template);
613
- }
614
-
615
- /**
616
- * Recursively index menu items by id for context-menu-clicked matching.
617
- */
618
- private indexMenuItems(items: TrayMenuItem[]): void {
619
- for (const item of items) {
620
- if (item.id) {
621
- this.trayMenuItems.set(item.id, item);
622
- }
623
- if (item.submenu) {
624
- this.indexMenuItems(item.submenu);
625
- }
626
- }
627
- }
628
-
629
- /**
630
- * Convert TrayMenuItem[] to Electrobun's menu format.
631
- * Electrobun uses { type, label, action, submenu? }.
632
- */
633
- private buildMenuTemplate(items: TrayMenuItem[]): MenuItemConfig[] {
634
- return items.map((item): MenuItemConfig => {
635
- if (item.type === "separator") {
636
- return { type: "separator" };
637
- }
638
-
639
- const menuItem: MenuItemConfig & { type: "normal" } = {
640
- type: "normal",
641
- label: item.label ?? "",
642
- // Use the item id as the action identifier for matching clicks
643
- action: item.id,
644
- };
645
-
646
- if (item.enabled === false) {
647
- menuItem.enabled = false;
648
- }
649
-
650
- if (item.submenu) {
651
- menuItem.submenu = this.buildMenuTemplate(item.submenu);
652
- }
653
-
654
- return menuItem;
655
- });
656
- }
657
-
658
- private setupTrayEvents(): void {
659
- if (!this.tray) return;
660
-
661
- this.teardownTrayEvents();
662
-
663
- // Electrobun tray click is simpler — no bounds/modifiers
664
- this.trayClickHandler = () => {
665
- void this.showWindow().catch((err: unknown) => {
666
- logger.warn(
667
- `[Desktop] Failed to show window from tray click: ${err instanceof Error ? err.message : String(err)}`,
668
- );
669
- });
670
- this.send("desktopTrayClick", {
671
- x: 0,
672
- y: 0,
673
- button: "left",
674
- modifiers: { alt: false, shift: false, ctrl: false, meta: false },
675
- });
676
- };
677
- this.tray.on("tray-clicked", this.trayClickHandler);
678
-
679
- const triggerAgentRestart = () => {
680
- // Lazy import to avoid circular dependency (agent → desktop → agent).
681
- import("./agent").then(({ getAgentManager }) => {
682
- getAgentManager()
683
- .restart()
684
- .catch((err: unknown) => {
685
- logger.error(
686
- `[Desktop] Agent restart failed: ${err instanceof Error ? err.message : String(err)}`,
687
- );
688
- });
689
- });
690
- };
691
-
692
- // Tray menu item clicks fire "tray-clicked" on the global event bus
693
- // (NOT "context-menu-clicked" — that's for right-click context menus).
694
- // The event data shape is { data: { id, action, data? } }.
695
- // Electrobun emits ElectrobunEvent<TrayClickedData> for tray-clicked;
696
- // the shape carries { data: { action, id, data? } }.
697
- this.contextMenuHandler = ((e: { data?: { action?: string } }): void => {
698
- const action = e?.data?.action;
699
- if (!action) return;
700
-
701
- // Native actions these must work even when the renderer RPC bridge
702
- // is not yet connected (e.g. PGLite init on Windows can take 240s).
703
- if (action === "show" || action === "tray-show-window") {
704
- void this.showWindow().catch((err: unknown) => {
705
- logger.warn(
706
- `[Desktop] Failed to show window from tray menu: ${err instanceof Error ? err.message : String(err)}`,
707
- );
708
- });
709
- } else if (action === "tray-hide-window") {
710
- void this.hideWindow().catch((err: unknown) => {
711
- logger.warn(
712
- `[Desktop] Failed to hide window from tray menu: ${err instanceof Error ? err.message : String(err)}`,
713
- );
714
- });
715
- } else if (action === "restart-agent" || action === "tray-restart") {
716
- triggerAgentRestart();
717
- } else if (action === "quit") {
718
- Utils.quit();
719
- } else if (action === "open-settings") {
720
- this.openSettingsCallback?.();
721
- }
722
-
723
- // Renderer notification for all items
724
- const menuItem = this.trayMenuItems.get(action);
725
- if (menuItem) {
726
- this.send("desktopTrayMenuClick", {
727
- itemId: menuItem.id,
728
- checked:
729
- menuItem.type === "checkbox" ? !menuItem.checked : menuItem.checked,
730
- });
731
- }
732
- }) as ElectrobunEventHandler;
733
- Electrobun.events.on(
734
- "tray-clicked",
735
- this.contextMenuHandler as ElectrobunEventHandler,
736
- );
737
- }
738
-
739
- private teardownTrayEvents(): void {
740
- this.removeEventHandler(this.tray, "tray-clicked", this.trayClickHandler);
741
- this.removeEventHandler(
742
- Electrobun.events,
743
- "tray-clicked",
744
- this.contextMenuHandler,
745
- );
746
- this.trayClickHandler = null;
747
- this.contextMenuHandler = null;
748
- }
749
-
750
- private removeEventHandler(
751
- target: unknown,
752
- event: string,
753
- handler: ElectrobunEventHandler | null | undefined,
754
- ): void {
755
- const eventTarget = target as ElectrobunEventTarget | null | undefined;
756
- if (!eventTarget || !handler) {
757
- return;
758
- }
759
-
760
- if (typeof eventTarget.off === "function") {
761
- eventTarget.off(event, handler);
762
- return;
763
- }
764
-
765
- if (typeof eventTarget.removeListener === "function") {
766
- eventTarget.removeListener(event, handler);
767
- }
768
- }
769
-
770
- // MARK: - Global Shortcuts
771
-
772
- async registerShortcut(
773
- options: ShortcutOptions,
774
- ): Promise<{ success: boolean }> {
775
- // Unregister existing shortcut with same id
776
- if (this.shortcuts.has(options.id)) {
777
- const existing = this.shortcuts.get(options.id);
778
- if (existing) {
779
- GlobalShortcut.unregister(existing.accelerator);
780
- }
781
- }
782
-
783
- try {
784
- GlobalShortcut.register(options.accelerator, () => {
785
- this.send("desktopShortcutPressed", {
786
- id: options.id,
787
- accelerator: options.accelerator,
788
- });
789
- });
790
- this.shortcuts.set(options.id, options);
791
- return { success: true };
792
- } catch {
793
- return { success: false };
794
- }
795
- }
796
-
797
- async unregisterShortcut(options: { id: string }): Promise<void> {
798
- const shortcut = this.shortcuts.get(options.id);
799
- if (shortcut) {
800
- GlobalShortcut.unregister(shortcut.accelerator);
801
- this.shortcuts.delete(options.id);
802
- }
803
- }
804
-
805
- async unregisterAllShortcuts(): Promise<void> {
806
- GlobalShortcut.unregisterAll();
807
- this.shortcuts.clear();
808
- }
809
-
810
- async isShortcutRegistered(options: {
811
- accelerator: string;
812
- }): Promise<{ registered: boolean }> {
813
- return { registered: GlobalShortcut.isRegistered(options.accelerator) };
814
- }
815
-
816
- // MARK: - Auto Launch
817
-
818
- async setAutoLaunch(options: {
819
- enabled: boolean;
820
- openAsHidden?: boolean;
821
- }): Promise<void> {
822
- const appPath = process.execPath;
823
-
824
- const openAsHidden = options.openAsHidden ?? false;
825
-
826
- if (process.platform === "darwin") {
827
- await this.setAutoLaunchMac(options.enabled, appPath, openAsHidden);
828
- } else if (process.platform === "linux") {
829
- this.setAutoLaunchLinux(options.enabled, appPath, openAsHidden);
830
- } else if (process.platform === "win32") {
831
- await this.setAutoLaunchWin(options.enabled, appPath, openAsHidden);
832
- } else {
833
- logger.warn(
834
- `[DesktopManager] setAutoLaunch: unsupported platform ${process.platform}`,
835
- );
836
- }
837
- }
838
-
839
- async getAutoLaunchStatus(): Promise<{
840
- enabled: boolean;
841
- openAsHidden: boolean;
842
- }> {
843
- if (process.platform === "darwin") {
844
- const plistPath = this.getMacLaunchAgentPath();
845
- if (!fs.existsSync(plistPath))
846
- return { enabled: false, openAsHidden: false };
847
- const content = fs.readFileSync(plistPath, "utf8");
848
- return { enabled: true, openAsHidden: content.includes("--hidden") };
849
- }
850
-
851
- if (process.platform === "linux") {
852
- const desktopPath = this.getLinuxAutostartPath();
853
- if (!fs.existsSync(desktopPath))
854
- return { enabled: false, openAsHidden: false };
855
- const content = fs.readFileSync(desktopPath, "utf8");
856
- return { enabled: true, openAsHidden: content.includes("--hidden") };
857
- }
858
-
859
- if (process.platform === "win32") {
860
- const { enabled, openAsHidden } = await this.getAutoLaunchStatusWin();
861
- return { enabled, openAsHidden };
862
- }
863
-
864
- return { enabled: false, openAsHidden: false };
865
- }
866
-
867
- // MARK: - Auto-launch helpers (macOS)
868
-
869
- private getMacLaunchAgentPath(): string {
870
- return path.join(
871
- os.homedir(),
872
- "Library",
873
- "LaunchAgents",
874
- getBrandConfig().macLaunchAgentPlist,
875
- );
876
- }
877
-
878
- private async setAutoLaunchMac(
879
- enabled: boolean,
880
- appPath: string,
881
- openAsHidden = false,
882
- ): Promise<void> {
883
- const plistPath = this.getMacLaunchAgentPath();
884
-
885
- if (enabled) {
886
- const hiddenArg = openAsHidden ? "\n <string>--hidden</string>" : "";
887
- const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
285
+ private mainWindow: BrowserWindow | null = null;
286
+ private tray: Tray | null = null;
287
+ private releaseNotesWindow: BrowserWindow | null = null;
288
+ private releaseNotesView: BrowserView | null = null;
289
+ private shortcuts: Map<string, ShortcutOptions> = new Map();
290
+ private notificationCounter = 0;
291
+ private sendToWebview: SendToWebview | null = null;
292
+ private _windowFocused = true;
293
+ private _windowHidden = false;
294
+ private _focusPoller: ReturnType<typeof setInterval> | null = null;
295
+ private _appActive = false;
296
+
297
+ // Callback to open the settings window (set by index.ts)
298
+ private openSettingsCallback: ((tabHint?: string) => void) | null = null;
299
+ private openSurfaceWindowCallback:
300
+ | ((
301
+ surface:
302
+ | "chat"
303
+ | "browser"
304
+ | "release"
305
+ | "triggers"
306
+ | "plugins"
307
+ | "connectors"
308
+ | "cloud",
309
+ browse?: string,
310
+ alwaysOnTop?: boolean,
311
+ ) => Promise<DesktopManagedWindowSnapshot> | DesktopManagedWindowSnapshot)
312
+ | null = null;
313
+ private openAppWindowCallback:
314
+ | ((options: {
315
+ slug?: string;
316
+ title: string;
317
+ path: string;
318
+ alwaysOnTop?: boolean;
319
+ }) =>
320
+ | Promise<DesktopManagedWindowSnapshot>
321
+ | DesktopManagedWindowSnapshot)
322
+ | null = null;
323
+ private managedWindowAlwaysOnTopCallback:
324
+ | ((id: string, flag: boolean) => boolean)
325
+ | null = null;
326
+ private openExternalHandler:
327
+ | ((url: string) => boolean | Promise<boolean>)
328
+ | null = null;
329
+ private requestQuitCallback: (() => void | Promise<void>) | null = null;
330
+ private restoreMainWindowCallback: (() => void | Promise<void>) | null = null;
331
+ /** Tray-first mode: Dock icon follows main-window presence (macOS). */
332
+ private trayFirstMode = false;
333
+
334
+ // Track menu items for context-menu-clicked matching
335
+ private trayMenuItems: Map<string, TrayMenuItem> = new Map();
336
+ private trayClickHandler: (() => void) | null = null;
337
+ private contextMenuHandler: ElectrobunEventHandler | null = null;
338
+ private windowEventHandlers: Partial<
339
+ Record<"focus" | "blur" | "close" | "resize" | "move", () => void>
340
+ > = {};
341
+ private appExitStarted = false;
342
+
343
+ constructor() {
344
+ activeDesktopManager = this;
345
+ this.ensureNativeContextMenuEvents();
346
+ }
347
+
348
+ // MARK: - Configuration
349
+
350
+ /**
351
+ * Set the main BrowserWindow reference and wire up window events.
352
+ */
353
+ setMainWindow(window: BrowserWindow): void {
354
+ if (this.mainWindow === window) {
355
+ return;
356
+ }
357
+
358
+ this.teardownWindowEvents(this.mainWindow);
359
+ this.mainWindow = window;
360
+ this.setupWindowEvents();
361
+ }
362
+
363
+ async getShellDiagnosticsState(): Promise<{
364
+ trayPresent: boolean;
365
+ mainWindowPresent: boolean;
366
+ windowVisible: boolean;
367
+ windowFocused: boolean;
368
+ }> {
369
+ return {
370
+ trayPresent: Boolean(this.tray),
371
+ mainWindowPresent: Boolean(this.mainWindow),
372
+ windowVisible: (await this.isWindowVisible()).visible,
373
+ windowFocused: this._windowFocused,
374
+ };
375
+ }
376
+
377
+ /**
378
+ * Set the callback used to push messages to the webview renderer.
379
+ */
380
+ setSendToWebview(fn: SendToWebview): void {
381
+ this.sendToWebview = fn;
382
+ }
383
+
384
+ /**
385
+ * Set the callback used to open the settings window from menus.
386
+ */
387
+ setOpenSettingsCallback(cb: (tabHint?: string) => void): void {
388
+ this.openSettingsCallback = cb;
389
+ }
390
+
391
+ /**
392
+ * Set the callback used to open detached surface windows from RPC or menus.
393
+ */
394
+ setOpenSurfaceWindowCallback(
395
+ cb: (
396
+ surface:
397
+ | "chat"
398
+ | "browser"
399
+ | "release"
400
+ | "triggers"
401
+ | "plugins"
402
+ | "connectors"
403
+ | "cloud",
404
+ browse?: string,
405
+ alwaysOnTop?: boolean,
406
+ ) => Promise<DesktopManagedWindowSnapshot> | DesktopManagedWindowSnapshot,
407
+ ): void {
408
+ this.openSurfaceWindowCallback = cb;
409
+ }
410
+
411
+ setOpenAppWindowCallback(
412
+ cb:
413
+ | ((options: {
414
+ slug?: string;
415
+ title: string;
416
+ path: string;
417
+ alwaysOnTop?: boolean;
418
+ }) =>
419
+ | Promise<DesktopManagedWindowSnapshot>
420
+ | DesktopManagedWindowSnapshot)
421
+ | null,
422
+ ): void {
423
+ this.openAppWindowCallback = cb;
424
+ }
425
+
426
+ setManagedWindowAlwaysOnTopCallback(
427
+ cb: ((id: string, flag: boolean) => boolean) | null,
428
+ ): void {
429
+ this.managedWindowAlwaysOnTopCallback = cb;
430
+ }
431
+
432
+ /**
433
+ * Optionally handle trusted external URLs inside an app-managed window.
434
+ */
435
+ setOpenExternalHandler(
436
+ cb: ((url: string) => boolean | Promise<boolean>) | null,
437
+ ): void {
438
+ this.openExternalHandler = cb;
439
+ }
440
+
441
+ setRequestQuitCallback(cb: (() => void | Promise<void>) | null): void {
442
+ this.requestQuitCallback = cb;
443
+ }
444
+
445
+ setRestoreMainWindowCallback(cb: (() => void | Promise<void>) | null): void {
446
+ this.restoreMainWindowCallback = cb;
447
+ }
448
+
449
+ clearMainWindow(window?: BrowserWindow | null): void {
450
+ if (!window || this.mainWindow === window) {
451
+ this.teardownWindowEvents(this.mainWindow);
452
+ this.mainWindow = null;
453
+ this.syncTrayFirstDock(false);
454
+ }
455
+ }
456
+
457
+ /**
458
+ * Open the settings window via the registered callback.
459
+ */
460
+ openSettings(tabHint?: string): void {
461
+ this.openSettingsCallback?.(tabHint);
462
+ }
463
+
464
+ /**
465
+ * Open a detached surface window via the registered callback.
466
+ */
467
+ openSurfaceWindow(
468
+ surface:
469
+ | "chat"
470
+ | "browser"
471
+ | "release"
472
+ | "triggers"
473
+ | "plugins"
474
+ | "connectors"
475
+ | "cloud",
476
+ browse?: string,
477
+ alwaysOnTop?: boolean,
478
+ ): Promise<DesktopManagedWindowSnapshot | null> {
479
+ return Promise.resolve(
480
+ this.openSurfaceWindowCallback?.(surface, browse, alwaysOnTop) ?? null,
481
+ );
482
+ }
483
+
484
+ async openAppWindow(options: {
485
+ slug?: string;
486
+ title: string;
487
+ path: string;
488
+ alwaysOnTop?: boolean;
489
+ }): Promise<DesktopManagedWindowSnapshot | null> {
490
+ return this.openAppWindowCallback?.(options) ?? null;
491
+ }
492
+
493
+ setManagedWindowAlwaysOnTop(id: string, flag: boolean): boolean {
494
+ return this.managedWindowAlwaysOnTopCallback?.(id, flag) ?? false;
495
+ }
496
+
497
+ private getWindow(): BrowserWindow | null {
498
+ return this.mainWindow ?? null;
499
+ }
500
+
501
+ private send(message: string, payload?: unknown): void {
502
+ if (this.sendToWebview) {
503
+ this.sendToWebview(message, payload);
504
+ }
505
+ }
506
+
507
+ private ensureNativeContextMenuEvents(): void {
508
+ activeDesktopManager = this;
509
+ if (nativeContextMenuEventsInstalled) {
510
+ return;
511
+ }
512
+
513
+ nativeContextMenuEventsInstalled = true;
514
+ ContextMenu.on("context-menu-clicked", (event) => {
515
+ activeDesktopManager?.handleNativeContextMenuClick(
516
+ event as {
517
+ data?: {
518
+ action?: string;
519
+ data?: { text?: string };
520
+ };
521
+ },
522
+ );
523
+ });
524
+ }
525
+
526
+ private handleNativeContextMenuClick(event: {
527
+ data?: { action?: string; data?: { text?: string } };
528
+ }): void {
529
+ const action = event.data?.action;
530
+ const text = event.data?.data?.text?.trim();
531
+
532
+ if (!action) {
533
+ return;
534
+ }
535
+
536
+ if (action === "copy-selection") {
537
+ if (text) {
538
+ Utils.clipboardWriteText(text);
539
+ }
540
+ return;
541
+ }
542
+
543
+ if (!text) {
544
+ return;
545
+ }
546
+
547
+ if (action === "ask-agent") {
548
+ this.send("contextMenuAskAgent", { text });
549
+ return;
550
+ }
551
+
552
+ if (action === "quote-in-chat") {
553
+ this.send("contextMenuQuoteInChat", { text });
554
+ return;
555
+ }
556
+
557
+ if (action === "create-skill") {
558
+ this.send("contextMenuCreateSkill", { text });
559
+ return;
560
+ }
561
+
562
+ if (action === "save-as-command") {
563
+ this.send("contextMenuSaveAsCommand", { text });
564
+ }
565
+ }
566
+
567
+ // MARK: - System Tray
568
+
569
+ async createTray(options: TrayOptions): Promise<void> {
570
+ if (this.tray) {
571
+ await this.destroyTray();
572
+ }
573
+
574
+ const iconPath = this.resolveIconPath(options.icon);
575
+
576
+ this.tray = new Tray({
577
+ title: options.tooltip ?? options.title ?? "",
578
+ image: iconPath,
579
+ });
580
+
581
+ if (options.title && process.platform === "darwin") {
582
+ this.tray.setTitle(options.title);
583
+ }
584
+
585
+ this.setTrayMenu({ menu: options.menu ?? FALLBACK_TRAY_MENU_ITEMS });
586
+
587
+ this.setupTrayEvents();
588
+ }
589
+
590
+ async updateTray(options: Partial<TrayOptions>): Promise<void> {
591
+ if (!this.tray) return;
592
+
593
+ if (options.icon) {
594
+ const iconPath = this.resolveIconPath(options.icon);
595
+ this.tray.setImage(iconPath);
596
+ }
597
+
598
+ if (options.title !== undefined && process.platform === "darwin") {
599
+ this.tray.setTitle(options.title);
600
+ }
601
+
602
+ if (options.menu) {
603
+ this.setTrayMenu({ menu: options.menu });
604
+ }
605
+ }
606
+
607
+ async destroyTray(): Promise<void> {
608
+ this.teardownTrayEvents();
609
+ if (this.tray) {
610
+ this.tray.remove();
611
+ this.tray = null;
612
+ }
613
+ this.trayMenuItems.clear();
614
+ }
615
+
616
+ setTrayMenu(options: { menu: TrayMenuItem[] }): void {
617
+ if (!this.tray) return;
618
+
619
+ const menu =
620
+ options.menu.length > 0 ? options.menu : FALLBACK_TRAY_MENU_ITEMS;
621
+
622
+ // Store menu items for action matching
623
+ this.trayMenuItems.clear();
624
+ this.indexMenuItems(menu);
625
+
626
+ const template = this.buildMenuTemplate(menu);
627
+ this.tray.setMenu(template);
628
+ }
629
+
630
+ /**
631
+ * Recursively index menu items by id for context-menu-clicked matching.
632
+ */
633
+ private indexMenuItems(items: TrayMenuItem[]): void {
634
+ for (const item of items) {
635
+ if (item.id) {
636
+ this.trayMenuItems.set(item.id, item);
637
+ }
638
+ if (item.submenu) {
639
+ this.indexMenuItems(item.submenu);
640
+ }
641
+ }
642
+ }
643
+
644
+ /**
645
+ * Convert TrayMenuItem[] to Electrobun's menu format.
646
+ * Electrobun uses { type, label, action, submenu? }.
647
+ */
648
+ private buildMenuTemplate(items: TrayMenuItem[]): MenuItemConfig[] {
649
+ return items.map((item): MenuItemConfig => {
650
+ if (item.type === "separator") {
651
+ return { type: "separator" };
652
+ }
653
+
654
+ const menuItem: MenuItemConfig & { type: "normal" } = {
655
+ type: "normal",
656
+ label: item.label ?? "",
657
+ // Use the item id as the action identifier for matching clicks
658
+ action: item.id,
659
+ };
660
+
661
+ if (item.enabled === false) {
662
+ menuItem.enabled = false;
663
+ }
664
+
665
+ if (item.submenu) {
666
+ menuItem.submenu = this.buildMenuTemplate(item.submenu);
667
+ }
668
+
669
+ return menuItem;
670
+ });
671
+ }
672
+
673
+ private setupTrayEvents(): void {
674
+ if (!this.tray) return;
675
+
676
+ this.teardownTrayEvents();
677
+
678
+ // Electrobun tray click is simpler no bounds/modifiers
679
+ this.trayClickHandler = () => {
680
+ void this.showWindow().catch((err: unknown) => {
681
+ logger.warn(
682
+ `[Desktop] Failed to show window from tray click: ${err instanceof Error ? err.message : String(err)}`,
683
+ );
684
+ });
685
+ this.send("desktopTrayClick", {
686
+ x: 0,
687
+ y: 0,
688
+ button: "left",
689
+ modifiers: { alt: false, shift: false, ctrl: false, meta: false },
690
+ });
691
+ };
692
+ this.tray.on("tray-clicked", this.trayClickHandler);
693
+
694
+ const triggerAgentRestart = () => {
695
+ // Lazy import to avoid circular dependency (agent desktop → agent).
696
+ import("./agent").then(({ getAgentManager }) => {
697
+ getAgentManager()
698
+ .restart()
699
+ .catch((err: unknown) => {
700
+ logger.error(
701
+ `[Desktop] Agent restart failed: ${err instanceof Error ? err.message : String(err)}`,
702
+ );
703
+ });
704
+ });
705
+ };
706
+
707
+ // Tray menu item clicks fire "tray-clicked" on the global event bus
708
+ // (NOT "context-menu-clicked" that's for right-click context menus).
709
+ // The event data shape is { data: { id, action, data? } }.
710
+ // Electrobun emits ElectrobunEvent<TrayClickedData> for tray-clicked;
711
+ // the shape carries { data: { action, id, data? } }.
712
+ this.contextMenuHandler = ((e: { data?: { action?: string } }): void => {
713
+ const action = e.data?.action;
714
+ if (!action) return;
715
+
716
+ // Native actions — these must work even when the renderer RPC bridge
717
+ // is not yet connected (e.g. PGLite init on Windows can take 240s).
718
+ if (action === "show" || action === "tray-show-window") {
719
+ void this.showWindow().catch((err: unknown) => {
720
+ logger.warn(
721
+ `[Desktop] Failed to show window from tray menu: ${err instanceof Error ? err.message : String(err)}`,
722
+ );
723
+ });
724
+ } else if (action === "tray-hide-window") {
725
+ void this.hideWindow().catch((err: unknown) => {
726
+ logger.warn(
727
+ `[Desktop] Failed to hide window from tray menu: ${err instanceof Error ? err.message : String(err)}`,
728
+ );
729
+ });
730
+ } else if (action === "restart-agent" || action === "tray-restart") {
731
+ triggerAgentRestart();
732
+ } else if (action === "quit") {
733
+ Utils.quit();
734
+ } else if (action === "open-settings") {
735
+ this.openSettingsCallback?.();
736
+ }
737
+
738
+ // Renderer notification for all items
739
+ const menuItem = this.trayMenuItems.get(action);
740
+ if (menuItem) {
741
+ this.send("desktopTrayMenuClick", {
742
+ itemId: menuItem.id,
743
+ checked:
744
+ menuItem.type === "checkbox" ? !menuItem.checked : menuItem.checked,
745
+ });
746
+ }
747
+ }) as ElectrobunEventHandler;
748
+ Electrobun.events.on(
749
+ "tray-clicked",
750
+ this.contextMenuHandler as ElectrobunEventHandler,
751
+ );
752
+ }
753
+
754
+ private teardownTrayEvents(): void {
755
+ this.removeEventHandler(this.tray, "tray-clicked", this.trayClickHandler);
756
+ this.removeEventHandler(
757
+ Electrobun.events,
758
+ "tray-clicked",
759
+ this.contextMenuHandler,
760
+ );
761
+ this.trayClickHandler = null;
762
+ this.contextMenuHandler = null;
763
+ }
764
+
765
+ private removeEventHandler(
766
+ target: unknown,
767
+ event: string,
768
+ handler: ElectrobunEventHandler | null | undefined,
769
+ ): void {
770
+ const eventTarget = target as ElectrobunEventTarget | null | undefined;
771
+ if (!eventTarget || !handler) {
772
+ return;
773
+ }
774
+
775
+ if (typeof eventTarget.off === "function") {
776
+ eventTarget.off(event, handler);
777
+ return;
778
+ }
779
+
780
+ if (typeof eventTarget.removeListener === "function") {
781
+ eventTarget.removeListener(event, handler);
782
+ }
783
+ }
784
+
785
+ // MARK: - Global Shortcuts
786
+
787
+ async registerShortcut(
788
+ options: ShortcutOptions,
789
+ ): Promise<{ success: boolean }> {
790
+ // Unregister existing shortcut with same id
791
+ if (this.shortcuts.has(options.id)) {
792
+ const existing = this.shortcuts.get(options.id);
793
+ if (existing) {
794
+ GlobalShortcut.unregister(existing.accelerator);
795
+ }
796
+ }
797
+
798
+ try {
799
+ GlobalShortcut.register(options.accelerator, () => {
800
+ this.send("desktopShortcutPressed", {
801
+ id: options.id,
802
+ accelerator: options.accelerator,
803
+ });
804
+ });
805
+ this.shortcuts.set(options.id, options);
806
+ return { success: true };
807
+ } catch {
808
+ return { success: false };
809
+ }
810
+ }
811
+
812
+ async unregisterShortcut(options: { id: string }): Promise<void> {
813
+ const shortcut = this.shortcuts.get(options.id);
814
+ if (shortcut) {
815
+ GlobalShortcut.unregister(shortcut.accelerator);
816
+ this.shortcuts.delete(options.id);
817
+ }
818
+ }
819
+
820
+ async unregisterAllShortcuts(): Promise<void> {
821
+ GlobalShortcut.unregisterAll();
822
+ this.shortcuts.clear();
823
+ }
824
+
825
+ async isShortcutRegistered(options: {
826
+ accelerator: string;
827
+ }): Promise<{ registered: boolean }> {
828
+ return { registered: GlobalShortcut.isRegistered(options.accelerator) };
829
+ }
830
+
831
+ // MARK: - Auto Launch
832
+
833
+ async setAutoLaunch(options: {
834
+ enabled: boolean;
835
+ openAsHidden?: boolean;
836
+ }): Promise<void> {
837
+ const appPath = process.execPath;
838
+
839
+ const openAsHidden = options.openAsHidden ?? false;
840
+
841
+ if (process.platform === "darwin") {
842
+ await this.setAutoLaunchMac(options.enabled, appPath, openAsHidden);
843
+ } else if (process.platform === "linux") {
844
+ this.setAutoLaunchLinux(options.enabled, appPath, openAsHidden);
845
+ } else if (process.platform === "win32") {
846
+ await this.setAutoLaunchWin(options.enabled, appPath, openAsHidden);
847
+ } else {
848
+ logger.warn(
849
+ `[DesktopManager] setAutoLaunch: unsupported platform ${process.platform}`,
850
+ );
851
+ }
852
+ }
853
+
854
+ async getAutoLaunchStatus(): Promise<{
855
+ enabled: boolean;
856
+ openAsHidden: boolean;
857
+ }> {
858
+ if (process.platform === "darwin") {
859
+ const plistPath = this.getMacLaunchAgentPath();
860
+ if (!fs.existsSync(plistPath))
861
+ return { enabled: false, openAsHidden: false };
862
+ const content = fs.readFileSync(plistPath, "utf8");
863
+ return { enabled: true, openAsHidden: content.includes("--hidden") };
864
+ }
865
+
866
+ if (process.platform === "linux") {
867
+ const desktopPath = this.getLinuxAutostartPath();
868
+ if (!fs.existsSync(desktopPath))
869
+ return { enabled: false, openAsHidden: false };
870
+ const content = fs.readFileSync(desktopPath, "utf8");
871
+ return { enabled: true, openAsHidden: content.includes("--hidden") };
872
+ }
873
+
874
+ if (process.platform === "win32") {
875
+ const { enabled, openAsHidden } = await this.getAutoLaunchStatusWin();
876
+ return { enabled, openAsHidden };
877
+ }
878
+
879
+ return { enabled: false, openAsHidden: false };
880
+ }
881
+
882
+ // MARK: - Auto-launch helpers (macOS)
883
+
884
+ private getMacLaunchAgentPath(): string {
885
+ return path.join(
886
+ os.homedir(),
887
+ "Library",
888
+ "LaunchAgents",
889
+ getBrandConfig().macLaunchAgentPlist,
890
+ );
891
+ }
892
+
893
+ private async setAutoLaunchMac(
894
+ enabled: boolean,
895
+ appPath: string,
896
+ openAsHidden = false,
897
+ ): Promise<void> {
898
+ const plistPath = this.getMacLaunchAgentPath();
899
+
900
+ if (enabled) {
901
+ const hiddenArg = openAsHidden ? "\n <string>--hidden</string>" : "";
902
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
888
903
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
889
904
  <plist version="1.0">
890
905
  <dict>
@@ -901,1406 +916,1431 @@ export class DesktopManager {
901
916
  </dict>
902
917
  </plist>
903
918
  `;
904
- const dir = path.dirname(plistPath);
905
- if (!fs.existsSync(dir)) {
906
- fs.mkdirSync(dir, { recursive: true });
907
- }
908
- fs.writeFileSync(plistPath, plistContent, "utf8");
909
-
910
- const proc = Bun.spawn(["launchctl", "load", plistPath], {
911
- stdout: "pipe",
912
- stderr: "pipe",
913
- });
914
- await proc.exited;
915
- } else {
916
- if (fs.existsSync(plistPath)) {
917
- const proc = Bun.spawn(["launchctl", "unload", plistPath], {
918
- stdout: "pipe",
919
- stderr: "pipe",
920
- });
921
- await proc.exited;
922
- fs.unlinkSync(plistPath);
923
- }
924
- }
925
- }
926
-
927
- // MARK: - Auto-launch helpers (Linux)
928
-
929
- private getLinuxAutostartPath(): string {
930
- return path.join(
931
- os.homedir(),
932
- ".config",
933
- "autostart",
934
- getBrandConfig().linuxDesktopFileName,
935
- );
936
- }
937
-
938
- private setAutoLaunchLinux(
939
- enabled: boolean,
940
- appPath: string,
941
- openAsHidden = false,
942
- ): void {
943
- const desktopPath = this.getLinuxAutostartPath();
944
-
945
- if (enabled) {
946
- const execLine = openAsHidden ? `${appPath} --hidden` : appPath;
947
- const desktopContent = `[Desktop Entry]
919
+ const dir = path.dirname(plistPath);
920
+ if (!fs.existsSync(dir)) {
921
+ fs.mkdirSync(dir, { recursive: true });
922
+ }
923
+ fs.writeFileSync(plistPath, plistContent, "utf8");
924
+
925
+ const proc = Bun.spawn(["launchctl", "load", plistPath], {
926
+ stdout: "pipe",
927
+ stderr: "pipe",
928
+ });
929
+ await proc.exited;
930
+ } else {
931
+ if (fs.existsSync(plistPath)) {
932
+ const proc = Bun.spawn(["launchctl", "unload", plistPath], {
933
+ stdout: "pipe",
934
+ stderr: "pipe",
935
+ });
936
+ await proc.exited;
937
+ fs.unlinkSync(plistPath);
938
+ }
939
+ }
940
+ }
941
+
942
+ // MARK: - Auto-launch helpers (Linux)
943
+
944
+ private getLinuxAutostartPath(): string {
945
+ return path.join(
946
+ os.homedir(),
947
+ ".config",
948
+ "autostart",
949
+ getBrandConfig().linuxDesktopFileName,
950
+ );
951
+ }
952
+
953
+ private setAutoLaunchLinux(
954
+ enabled: boolean,
955
+ appPath: string,
956
+ openAsHidden = false,
957
+ ): void {
958
+ const desktopPath = this.getLinuxAutostartPath();
959
+
960
+ if (enabled) {
961
+ const execLine = openAsHidden ? `${appPath} --hidden` : appPath;
962
+ const desktopContent = `[Desktop Entry]
948
963
  Type=Application
949
964
  Name=${getBrandConfig().linuxDesktopEntryName}
950
965
  Exec=${execLine}
951
966
  X-GNOME-Autostart-enabled=true
952
967
  `;
953
- const dir = path.dirname(desktopPath);
954
- if (!fs.existsSync(dir)) {
955
- fs.mkdirSync(dir, { recursive: true });
956
- }
957
- fs.writeFileSync(desktopPath, desktopContent, "utf8");
958
- } else {
959
- if (fs.existsSync(desktopPath)) {
960
- fs.unlinkSync(desktopPath);
961
- }
962
- }
963
- }
964
-
965
- // MARK: - Auto-launch helpers (Windows)
966
-
967
- private readonly WIN_REG_KEY =
968
- "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
969
-
970
- private async setAutoLaunchWin(
971
- enabled: boolean,
972
- appPath: string,
973
- openAsHidden = false,
974
- ): Promise<void> {
975
- if (enabled) {
976
- const launchValue = openAsHidden ? `${appPath} --hidden` : appPath;
977
- const proc = Bun.spawn(
978
- [
979
- "reg",
980
- "add",
981
- this.WIN_REG_KEY,
982
- "/v",
983
- getBrandConfig().windowsRegistryValueName,
984
- "/t",
985
- "REG_SZ",
986
- "/d",
987
- launchValue,
988
- "/f",
989
- ],
990
- { stdout: "pipe", stderr: "pipe" },
991
- );
992
- await proc.exited;
993
- } else {
994
- const proc = Bun.spawn(
995
- [
996
- "reg",
997
- "delete",
998
- this.WIN_REG_KEY,
999
- "/v",
1000
- getBrandConfig().windowsRegistryValueName,
1001
- "/f",
1002
- ],
1003
- { stdout: "pipe", stderr: "pipe" },
1004
- );
1005
- await proc.exited;
1006
- }
1007
- }
1008
-
1009
- private async getAutoLaunchStatusWin(): Promise<{
1010
- enabled: boolean;
1011
- openAsHidden: boolean;
1012
- }> {
1013
- try {
1014
- const proc = Bun.spawn(
1015
- [
1016
- "reg",
1017
- "query",
1018
- this.WIN_REG_KEY,
1019
- "/v",
1020
- getBrandConfig().windowsRegistryValueName,
1021
- ],
1022
- { stdout: "pipe", stderr: "pipe" },
1023
- );
1024
- const [stdout] = await Promise.all([
1025
- new Response(proc.stdout).text(),
1026
- proc.exited,
1027
- ]);
1028
- if (!stdout.includes(getBrandConfig().windowsRegistryValueName))
1029
- return { enabled: false, openAsHidden: false };
1030
- return { enabled: true, openAsHidden: stdout.includes("--hidden") };
1031
- } catch {
1032
- return { enabled: false, openAsHidden: false };
1033
- }
1034
- }
1035
-
1036
- // MARK: - Window Management
1037
-
1038
- async setWindowOptions(options: WindowOptions): Promise<void> {
1039
- const win = this.getWindow();
1040
- if (!win) return;
1041
-
1042
- if (options.width !== undefined || options.height !== undefined) {
1043
- const { width: currentW, height: currentH } = win.getSize();
1044
- win.setSize(options.width ?? currentW, options.height ?? currentH);
1045
- }
1046
-
1047
- if (options.x !== undefined || options.y !== undefined) {
1048
- const { x: currentX, y: currentY } = win.getPosition();
1049
- win.setPosition(options.x ?? currentX, options.y ?? currentY);
1050
- }
1051
-
1052
- // minWidth/minHeight/maxWidth/maxHeight — not directly supported
1053
- // in Electrobun BrowserWindow. Skip silently.
1054
-
1055
- if (options.alwaysOnTop !== undefined) {
1056
- win.setAlwaysOnTop(options.alwaysOnTop);
1057
- }
1058
-
1059
- if (options.fullscreen !== undefined) {
1060
- win.setFullScreen(options.fullscreen);
1061
- }
1062
-
1063
- // opacity — no setOpacity in Electrobun (no-op)
1064
- if (options.opacity !== undefined) {
1065
- // No-op: Electrobun BrowserWindow does not support setOpacity
1066
- }
1067
-
1068
- if (options.title !== undefined) {
1069
- win.setTitle(options.title);
1070
- }
1071
-
1072
- // resizable — not directly settable post-creation in Electrobun.
1073
- // Skip silently.
1074
- }
1075
-
1076
- async getWindowBounds(): Promise<WindowBounds> {
1077
- const win = this.getWindow();
1078
- if (!win) return { x: 0, y: 0, width: 0, height: 0 };
1079
- const { x, y } = win.getPosition();
1080
- const { width, height } = win.getSize();
1081
- return { x, y, width, height };
1082
- }
1083
-
1084
- async setWindowBounds(options: WindowBounds): Promise<void> {
1085
- const win = this.getWindow();
1086
- if (!win) return;
1087
- win.setPosition(options.x, options.y);
1088
- win.setSize(options.width, options.height);
1089
- }
1090
-
1091
- async minimizeWindow(): Promise<void> {
1092
- this.getWindow()?.minimize();
1093
- }
1094
-
1095
- async unminimizeWindow(): Promise<void> {
1096
- this.getWindow()?.unminimize();
1097
- }
1098
-
1099
- async maximizeWindow(): Promise<void> {
1100
- this.getWindow()?.maximize();
1101
- }
1102
-
1103
- async unmaximizeWindow(): Promise<void> {
1104
- this.getWindow()?.unmaximize();
1105
- }
1106
-
1107
- async closeWindow(): Promise<void> {
1108
- this.getWindow()?.close();
1109
- }
1110
-
1111
- async showWindow(): Promise<void> {
1112
- let win = this.mainWindow;
1113
- if (!win) {
1114
- await this.restoreMainWindowCallback?.();
1115
- win = this.mainWindow;
1116
- }
1117
- if (!win) return;
1118
- try {
1119
- this.showMainWindow(win);
1120
- } catch {
1121
- this.clearMainWindow(win);
1122
- await this.restoreMainWindowCallback?.();
1123
- win = this.mainWindow;
1124
- if (!win) return;
1125
- this.showMainWindow(win);
1126
- }
1127
- }
1128
-
1129
- async hideWindow(): Promise<void> {
1130
- const win = this.mainWindow;
1131
- if (!win) return;
1132
- const ptr = (win as { ptr?: unknown }).ptr;
1133
- if (ptr && process.platform === "darwin") {
1134
- // orderOut removes the window from screen AND Cmd+Tab / Mission Control
1135
- orderOut(ptr as Parameters<typeof orderOut>[0]);
1136
- } else {
1137
- // Non-macOS fallback: minimize
1138
- win.minimize();
1139
- }
1140
- this._windowHidden = true;
1141
- }
1142
-
1143
- async focusWindow(): Promise<void> {
1144
- this.getWindow()?.focus();
1145
- }
1146
-
1147
- async isWindowMaximized(): Promise<{ maximized: boolean }> {
1148
- const win = this.getWindow();
1149
- return { maximized: win ? win.isMaximized() : false };
1150
- }
1151
-
1152
- async isWindowMinimized(): Promise<{ minimized: boolean }> {
1153
- const win = this.getWindow();
1154
- return { minimized: win ? win.isMinimized() : false };
1155
- }
1156
-
1157
- async isWindowVisible(): Promise<{ visible: boolean }> {
1158
- if (this._windowHidden) return { visible: false };
1159
- const win = this.getWindow();
1160
- if (!win) return { visible: false };
1161
- return { visible: !win.isMinimized() };
1162
- }
1163
-
1164
- async isWindowFocused(): Promise<{ focused: boolean }> {
1165
- return { focused: this._windowFocused };
1166
- }
1167
-
1168
- async setAlwaysOnTop(options: SetAlwaysOnTopOptions): Promise<void> {
1169
- // Electrobun setAlwaysOnTop takes a boolean ignore level
1170
- this.getWindow()?.setAlwaysOnTop(options.flag);
1171
- }
1172
-
1173
- async setFullscreen(options: SetFullscreenOptions): Promise<void> {
1174
- this.getWindow()?.setFullScreen(options.flag);
1175
- }
1176
-
1177
- async setOpacity(_options: SetOpacityOptions): Promise<void> {
1178
- // No-op: Electrobun BrowserWindow does not support setOpacity
1179
- }
1180
-
1181
- private showMainWindow(win: BrowserWindow): void {
1182
- const ptr = (win as { ptr?: unknown }).ptr;
1183
- if (ptr && process.platform === "darwin") {
1184
- makeKeyAndOrderFront(ptr as Parameters<typeof makeKeyAndOrderFront>[0]);
1185
- } else {
1186
- win.show();
1187
- win.focus();
1188
- }
1189
- this._windowHidden = false;
1190
- }
1191
-
1192
- private setupWindowEvents(): void {
1193
- const win = this.mainWindow;
1194
- if (!win) return;
1195
-
1196
- const focusHandler = () => {
1197
- this._windowFocused = true;
1198
- this.send("desktopWindowFocus");
1199
- };
1200
- this.windowEventHandlers.focus = focusHandler;
1201
- win.on("focus", focusHandler);
1202
-
1203
- // Blur via native event (Electrobun may not surface this, but try it for free)
1204
- const blurHandler = () => {
1205
- this._windowFocused = false;
1206
- this.send("desktopWindowBlur");
1207
- };
1208
- this.windowEventHandlers.blur = blurHandler;
1209
- win.on("blur", blurHandler);
1210
-
1211
- const closeHandler = () => {
1212
- this.send("desktopWindowClose");
1213
- };
1214
- this.windowEventHandlers.close = closeHandler;
1215
- win.on("close", closeHandler);
1216
-
1217
- const resizeHandler = () => {
1218
- // Electrobun fires resize but doesn't distinguish maximize/unmaximize.
1219
- // We detect state changes to emit the right event.
1220
- if (win.isMaximized()) {
1221
- this.send("desktopWindowMaximize");
1222
- }
1223
- };
1224
- this.windowEventHandlers.resize = resizeHandler;
1225
- win.on("resize", resizeHandler);
1226
-
1227
- let wasMaximized = false;
1228
- const moveHandler = () => {
1229
- // Only emit desktopWindowUnmaximize when transitioning FROM maximized
1230
- // to not-maximized, not on every move during a normal window drag.
1231
- const isMaximized = win.isMaximized();
1232
- if (wasMaximized && !isMaximized) {
1233
- this.send("desktopWindowUnmaximize");
1234
- }
1235
- wasMaximized = isMaximized;
1236
- };
1237
- this.windowEventHandlers.move = moveHandler;
1238
- win.on("move", moveHandler);
1239
-
1240
- // Blur fallback: poll [NSWindow isKeyWindow] at 2Hz on macOS.
1241
- // Electrobun does not guarantee blur events, so this gives bounded
1242
- // ≤500ms latency for focus-loss detection.
1243
- if (process.platform === "darwin") {
1244
- this._startFocusPoller();
1245
- }
1246
- }
1247
-
1248
- private teardownWindowEvents(window: BrowserWindow | null): void {
1249
- if (!window) {
1250
- return;
1251
- }
1252
-
1253
- this.removeEventHandler(window, "focus", this.windowEventHandlers.focus);
1254
- this.removeEventHandler(window, "blur", this.windowEventHandlers.blur);
1255
- this.removeEventHandler(window, "close", this.windowEventHandlers.close);
1256
- this.removeEventHandler(window, "resize", this.windowEventHandlers.resize);
1257
- this.removeEventHandler(window, "move", this.windowEventHandlers.move);
1258
- this.windowEventHandlers = {};
1259
- }
1260
-
1261
- private _startFocusPoller(): void {
1262
- if (this._focusPoller) return;
1263
- this._focusPoller = setInterval(() => {
1264
- const win = this.mainWindow;
1265
- if (!win) return;
1266
-
1267
- // Electrobun does not expose an application activation callback.
1268
- // When the app becomes foreground again with only a minimized window
1269
- // (for example via Dock click), restore it automatically.
1270
- const appActive = isAppActive();
1271
- if (!this._appActive && appActive && win.isMinimized()) {
1272
- void this.showWindow();
1273
- }
1274
- this._appActive = appActive;
1275
-
1276
- const ptr = (win as { ptr?: unknown }).ptr;
1277
- if (!ptr) return;
1278
- const focused = isKeyWindow(ptr as Parameters<typeof isKeyWindow>[0]);
1279
- if (focused !== this._windowFocused) {
1280
- this._windowFocused = focused;
1281
- if (!focused) {
1282
- this.send("desktopWindowBlur");
1283
- }
1284
- }
1285
- }, 500);
1286
- }
1287
-
1288
- // MARK: - Notifications
1289
-
1290
- async showNotification(
1291
- options: NotificationOptions,
1292
- ): Promise<{ id: string }> {
1293
- const id = `notification_${++this.notificationCounter}`;
1294
-
1295
- // Electrobun Utils.showNotification — fire-and-forget, no event callbacks
1296
- Utils.showNotification({
1297
- title: options.title,
1298
- body: options.body,
1299
- subtitle: undefined,
1300
- silent: options.silent,
1301
- });
1302
-
1303
- return { id };
1304
- }
1305
-
1306
- async closeNotification(_options: { id: string }): Promise<void> {
1307
- // Electrobun does not support programmatic notification dismissal.
1308
- // No-op.
1309
- }
1310
-
1311
- // MARK: - Power Monitor
1312
-
1313
- async getPowerState(): Promise<PowerState> {
1314
- try {
1315
- if (process.platform === "darwin") {
1316
- const powerSource = parseMacOsPowerSourceOutput(
1317
- await readProcessStdout(["pmset", "-g", "batt"]),
1318
- );
1319
- const idleTime =
1320
- parseMacOsHidIdleTimeOutput(
1321
- await readProcessStdout(["ioreg", "-c", "IOHIDSystem"]),
1322
- ) ?? 0;
1323
- const locked = fs.existsSync(MACOS_CGSESSION_PATH)
1324
- ? parseMacOsSessionLockedOutput(
1325
- await readProcessStdout([
1326
- MACOS_CGSESSION_PATH,
1327
- "-currentSession",
1328
- ]),
1329
- )
1330
- : null;
1331
- const idleState =
1332
- locked === true
1333
- ? "locked"
1334
- : idleTime >= MACOS_IDLE_THRESHOLD_SECONDS
1335
- ? "idle"
1336
- : locked === false
1337
- ? "active"
1338
- : "unknown";
1339
- return {
1340
- onBattery: powerSource.known ? powerSource.onBattery : false,
1341
- idleState,
1342
- idleTime,
1343
- };
1344
- }
1345
- if (process.platform === "linux") {
1346
- const idleOutput = await readProcessStdoutSafe(["xprintidle"]);
1347
- const idleTime =
1348
- idleOutput !== null ? (parseXprintidleOutput(idleOutput) ?? 0) : 0;
1349
- const locked = await readLinuxLockedHint();
1350
- const idleState =
1351
- locked === true
1352
- ? "locked"
1353
- : idleOutput === null
1354
- ? "unknown"
1355
- : idleTime >= LINUX_IDLE_THRESHOLD_SECONDS
1356
- ? "idle"
1357
- : locked === false
1358
- ? "active"
1359
- : "active";
1360
- return {
1361
- onBattery: linuxSysfsOnBattery(),
1362
- idleState,
1363
- idleTime,
1364
- };
1365
- }
1366
- if (process.platform === "win32") {
1367
- const batteryOutput = await readProcessStdoutSafe([
1368
- "powershell",
1369
- "-NoProfile",
1370
- "-NoLogo",
1371
- "-Command",
1372
- "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SystemInformation]::PowerStatus.PowerLineStatus.ToString()",
1373
- ]);
1374
- const batteryParsed = batteryOutput
1375
- ? parseWindowsPowerLineOutput(batteryOutput)
1376
- : { onBattery: false, known: false };
1377
- const idleOutput = await readProcessStdoutSafe([
1378
- "powershell",
1379
- "-NoProfile",
1380
- "-NoLogo",
1381
- "-Command",
1382
- WINDOWS_IDLE_POWERSHELL_SCRIPT,
1383
- ]);
1384
- const idleTime =
1385
- idleOutput !== null
1386
- ? (parseWindowsIdleTimeOutput(idleOutput) ?? 0)
1387
- : 0;
1388
- const lockOutput = await readProcessStdoutSafe([
1389
- "powershell",
1390
- "-NoProfile",
1391
- "-NoLogo",
1392
- "-Command",
1393
- "(Get-Process logonui -ErrorAction SilentlyContinue).Count",
1394
- ]);
1395
- const locked = lockOutput
1396
- ? parseWindowsLockStateOutput(lockOutput)
1397
- : null;
1398
- const idleState =
1399
- locked === true
1400
- ? "locked"
1401
- : idleOutput === null
1402
- ? "unknown"
1403
- : idleTime >= WINDOWS_IDLE_THRESHOLD_SECONDS
1404
- ? "idle"
1405
- : "active";
1406
- return {
1407
- onBattery: batteryParsed.known ? batteryParsed.onBattery : false,
1408
- idleState,
1409
- idleTime,
1410
- };
1411
- }
1412
- } catch {
1413
- // Fall through to stub below
1414
- }
1415
- return { onBattery: false, idleState: "unknown", idleTime: 0 };
1416
- }
1417
-
1418
- // MARK: - App
1419
-
1420
- private async beginAppExit(reason: string): Promise<void> {
1421
- if (this.appExitStarted) {
1422
- return;
1423
- }
1424
- this.appExitStarted = true;
1425
- await this.showWindow().catch(() => {});
1426
- this.send("desktopShutdownStarted", { reason });
1427
- await new Promise((resolve) => setTimeout(resolve, 150));
1428
- }
1429
-
1430
- async quit(): Promise<void> {
1431
- await this.beginAppExit("desktop-quit");
1432
- if (this.requestQuitCallback) {
1433
- await this.requestQuitCallback();
1434
- return;
1435
- }
1436
- Utils.quit();
1437
- }
1438
-
1439
- async relaunch(): Promise<void> {
1440
- await this.beginAppExit("desktop-relaunch");
1441
- try {
1442
- const child = Bun.spawn([process.execPath, ...process.argv.slice(1)], {
1443
- detached: true,
1444
- stdout: "ignore",
1445
- stderr: "ignore",
1446
- stdin: "ignore",
1447
- });
1448
- // Detach so the new instance survives the parent quitting
1449
- child.unref?.();
1450
- } catch (err) {
1451
- logger.error(
1452
- `[DesktopManager] relaunch: failed to spawn new instance: ${err instanceof Error ? err.message : String(err)}`,
1453
- );
1454
- }
1455
- Utils.quit();
1456
- }
1457
-
1458
- async getVersion(): Promise<VersionInfo> {
1459
- let version = "0.0.0";
1460
- try {
1461
- version = await Updater.localInfo.version();
1462
- } catch {
1463
- // Updater may not be available in dev
1464
- }
1465
-
1466
- return {
1467
- version,
1468
- name: getBrandConfig().appName,
1469
- runtime: `electrobun/${Bun.version}`,
1470
- };
1471
- }
1472
-
1473
- async isPackaged(): Promise<{ packaged: boolean }> {
1474
- // In Electrobun, check if running from a built bundle
1475
- // DEV mode typically has specific env flags
1476
- return {
1477
- packaged:
1478
- process.env.NODE_ENV === "production" || !process.env.ELECTROBUN_DEV,
1479
- };
1480
- }
1481
-
1482
- async getPath(options: { name: string }): Promise<{ path: string }> {
1483
- const mapped = PATH_NAME_MAP[options.name];
1484
- if (typeof mapped === "function") {
1485
- return { path: mapped() };
1486
- }
1487
- if (typeof mapped === "string") {
1488
- return { path: mapped };
1489
- }
1490
-
1491
- // Fallback: try to return a sensible default under userData
1492
- logger.warn(
1493
- `[DesktopManager] Unknown path name "${options.name}", falling back to userData`,
1494
- );
1495
- return { path: Utils.paths.userData };
1496
- }
1497
-
1498
- async getStartupDiagnostics(): Promise<{
1499
- state: "not_started" | "starting" | "running" | "stopped" | "error";
1500
- phase: string;
1501
- updatedAt: string;
1502
- lastError: string | null;
1503
- agentName: string | null;
1504
- port: number | null;
1505
- startedAt: number | null;
1506
- platform: string;
1507
- arch: string;
1508
- configDir: string;
1509
- logPath: string;
1510
- statusPath: string;
1511
- logTail: string;
1512
- appVersion?: string;
1513
- appRuntime?: string;
1514
- packaged?: boolean;
1515
- locale?: string;
1516
- }> {
1517
- const snapshot = getStartupDiagnosticsSnapshot();
1518
- const version = await this.getVersion();
1519
- const packaged = await this.isPackaged();
1520
- const locale = Intl.DateTimeFormat().resolvedOptions().locale;
1521
- return {
1522
- ...snapshot,
1523
- logTail: getStartupDiagnosticLogTail(),
1524
- appVersion: version.version,
1525
- appRuntime: version.runtime,
1526
- packaged: packaged.packaged,
1527
- locale,
1528
- };
1529
- }
1530
-
1531
- async openLogsFolder(): Promise<void> {
1532
- const diagnostics = getStartupDiagnosticsSnapshot();
1533
- const folderPath = path.dirname(diagnostics.logPath);
1534
- Utils.openPath(folderPath);
1535
- }
1536
-
1537
- async createBugReportBundle(options: {
1538
- reportMarkdown: string;
1539
- reportJson: Record<string, unknown>;
1540
- prefix?: string;
1541
- }): Promise<{
1542
- directory: string;
1543
- reportMarkdownPath: string;
1544
- reportJsonPath: string;
1545
- startupLogPath: string | null;
1546
- startupStatusPath: string | null;
1547
- }> {
1548
- return createBugReportBundle(options);
1549
- }
1550
-
1551
- async checkForUpdates(): Promise<DesktopUpdaterSnapshot> {
1552
- const availability = this.getUpdaterAvailability();
1553
- if (!availability.canAutoUpdate) {
1554
- return this.buildUpdaterSnapshot(undefined, availability);
1555
- }
1556
-
1557
- try {
1558
- const result = await Updater.checkForUpdate();
1559
- if (result?.updateAvailable) {
1560
- void this.downloadUpdateWithRetry().catch((error: unknown) => {
1561
- logger.warn(
1562
- `[Desktop] Update download failed after retries: ${error instanceof Error ? error.message : String(error)}`,
1563
- );
1564
- });
1565
- }
1566
- return await this.getUpdaterState();
1567
- } catch (error) {
1568
- return this.buildUpdaterSnapshot(error, availability);
1569
- }
1570
- }
1571
-
1572
- async getUpdaterState(): Promise<DesktopUpdaterSnapshot> {
1573
- return this.buildUpdaterSnapshot();
1574
- }
1575
-
1576
- private async downloadUpdateWithRetry(
1577
- maxAttempts = 3,
1578
- baseDelayMs = 2_000,
1579
- ): Promise<void> {
1580
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1581
- try {
1582
- await Updater.downloadUpdate();
1583
- return;
1584
- } catch (error) {
1585
- const isLastAttempt = attempt === maxAttempts;
1586
- if (isLastAttempt) throw error;
1587
- const delay = baseDelayMs * 2 ** (attempt - 1);
1588
- logger.warn(
1589
- `[Desktop] Update download attempt ${attempt}/${maxAttempts} failed, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`,
1590
- );
1591
- await new Promise((resolve) => setTimeout(resolve, delay));
1592
- }
1593
- }
1594
- }
1595
-
1596
- async applyUpdate(): Promise<void> {
1597
- const availability = this.getUpdaterAvailability();
1598
- if (!availability.canAutoUpdate) {
1599
- throw new Error(
1600
- availability.autoUpdateDisabledReason ??
1601
- "Auto-update is unavailable for this installation.",
1602
- );
1603
- }
1604
-
1605
- Updater.applyUpdate();
1606
- }
1607
-
1608
- async getBuildInfo(): Promise<DesktopBuildInfo> {
1609
- const config = await BuildConfig.get();
1610
- return {
1611
- platform: process.platform,
1612
- arch: process.arch,
1613
- defaultRenderer: config.defaultRenderer,
1614
- availableRenderers: config.availableRenderers,
1615
- cefVersion: config.cefVersion,
1616
- bunVersion: config.bunVersion,
1617
- runtime: config.runtime,
1618
- };
1619
- }
1620
-
1621
- async getDockIconVisibility(): Promise<{ visible: boolean }> {
1622
- if (process.platform !== "darwin") {
1623
- return { visible: true };
1624
- }
1625
-
1626
- return {
1627
- visible: Utils.isDockIconVisible?.() ?? true,
1628
- };
1629
- }
1630
-
1631
- async setDockIconVisibility(options: {
1632
- visible: boolean;
1633
- }): Promise<{ visible: boolean }> {
1634
- if (process.platform === "darwin") {
1635
- Utils.setDockIconVisible?.(options.visible);
1636
- }
1637
-
1638
- return this.getDockIconVisibility();
1639
- }
1640
-
1641
- async showSelectionContextMenu(options: {
1642
- text: string;
1643
- }): Promise<{ shown: boolean }> {
1644
- const text = options.text.trim();
1645
- if (!text) {
1646
- return { shown: false };
1647
- }
1648
-
1649
- const menu: ApplicationMenuItemConfig[] = [
1650
- {
1651
- type: "normal",
1652
- label: "Ask Agent",
1653
- action: "ask-agent",
1654
- data: { text },
1655
- },
1656
- {
1657
- type: "normal",
1658
- label: "Quote in Chat",
1659
- action: "quote-in-chat",
1660
- data: { text },
1661
- },
1662
- {
1663
- type: "normal",
1664
- label: "Create Skill",
1665
- action: "create-skill",
1666
- data: { text },
1667
- },
1668
- {
1669
- type: "normal",
1670
- label: "Save as Command",
1671
- action: "save-as-command",
1672
- data: { text },
1673
- },
1674
- { type: "separator" },
1675
- {
1676
- type: "normal",
1677
- label: "Copy Selection",
1678
- action: "copy-selection",
1679
- data: { text },
1680
- },
1681
- ];
1682
-
1683
- ContextMenu.showContextMenu(menu);
1684
- return { shown: true };
1685
- }
1686
-
1687
- async getSessionSnapshot(options: {
1688
- partition: string;
1689
- }): Promise<DesktopSessionSnapshot> {
1690
- return this.readSessionSnapshot(options.partition);
1691
- }
1692
-
1693
- async clearSessionData(options: {
1694
- partition: string;
1695
- storageTypes?: DesktopSessionStorageType[] | "all";
1696
- clearCookies?: boolean;
1697
- }): Promise<DesktopSessionSnapshot> {
1698
- const session = this.getSession(options.partition);
1699
- const shouldClearCookies =
1700
- options.clearCookies === true ||
1701
- options.storageTypes === "all" ||
1702
- options.storageTypes?.includes("cookies");
1703
-
1704
- if (shouldClearCookies) {
1705
- session.cookies.clear();
1706
- }
1707
-
1708
- if (options.storageTypes === "all") {
1709
- session.clearStorageData("all");
1710
- } else if (options.storageTypes) {
1711
- const storageTypes = options.storageTypes.filter(
1712
- (type) => type !== "cookies",
1713
- );
1714
- if (storageTypes.length > 0) {
1715
- session.clearStorageData(
1716
- storageTypes as Exclude<DesktopSessionStorageType, "cookies">[],
1717
- );
1718
- }
1719
- }
1720
-
1721
- return this.readSessionSnapshot(options.partition);
1722
- }
1723
-
1724
- async getWebGpuBrowserStatus(): Promise<
1725
- ReturnType<typeof checkWebGpuSupport>
1726
- > {
1727
- const config = await BuildConfig.get();
1728
- return checkWebGpuSupport(this.resolvePreferredBrowserRenderer(config));
1729
- }
1730
-
1731
- async openReleaseNotesWindow(options: {
1732
- url: string;
1733
- title?: string;
1734
- }): Promise<DesktopReleaseNotesWindowInfo> {
1735
- const url = this.normalizeReleaseNotesUrl(options.url);
1736
- const title =
1737
- options.title?.trim() || `${getBrandConfig().appName} Release Notes`;
1738
-
1739
- if (this.releaseNotesWindow && this.releaseNotesView) {
1740
- this.releaseNotesWindow.setTitle(title);
1741
- if (this.releaseNotesView.url !== url) {
1742
- this.releaseNotesView.loadURL(url);
1743
- }
1744
- this.releaseNotesWindow.focus();
1745
- return {
1746
- url,
1747
- windowId: this.releaseNotesWindow.id,
1748
- webviewId: this.releaseNotesView.id,
1749
- };
1750
- }
1751
-
1752
- const buildConfig = await BuildConfig.get();
1753
- const renderer = this.resolvePreferredBrowserRenderer(buildConfig);
1754
- const win = new Electrobun.BrowserWindow({
1755
- title,
1756
- frame: {
1757
- x: 170,
1758
- y: 110,
1759
- width: 1180,
1760
- height: 860,
1761
- },
1762
- renderer,
1763
- transparent: false,
1764
- titleBarStyle: "default",
1765
- });
1766
-
1767
- // BrowserWindow always creates a default webview. Remove it so the
1768
- // manual BrowserView becomes the only live browsing surface.
1769
- win.webview.remove();
1770
-
1771
- const view = new BrowserView({
1772
- url,
1773
- renderer,
1774
- windowId: win.id,
1775
- partition: RELEASE_NOTES_PARTITION,
1776
- sandbox: true,
1777
- navigationRules: JSON.stringify(
1778
- this.buildReleaseNotesNavigationRules(url),
1779
- ),
1780
- frame: {
1781
- x: 0,
1782
- y: 0,
1783
- width: win.frame.width,
1784
- height: win.frame.height,
1785
- },
1786
- });
1787
-
1788
- win.on("close", () => {
1789
- this.releaseNotesView?.remove();
1790
- this.releaseNotesWindow = null;
1791
- this.releaseNotesView = null;
1792
- });
1793
-
1794
- this.releaseNotesWindow = win;
1795
- this.releaseNotesView = view;
1796
- win.focus();
1797
-
1798
- return {
1799
- url,
1800
- windowId: win.id,
1801
- webviewId: view.id,
1802
- };
1803
- }
1804
-
1805
- // MARK: - Clipboard
1806
-
1807
- async writeToClipboard(options: ClipboardWriteOptions): Promise<void> {
1808
- if (options.text) {
1809
- Utils.clipboardWriteText(options.text);
1810
- } else if (options.image) {
1811
- // clipboardWriteImage expects a Uint8Array — decode base64 before passing.
1812
- const bytes = Buffer.from(options.image, "base64");
1813
- Utils.clipboardWriteImage(new Uint8Array(bytes));
1814
- }
1815
- // html/rtf not supported by Electrobun clipboard — drop silently
1816
- }
1817
-
1818
- async readFromClipboard(): Promise<ClipboardReadResult> {
1819
- const text = Utils.clipboardReadText();
1820
- let hasImage = false;
1821
- try {
1822
- const imgData = Utils.clipboardReadImage();
1823
- hasImage = !!imgData && imgData.length > 0;
1824
- } catch {
1825
- // clipboardReadImage may throw if no image data
1826
- }
1827
-
1828
- return {
1829
- text: text || undefined,
1830
- // html/rtf not supported by Electrobun clipboard
1831
- hasImage,
1832
- };
1833
- }
1834
-
1835
- async clearClipboard(): Promise<void> {
1836
- Utils.clipboardClear();
1837
- }
1838
-
1839
- async clipboardAvailableFormats(): Promise<{ formats: string[] }> {
1840
- const formats = Utils.clipboardAvailableFormats?.() ?? [];
1841
- return { formats: Array.isArray(formats) ? formats : [] };
1842
- }
1843
-
1844
- // MARK: - Shell
1845
-
1846
- /**
1847
- * Open an external URL in the default browser.
1848
- * SECURITY: restricted to http/https to prevent opening arbitrary protocols.
1849
- */
1850
- async openExternal(options: OpenExternalOptions): Promise<void> {
1851
- const url = typeof options.url === "string" ? options.url.trim() : "";
1852
- try {
1853
- const parsed = new URL(url);
1854
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1855
- throw new Error(
1856
- `Blocked openExternal for non-http(s) URL: ${parsed.protocol}`,
1857
- );
1858
- }
1859
- } catch (err) {
1860
- if (err instanceof TypeError) {
1861
- throw new Error(`Invalid URL passed to openExternal: ${url}`);
1862
- }
1863
- throw err;
1864
- }
1865
-
1866
- if (this.openExternalHandler) {
1867
- try {
1868
- const handled = await this.openExternalHandler(url);
1869
- if (handled) {
1870
- return;
1871
- }
1872
- } catch (err) {
1873
- logger.warn(
1874
- `[Desktop] openExternal handler failed: ${err instanceof Error ? err.message : String(err)}`,
1875
- );
1876
- }
1877
- }
1878
-
1879
- Utils.openExternal(url);
1880
- }
1881
-
1882
- /**
1883
- * Reveal a file in the OS file manager.
1884
- * SECURITY: requires an absolute path.
1885
- */
1886
- async showItemInFolder(options: ShowItemInFolderOptions): Promise<void> {
1887
- const p = typeof options.path === "string" ? options.path.trim() : "";
1888
- if (!p || !path.isAbsolute(p)) {
1889
- throw new Error("showItemInFolder requires an absolute path");
1890
- }
1891
- Utils.showItemInFolder(p);
1892
- }
1893
-
1894
- async openPath(options: { path: string }): Promise<void> {
1895
- const p = typeof options.path === "string" ? options.path.trim() : "";
1896
- if (!p) {
1897
- throw new Error("openPath requires a non-empty path");
1898
- }
1899
- Utils.openPath(p);
1900
- }
1901
-
1902
- async beep(): Promise<void> {
1903
- try {
1904
- if (process.platform === "darwin") {
1905
- Bun.spawn(["afplay", "/System/Library/Sounds/Funk.aiff"], {
1906
- stdout: "ignore",
1907
- stderr: "ignore",
1908
- });
1909
- } else if (process.platform === "linux") {
1910
- // Try paplay (PulseAudio), fall back to terminal bell
1911
- try {
1912
- const proc = Bun.spawn(
1913
- ["paplay", "/usr/share/sounds/freedesktop/stereo/bell.oga"],
1914
- { stdout: "ignore", stderr: "ignore" },
1915
- );
1916
- await proc.exited;
1917
- } catch {
1918
- process.stdout.write("\x07");
1919
- }
1920
- } else if (process.platform === "win32") {
1921
- const proc = Bun.spawn(
1922
- ["powershell", "-NoProfile", "-Command", "[Console]::Beep(800, 200)"],
1923
- { stdout: "ignore", stderr: "ignore" },
1924
- );
1925
- await proc.exited;
1926
- }
1927
- } catch {
1928
- // beep is best-effort never throw
1929
- }
1930
- }
1931
-
1932
- // MARK: - Screen / Display
1933
-
1934
- async getPrimaryDisplay(): Promise<DisplayInfo> {
1935
- const display = Screen.getPrimaryDisplay();
1936
- return {
1937
- id: display.id ?? 0,
1938
- bounds: display.bounds,
1939
- workArea: display.workArea,
1940
- scaleFactor: display.scaleFactor ?? 1,
1941
- isPrimary: display.isPrimary ?? true,
1942
- };
1943
- }
1944
-
1945
- async getAllDisplays(): Promise<{ displays: DisplayInfo[] }> {
1946
- const displays = Screen.getAllDisplays();
1947
- return {
1948
- displays: displays.map((d) => ({
1949
- id: d.id ?? 0,
1950
- bounds: d.bounds,
1951
- workArea: d.workArea,
1952
- scaleFactor: d.scaleFactor ?? 1,
1953
- isPrimary: d.isPrimary ?? false,
1954
- })),
1955
- };
1956
- }
1957
-
1958
- async getCursorPosition(): Promise<CursorPosition> {
1959
- return Screen.getCursorScreenPoint();
1960
- }
1961
-
1962
- // MARK: - Message Box
1963
-
1964
- async showMessageBox(options: MessageBoxOptions): Promise<MessageBoxResult> {
1965
- const autoConfirm =
1966
- process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_DIALOGS === "1" ||
1967
- process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_RESET === "1";
1968
- if (autoConfirm) {
1969
- return { response: options.defaultId ?? 0 };
1970
- }
1971
- const result = await Utils.showMessageBox({
1972
- type: options.type ?? "info",
1973
- title: options.title,
1974
- message: options.message,
1975
- detail: options.detail,
1976
- buttons: options.buttons ?? ["OK"],
1977
- defaultId: options.defaultId ?? 0,
1978
- cancelId: options.cancelId,
1979
- });
1980
- return { response: result.response ?? result };
1981
- }
1982
-
1983
- // MARK: - File Dialogs
1984
-
1985
- /**
1986
- * Show a native file/directory open picker.
1987
- * Maps to Electrobun's Utils.openFileDialog.
1988
- */
1989
- async showOpenDialog(options: FileDialogOptions): Promise<FileDialogResult> {
1990
- const filePaths = await Utils.openFileDialog({
1991
- startingFolder: options.defaultPath,
1992
- allowedFileTypes: options.allowedFileTypes,
1993
- canChooseFiles: options.canChooseFiles ?? true,
1994
- canChooseDirectory: options.canChooseDirectory ?? false,
1995
- allowsMultipleSelection: options.allowsMultipleSelection ?? false,
1996
- });
1997
- const canceled = filePaths.length === 0 || filePaths[0] === "";
1998
- return { canceled, filePaths: canceled ? [] : filePaths };
1999
- }
2000
-
2001
- /**
2002
- * Show a native directory picker for save operations.
2003
- * Electrobun has no separate save dialog — we pick a directory and the
2004
- * caller appends the filename. Returns the chosen directory path.
2005
- */
2006
- async showSaveDialog(options: FileDialogOptions): Promise<FileDialogResult> {
2007
- const filePaths = await Utils.openFileDialog({
2008
- startingFolder: options.defaultPath,
2009
- allowedFileTypes: options.allowedFileTypes,
2010
- canChooseFiles: false,
2011
- canChooseDirectory: true,
2012
- allowsMultipleSelection: false,
2013
- });
2014
- const canceled = filePaths.length === 0 || filePaths[0] === "";
2015
- return { canceled, filePaths: canceled ? [] : filePaths };
2016
- }
2017
-
2018
- /**
2019
- * Pick a workspace folder for store-distributed builds. Maps to a directory-only
2020
- * NSOpenPanel on macOS (via Electrobun's openFileDialog).
2021
- *
2022
- * The `bookmark` field is the OS-specific persistence handle: on macOS, a
2023
- * base64 NSURLBookmarkCreationOptions.WithSecurityScope blob the caller
2024
- * stores and re-resolves on next launch. Non-macOS platforms return null
2025
- * because portals / AppContainer do not use NSURL bookmarks.
2026
- */
2027
- async pickWorkspaceFolder(options: {
2028
- defaultPath?: string;
2029
- promptTitle?: string;
2030
- }): Promise<{ canceled: boolean; path: string; bookmark: string | null }> {
2031
- const filePaths = await Utils.openFileDialog({
2032
- startingFolder: options.defaultPath,
2033
- canChooseFiles: false,
2034
- canChooseDirectory: true,
2035
- allowsMultipleSelection: false,
2036
- });
2037
- const canceled = filePaths.length === 0 || filePaths[0] === "";
2038
- if (canceled) {
2039
- return { canceled: true, path: "", bookmark: null };
2040
- }
2041
- const selectedPath = filePaths[0] ?? "";
2042
- if (!selectedPath) {
2043
- return { canceled: true, path: "", bookmark: null };
2044
- }
2045
- const bookmark =
2046
- process.platform === "darwin"
2047
- ? createSecurityScopedBookmark(selectedPath)
2048
- : null;
2049
- return { canceled: false, path: selectedPath, bookmark };
2050
- }
2051
-
2052
- resolveWorkspaceFolderBookmark(options: { bookmark: string }): {
2053
- ok: boolean;
2054
- path: string;
2055
- stale?: boolean;
2056
- error?: string;
2057
- } {
2058
- if (process.platform !== "darwin") {
2059
- return { ok: true, path: "" };
2060
- }
2061
- const path = startAccessingSecurityScopedBookmark(options.bookmark);
2062
- if (!path) {
2063
- return {
2064
- ok: false,
2065
- path: "",
2066
- error: "Unable to resolve security-scoped bookmark.",
2067
- };
2068
- }
2069
- return { ok: true, path };
2070
- }
2071
-
2072
- releaseWorkspaceFolderBookmarks(): { ok: true } {
2073
- stopAccessingSecurityScopedBookmarks();
2074
- return { ok: true };
2075
- }
2076
-
2077
- // MARK: - Helpers
2078
-
2079
- /**
2080
- * Resolve an icon path, trying absolute, then relative to known asset dirs.
2081
- */
2082
- private resolveIconPath(iconPath: string): string {
2083
- if (path.isAbsolute(iconPath)) {
2084
- return iconPath;
2085
- }
2086
-
2087
- // Try relative to the electrobun assets directory
2088
- const assetsPath = path.join(import.meta.dir, "../../assets", iconPath);
2089
- if (fs.existsSync(assetsPath)) {
2090
- return assetsPath;
2091
- }
2092
-
2093
- // Try relative to cwd
2094
- const cwdPath = path.join(process.cwd(), iconPath);
2095
- if (fs.existsSync(cwdPath)) {
2096
- return cwdPath;
2097
- }
2098
-
2099
- // Return as-is and let Electrobun handle it
2100
- return iconPath;
2101
- }
2102
-
2103
- private getSession(partition: string) {
2104
- const normalized = partition.trim() || "persist:default";
2105
- if (normalized === "persist:default") {
2106
- return Session.defaultSession;
2107
- }
2108
- return Session.fromPartition(normalized);
2109
- }
2110
-
2111
- private readSessionSnapshot(partition: string): DesktopSessionSnapshot {
2112
- const session = this.getSession(partition);
2113
- const cookies = session.cookies.get().map((cookie) => ({
2114
- name: cookie.name,
2115
- domain: cookie.domain,
2116
- path: cookie.path,
2117
- secure: cookie.secure,
2118
- httpOnly: cookie.httpOnly,
2119
- session: (cookie as typeof cookie & { session?: boolean }).session,
2120
- expirationDate: cookie.expirationDate,
2121
- }));
2122
-
2123
- return {
2124
- partition: session.partition,
2125
- persistent: session.partition.startsWith("persist:"),
2126
- cookieCount: cookies.length,
2127
- cookies,
2128
- };
2129
- }
2130
-
2131
- private normalizeReleaseNotesUrl(url: string): string {
2132
- const trimmed = url.trim() || DEFAULT_RELEASE_NOTES_URL;
2133
- const parsed = new URL(trimmed);
2134
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2135
- throw new Error("Release notes URL must use http or https");
2136
- }
2137
- return parsed.toString();
2138
- }
2139
-
2140
- private buildReleaseNotesNavigationRules(url: string): string[] {
2141
- const parsed = new URL(url);
2142
- const origin = parsed.origin.replace(/\/+$/, "");
2143
- return [parsed.toString(), `${origin}/*`];
2144
- }
2145
-
2146
- private async buildUpdaterSnapshot(
2147
- error?: unknown,
2148
- availability = this.getUpdaterAvailability(),
2149
- ): Promise<DesktopUpdaterSnapshot> {
2150
- let currentVersion = "unknown";
2151
- let currentHash: string | undefined;
2152
- let channel: string | undefined;
2153
- let baseUrl: string | undefined;
2154
- let snapshotError =
2155
- error instanceof Error ? error.message : error ? String(error) : null;
2156
-
2157
- try {
2158
- const localInfo = await Updater.getLocallocalInfo();
2159
- currentVersion = localInfo.version;
2160
- currentHash = localInfo.hash;
2161
- channel = localInfo.channel;
2162
- baseUrl = localInfo.baseUrl;
2163
- } catch (localError) {
2164
- if (!snapshotError) {
2165
- snapshotError =
2166
- localError instanceof Error
2167
- ? localError.message
2168
- : String(localError ?? "Unknown updater error");
2169
- }
2170
- }
2171
-
2172
- const updateInfo =
2173
- (Updater.updateInfo?.() as Partial<{
2174
- version: string;
2175
- hash: string;
2176
- updateAvailable: boolean;
2177
- updateReady: boolean;
2178
- error: string;
2179
- }>) ?? {};
2180
- const lastStatusEntry = Updater.getStatusHistory?.().at(-1) ?? null;
2181
-
2182
- return {
2183
- currentVersion,
2184
- currentHash,
2185
- channel,
2186
- baseUrl,
2187
- appBundlePath: availability.appBundlePath,
2188
- canAutoUpdate: availability.canAutoUpdate,
2189
- autoUpdateDisabledReason: availability.autoUpdateDisabledReason,
2190
- updateAvailable: Boolean(updateInfo.updateAvailable),
2191
- updateReady: Boolean(updateInfo.updateReady),
2192
- latestVersion: updateInfo.version ?? null,
2193
- latestHash: updateInfo.hash ?? null,
2194
- error: updateInfo.error || snapshotError,
2195
- lastStatus: lastStatusEntry
2196
- ? {
2197
- status: lastStatusEntry.status,
2198
- message: lastStatusEntry.message,
2199
- timestamp: lastStatusEntry.timestamp,
2200
- }
2201
- : null,
2202
- };
2203
- }
2204
-
2205
- private getUpdaterAvailability(): {
2206
- appBundlePath: string | null;
2207
- canAutoUpdate: boolean;
2208
- autoUpdateDisabledReason: string | null;
2209
- } {
2210
- if (process.platform !== "darwin") {
2211
- return {
2212
- appBundlePath: null,
2213
- canAutoUpdate: true,
2214
- autoUpdateDisabledReason: null,
2215
- };
2216
- }
2217
-
2218
- const appBundlePath = this.resolveMacAppBundlePath(process.execPath);
2219
- if (!appBundlePath) {
2220
- return {
2221
- appBundlePath: null,
2222
- canAutoUpdate: false,
2223
- autoUpdateDisabledReason: `${getBrandConfig().appName} must run from an installed .app bundle to enable in-place updates.`,
2224
- };
2225
- }
2226
-
2227
- const supportedRoots = [
2228
- "/Applications",
2229
- path.join(Utils.paths.home, "Applications"),
2230
- ].map((root) => path.resolve(root));
2231
- const normalizedBundlePath = path.resolve(appBundlePath);
2232
- const inApplications = supportedRoots.some((root) => {
2233
- const normalizedRoot = root.endsWith(path.sep)
2234
- ? root
2235
- : `${root}${path.sep}`;
2236
- return (
2237
- normalizedBundlePath === root ||
2238
- normalizedBundlePath.startsWith(normalizedRoot)
2239
- );
2240
- });
2241
-
2242
- if (inApplications) {
2243
- return {
2244
- appBundlePath: normalizedBundlePath,
2245
- canAutoUpdate: true,
2246
- autoUpdateDisabledReason: null,
2247
- };
2248
- }
2249
-
2250
- return {
2251
- appBundlePath: normalizedBundlePath,
2252
- canAutoUpdate: false,
2253
- autoUpdateDisabledReason: `Move ${path.basename(
2254
- normalizedBundlePath,
2255
- )} to /Applications to enable in-place desktop updates.`,
2256
- };
2257
- }
2258
-
2259
- private resolveMacAppBundlePath(execPath: string): string | null {
2260
- let current = path.resolve(execPath);
2261
- while (true) {
2262
- if (current.endsWith(".app")) {
2263
- return current;
2264
- }
2265
- const parent = path.dirname(current);
2266
- if (parent === current) {
2267
- return null;
2268
- }
2269
- current = parent;
2270
- }
2271
- }
2272
-
2273
- private resolvePreferredBrowserRenderer(
2274
- buildInfo: Awaited<ReturnType<typeof BuildConfig.get>>,
2275
- ): "native" | "cef" {
2276
- if (
2277
- process.platform === "linux" &&
2278
- buildInfo.availableRenderers.includes("cef")
2279
- ) {
2280
- return "cef";
2281
- }
2282
-
2283
- return buildInfo.defaultRenderer;
2284
- }
2285
-
2286
- /**
2287
- * Clean up all resources.
2288
- */
2289
- dispose(): void {
2290
- if (this._focusPoller) {
2291
- clearInterval(this._focusPoller);
2292
- this._focusPoller = null;
2293
- }
2294
- this.teardownWindowEvents(this.mainWindow);
2295
- this.mainWindow = null;
2296
- this.releaseNotesView?.remove();
2297
- this.releaseNotesView = null;
2298
- this.releaseNotesWindow = null;
2299
- this.unregisterAllShortcuts();
2300
- void this.destroyTray();
2301
- this.trayMenuItems.clear();
2302
- this.sendToWebview = null;
2303
- }
968
+ const dir = path.dirname(desktopPath);
969
+ if (!fs.existsSync(dir)) {
970
+ fs.mkdirSync(dir, { recursive: true });
971
+ }
972
+ fs.writeFileSync(desktopPath, desktopContent, "utf8");
973
+ } else {
974
+ if (fs.existsSync(desktopPath)) {
975
+ fs.unlinkSync(desktopPath);
976
+ }
977
+ }
978
+ }
979
+
980
+ // MARK: - Auto-launch helpers (Windows)
981
+
982
+ private readonly WIN_REG_KEY =
983
+ "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
984
+
985
+ private async setAutoLaunchWin(
986
+ enabled: boolean,
987
+ appPath: string,
988
+ openAsHidden = false,
989
+ ): Promise<void> {
990
+ if (enabled) {
991
+ const launchValue = openAsHidden ? `${appPath} --hidden` : appPath;
992
+ const proc = Bun.spawn(
993
+ [
994
+ "reg",
995
+ "add",
996
+ this.WIN_REG_KEY,
997
+ "/v",
998
+ getBrandConfig().windowsRegistryValueName,
999
+ "/t",
1000
+ "REG_SZ",
1001
+ "/d",
1002
+ launchValue,
1003
+ "/f",
1004
+ ],
1005
+ { stdout: "pipe", stderr: "pipe" },
1006
+ );
1007
+ await proc.exited;
1008
+ } else {
1009
+ const proc = Bun.spawn(
1010
+ [
1011
+ "reg",
1012
+ "delete",
1013
+ this.WIN_REG_KEY,
1014
+ "/v",
1015
+ getBrandConfig().windowsRegistryValueName,
1016
+ "/f",
1017
+ ],
1018
+ { stdout: "pipe", stderr: "pipe" },
1019
+ );
1020
+ await proc.exited;
1021
+ }
1022
+ }
1023
+
1024
+ private async getAutoLaunchStatusWin(): Promise<{
1025
+ enabled: boolean;
1026
+ openAsHidden: boolean;
1027
+ }> {
1028
+ try {
1029
+ const proc = Bun.spawn(
1030
+ [
1031
+ "reg",
1032
+ "query",
1033
+ this.WIN_REG_KEY,
1034
+ "/v",
1035
+ getBrandConfig().windowsRegistryValueName,
1036
+ ],
1037
+ { stdout: "pipe", stderr: "pipe" },
1038
+ );
1039
+ const [stdout] = await Promise.all([
1040
+ new Response(proc.stdout).text(),
1041
+ proc.exited,
1042
+ ]);
1043
+ if (!stdout.includes(getBrandConfig().windowsRegistryValueName))
1044
+ return { enabled: false, openAsHidden: false };
1045
+ return { enabled: true, openAsHidden: stdout.includes("--hidden") };
1046
+ } catch {
1047
+ return { enabled: false, openAsHidden: false };
1048
+ }
1049
+ }
1050
+
1051
+ // MARK: - Window Management
1052
+
1053
+ async setWindowOptions(options: WindowOptions): Promise<void> {
1054
+ const win = this.getWindow();
1055
+ if (!win) return;
1056
+
1057
+ if (options.width !== undefined || options.height !== undefined) {
1058
+ const { width: currentW, height: currentH } = win.getSize();
1059
+ win.setSize(options.width ?? currentW, options.height ?? currentH);
1060
+ }
1061
+
1062
+ if (options.x !== undefined || options.y !== undefined) {
1063
+ const { x: currentX, y: currentY } = win.getPosition();
1064
+ win.setPosition(options.x ?? currentX, options.y ?? currentY);
1065
+ }
1066
+
1067
+ // minWidth/minHeight/maxWidth/maxHeight — not directly supported
1068
+ // in Electrobun BrowserWindow. Skip silently.
1069
+
1070
+ if (options.alwaysOnTop !== undefined) {
1071
+ win.setAlwaysOnTop(options.alwaysOnTop);
1072
+ }
1073
+
1074
+ if (options.fullscreen !== undefined) {
1075
+ win.setFullScreen(options.fullscreen);
1076
+ }
1077
+
1078
+ // opacity — no setOpacity in Electrobun (no-op)
1079
+ if (options.opacity !== undefined) {
1080
+ // No-op: Electrobun BrowserWindow does not support setOpacity
1081
+ }
1082
+
1083
+ if (options.title !== undefined) {
1084
+ win.setTitle(options.title);
1085
+ }
1086
+
1087
+ // resizable — not directly settable post-creation in Electrobun.
1088
+ // Skip silently.
1089
+ }
1090
+
1091
+ async getWindowBounds(): Promise<WindowBounds> {
1092
+ const win = this.getWindow();
1093
+ if (!win) return { x: 0, y: 0, width: 0, height: 0 };
1094
+ const { x, y } = win.getPosition();
1095
+ const { width, height } = win.getSize();
1096
+ return { x, y, width, height };
1097
+ }
1098
+
1099
+ async setWindowBounds(options: WindowBounds): Promise<void> {
1100
+ const win = this.getWindow();
1101
+ if (!win) return;
1102
+ win.setPosition(options.x, options.y);
1103
+ win.setSize(options.width, options.height);
1104
+ }
1105
+
1106
+ async minimizeWindow(): Promise<void> {
1107
+ this.getWindow()?.minimize();
1108
+ }
1109
+
1110
+ async unminimizeWindow(): Promise<void> {
1111
+ this.getWindow()?.unminimize();
1112
+ }
1113
+
1114
+ async maximizeWindow(): Promise<void> {
1115
+ this.getWindow()?.maximize();
1116
+ }
1117
+
1118
+ async unmaximizeWindow(): Promise<void> {
1119
+ this.getWindow()?.unmaximize();
1120
+ }
1121
+
1122
+ async closeWindow(): Promise<void> {
1123
+ if (process.env.ELIZAOS_CLOSE_MINIMIZES_TO_TRAY !== "0") {
1124
+ await this.hideWindow();
1125
+ return;
1126
+ }
1127
+ this.getWindow()?.close();
1128
+ }
1129
+
1130
+ async showWindow(): Promise<void> {
1131
+ let win = this.mainWindow;
1132
+ if (!win) {
1133
+ await this.restoreMainWindowCallback?.();
1134
+ win = this.mainWindow;
1135
+ }
1136
+ if (!win) return;
1137
+ try {
1138
+ this.showMainWindow(win);
1139
+ } catch {
1140
+ this.clearMainWindow(win);
1141
+ await this.restoreMainWindowCallback?.();
1142
+ win = this.mainWindow;
1143
+ if (!win) return;
1144
+ this.showMainWindow(win);
1145
+ }
1146
+ }
1147
+
1148
+ async hideWindow(): Promise<void> {
1149
+ const win = this.mainWindow;
1150
+ if (!win) return;
1151
+ const ptr = (win as { ptr?: unknown }).ptr;
1152
+ if (ptr && process.platform === "darwin") {
1153
+ // orderOut removes the window from screen AND Cmd+Tab / Mission Control
1154
+ orderOut(ptr as Parameters<typeof orderOut>[0]);
1155
+ } else {
1156
+ // Non-macOS fallback: minimize
1157
+ win.minimize();
1158
+ }
1159
+ this._windowHidden = true;
1160
+ this.syncTrayFirstDock(false);
1161
+ }
1162
+
1163
+ async focusWindow(): Promise<void> {
1164
+ this.getWindow()?.focus();
1165
+ }
1166
+
1167
+ async isWindowMaximized(): Promise<{ maximized: boolean }> {
1168
+ const win = this.getWindow();
1169
+ return { maximized: win ? win.isMaximized() : false };
1170
+ }
1171
+
1172
+ async isWindowMinimized(): Promise<{ minimized: boolean }> {
1173
+ const win = this.getWindow();
1174
+ return { minimized: win ? win.isMinimized() : false };
1175
+ }
1176
+
1177
+ async isWindowVisible(): Promise<{ visible: boolean }> {
1178
+ if (this._windowHidden) return { visible: false };
1179
+ const win = this.getWindow();
1180
+ if (!win) return { visible: false };
1181
+ return { visible: !win.isMinimized() };
1182
+ }
1183
+
1184
+ async isWindowFocused(): Promise<{ focused: boolean }> {
1185
+ return { focused: this._windowFocused };
1186
+ }
1187
+
1188
+ async setAlwaysOnTop(options: SetAlwaysOnTopOptions): Promise<void> {
1189
+ // Electrobun setAlwaysOnTop takes a boolean — ignore level
1190
+ this.getWindow()?.setAlwaysOnTop(options.flag);
1191
+ }
1192
+
1193
+ async setFullscreen(options: SetFullscreenOptions): Promise<void> {
1194
+ this.getWindow()?.setFullScreen(options.flag);
1195
+ }
1196
+
1197
+ async setOpacity(_options: SetOpacityOptions): Promise<void> {
1198
+ // No-op: Electrobun BrowserWindow does not support setOpacity
1199
+ }
1200
+
1201
+ private showMainWindow(win: BrowserWindow): void {
1202
+ const ptr = (win as { ptr?: unknown }).ptr;
1203
+ if (ptr && process.platform === "darwin") {
1204
+ makeKeyAndOrderFront(ptr as Parameters<typeof makeKeyAndOrderFront>[0]);
1205
+ } else {
1206
+ win.show();
1207
+ win.focus();
1208
+ }
1209
+ this._windowHidden = false;
1210
+ this.syncTrayFirstDock(true);
1211
+ }
1212
+
1213
+ private setupWindowEvents(): void {
1214
+ const win = this.mainWindow;
1215
+ if (!win) return;
1216
+
1217
+ const focusHandler = () => {
1218
+ this._windowFocused = true;
1219
+ this.send("desktopWindowFocus");
1220
+ };
1221
+ this.windowEventHandlers.focus = focusHandler;
1222
+ win.on("focus", focusHandler);
1223
+
1224
+ // Blur via native event (Electrobun may not surface this, but try it for free)
1225
+ const blurHandler = () => {
1226
+ this._windowFocused = false;
1227
+ this.send("desktopWindowBlur");
1228
+ };
1229
+ this.windowEventHandlers.blur = blurHandler;
1230
+ win.on("blur", blurHandler);
1231
+
1232
+ const closeHandler = () => {
1233
+ this.send("desktopWindowClose");
1234
+ };
1235
+ this.windowEventHandlers.close = closeHandler;
1236
+ win.on("close", closeHandler);
1237
+
1238
+ const resizeHandler = () => {
1239
+ // Electrobun fires resize but doesn't distinguish maximize/unmaximize.
1240
+ // We detect state changes to emit the right event.
1241
+ if (win.isMaximized()) {
1242
+ this.send("desktopWindowMaximize");
1243
+ }
1244
+ };
1245
+ this.windowEventHandlers.resize = resizeHandler;
1246
+ win.on("resize", resizeHandler);
1247
+
1248
+ let wasMaximized = false;
1249
+ const moveHandler = () => {
1250
+ // Only emit desktopWindowUnmaximize when transitioning FROM maximized
1251
+ // to not-maximized, not on every move during a normal window drag.
1252
+ const isMaximized = win.isMaximized();
1253
+ if (wasMaximized && !isMaximized) {
1254
+ this.send("desktopWindowUnmaximize");
1255
+ }
1256
+ wasMaximized = isMaximized;
1257
+ };
1258
+ this.windowEventHandlers.move = moveHandler;
1259
+ win.on("move", moveHandler);
1260
+
1261
+ // Blur fallback: poll [NSWindow isKeyWindow] at 2Hz on macOS.
1262
+ // Electrobun does not guarantee blur events, so this gives bounded
1263
+ // ≤500ms latency for focus-loss detection.
1264
+ if (process.platform === "darwin") {
1265
+ this._startFocusPoller();
1266
+ }
1267
+ }
1268
+
1269
+ private teardownWindowEvents(window: BrowserWindow | null): void {
1270
+ if (!window) {
1271
+ return;
1272
+ }
1273
+
1274
+ this.removeEventHandler(window, "focus", this.windowEventHandlers.focus);
1275
+ this.removeEventHandler(window, "blur", this.windowEventHandlers.blur);
1276
+ this.removeEventHandler(window, "close", this.windowEventHandlers.close);
1277
+ this.removeEventHandler(window, "resize", this.windowEventHandlers.resize);
1278
+ this.removeEventHandler(window, "move", this.windowEventHandlers.move);
1279
+ this.windowEventHandlers = {};
1280
+ }
1281
+
1282
+ private _startFocusPoller(): void {
1283
+ if (this._focusPoller) return;
1284
+ this._focusPoller = setInterval(() => {
1285
+ const win = this.mainWindow;
1286
+ if (!win) return;
1287
+
1288
+ // Electrobun does not expose an application activation callback.
1289
+ // When the app becomes foreground again with only a minimized window
1290
+ // (for example via Dock click), restore it automatically.
1291
+ const appActive = isAppActive();
1292
+ if (!this._appActive && appActive && win.isMinimized()) {
1293
+ void this.showWindow();
1294
+ }
1295
+ this._appActive = appActive;
1296
+
1297
+ const ptr = (win as { ptr?: unknown }).ptr;
1298
+ if (!ptr) return;
1299
+ const focused = isKeyWindow(ptr as Parameters<typeof isKeyWindow>[0]);
1300
+ if (focused !== this._windowFocused) {
1301
+ this._windowFocused = focused;
1302
+ if (!focused) {
1303
+ this.send("desktopWindowBlur");
1304
+ }
1305
+ }
1306
+ }, 500);
1307
+ }
1308
+
1309
+ // MARK: - Notifications
1310
+
1311
+ async showNotification(
1312
+ options: NotificationOptions,
1313
+ ): Promise<{ id: string }> {
1314
+ const id = `notification_${++this.notificationCounter}`;
1315
+
1316
+ // Electrobun Utils.showNotification — fire-and-forget, no event callbacks
1317
+ Utils.showNotification({
1318
+ title: options.title,
1319
+ body: options.body,
1320
+ subtitle: undefined,
1321
+ silent: options.silent,
1322
+ });
1323
+
1324
+ return { id };
1325
+ }
1326
+
1327
+ async closeNotification(_options: { id: string }): Promise<void> {
1328
+ // Electrobun does not support programmatic notification dismissal.
1329
+ // No-op.
1330
+ }
1331
+
1332
+ // MARK: - Power Monitor
1333
+
1334
+ async getPowerState(): Promise<PowerState> {
1335
+ try {
1336
+ if (process.platform === "darwin") {
1337
+ const powerSource = parseMacOsPowerSourceOutput(
1338
+ await readProcessStdout(["pmset", "-g", "batt"]),
1339
+ );
1340
+ const idleTime =
1341
+ parseMacOsHidIdleTimeOutput(
1342
+ await readProcessStdout(["ioreg", "-c", "IOHIDSystem"]),
1343
+ ) ?? 0;
1344
+ const locked = fs.existsSync(MACOS_CGSESSION_PATH)
1345
+ ? parseMacOsSessionLockedOutput(
1346
+ await readProcessStdout([
1347
+ MACOS_CGSESSION_PATH,
1348
+ "-currentSession",
1349
+ ]),
1350
+ )
1351
+ : null;
1352
+ const idleState =
1353
+ locked === true
1354
+ ? "locked"
1355
+ : idleTime >= MACOS_IDLE_THRESHOLD_SECONDS
1356
+ ? "idle"
1357
+ : locked === false
1358
+ ? "active"
1359
+ : "unknown";
1360
+ return {
1361
+ onBattery: powerSource.known ? powerSource.onBattery : false,
1362
+ idleState,
1363
+ idleTime,
1364
+ };
1365
+ }
1366
+ if (process.platform === "linux") {
1367
+ const idleOutput = await readProcessStdoutSafe(["xprintidle"]);
1368
+ const idleTime =
1369
+ idleOutput !== null ? (parseXprintidleOutput(idleOutput) ?? 0) : 0;
1370
+ const locked = await readLinuxLockedHint();
1371
+ const idleState =
1372
+ locked === true
1373
+ ? "locked"
1374
+ : idleOutput === null
1375
+ ? "unknown"
1376
+ : idleTime >= LINUX_IDLE_THRESHOLD_SECONDS
1377
+ ? "idle"
1378
+ : locked === false
1379
+ ? "active"
1380
+ : "active";
1381
+ return {
1382
+ onBattery: linuxSysfsOnBattery(),
1383
+ idleState,
1384
+ idleTime,
1385
+ };
1386
+ }
1387
+ if (process.platform === "win32") {
1388
+ const batteryOutput = await readProcessStdoutSafe([
1389
+ "powershell",
1390
+ "-NoProfile",
1391
+ "-NoLogo",
1392
+ "-Command",
1393
+ "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SystemInformation]::PowerStatus.PowerLineStatus.ToString()",
1394
+ ]);
1395
+ const batteryParsed = batteryOutput
1396
+ ? parseWindowsPowerLineOutput(batteryOutput)
1397
+ : { onBattery: false, known: false };
1398
+ const idleOutput = await readProcessStdoutSafe([
1399
+ "powershell",
1400
+ "-NoProfile",
1401
+ "-NoLogo",
1402
+ "-Command",
1403
+ WINDOWS_IDLE_POWERSHELL_SCRIPT,
1404
+ ]);
1405
+ const idleTime =
1406
+ idleOutput !== null
1407
+ ? (parseWindowsIdleTimeOutput(idleOutput) ?? 0)
1408
+ : 0;
1409
+ const lockOutput = await readProcessStdoutSafe([
1410
+ "powershell",
1411
+ "-NoProfile",
1412
+ "-NoLogo",
1413
+ "-Command",
1414
+ "(Get-Process logonui -ErrorAction SilentlyContinue).Count",
1415
+ ]);
1416
+ const locked = lockOutput
1417
+ ? parseWindowsLockStateOutput(lockOutput)
1418
+ : null;
1419
+ const idleState =
1420
+ locked === true
1421
+ ? "locked"
1422
+ : idleOutput === null
1423
+ ? "unknown"
1424
+ : idleTime >= WINDOWS_IDLE_THRESHOLD_SECONDS
1425
+ ? "idle"
1426
+ : "active";
1427
+ return {
1428
+ onBattery: batteryParsed.known ? batteryParsed.onBattery : false,
1429
+ idleState,
1430
+ idleTime,
1431
+ };
1432
+ }
1433
+ } catch {
1434
+ // Fall through to stub below
1435
+ }
1436
+ return { onBattery: false, idleState: "unknown", idleTime: 0 };
1437
+ }
1438
+
1439
+ // MARK: - App
1440
+
1441
+ private async beginAppExit(reason: string): Promise<void> {
1442
+ if (this.appExitStarted) {
1443
+ return;
1444
+ }
1445
+ this.appExitStarted = true;
1446
+ await this.showWindow().catch(() => {});
1447
+ this.send("desktopShutdownStarted", { reason });
1448
+ await new Promise((resolve) => setTimeout(resolve, 150));
1449
+ }
1450
+
1451
+ async quit(): Promise<void> {
1452
+ await this.beginAppExit("desktop-quit");
1453
+ if (this.requestQuitCallback) {
1454
+ await this.requestQuitCallback();
1455
+ return;
1456
+ }
1457
+ Utils.quit();
1458
+ }
1459
+
1460
+ async relaunch(): Promise<void> {
1461
+ await this.beginAppExit("desktop-relaunch");
1462
+ try {
1463
+ const child = Bun.spawn([process.execPath, ...process.argv.slice(1)], {
1464
+ detached: true,
1465
+ stdout: "ignore",
1466
+ stderr: "ignore",
1467
+ stdin: "ignore",
1468
+ });
1469
+ // Detach so the new instance survives the parent quitting
1470
+ child.unref();
1471
+ } catch (err) {
1472
+ logger.error(
1473
+ `[DesktopManager] relaunch: failed to spawn new instance: ${err instanceof Error ? err.message : String(err)}`,
1474
+ );
1475
+ }
1476
+ Utils.quit();
1477
+ }
1478
+
1479
+ async getVersion(): Promise<VersionInfo> {
1480
+ let version = "0.0.0";
1481
+ try {
1482
+ version = await Updater.localInfo.version();
1483
+ } catch {
1484
+ // Updater may not be available in dev
1485
+ }
1486
+
1487
+ return {
1488
+ version,
1489
+ name: getBrandConfig().appName,
1490
+ runtime: `electrobun/${Bun.version}`,
1491
+ };
1492
+ }
1493
+
1494
+ async isPackaged(): Promise<{ packaged: boolean }> {
1495
+ // In Electrobun, check if running from a built bundle
1496
+ // DEV mode typically has specific env flags
1497
+ return {
1498
+ packaged:
1499
+ process.env.NODE_ENV === "production" || !process.env.ELECTROBUN_DEV,
1500
+ };
1501
+ }
1502
+
1503
+ async getPath(options: { name: string }): Promise<{ path: string }> {
1504
+ const mapped = PATH_NAME_MAP[options.name];
1505
+ if (typeof mapped === "function") {
1506
+ return { path: mapped() };
1507
+ }
1508
+ if (typeof mapped === "string") {
1509
+ return { path: mapped };
1510
+ }
1511
+
1512
+ // Fallback: try to return a sensible default under userData
1513
+ logger.warn(
1514
+ `[DesktopManager] Unknown path name "${options.name}", falling back to userData`,
1515
+ );
1516
+ return { path: Utils.paths.userData };
1517
+ }
1518
+
1519
+ async getStartupDiagnostics(): Promise<{
1520
+ state: "not_started" | "starting" | "running" | "stopped" | "error";
1521
+ phase: string;
1522
+ updatedAt: string;
1523
+ lastError: string | null;
1524
+ agentName: string | null;
1525
+ port: number | null;
1526
+ startedAt: number | null;
1527
+ platform: string;
1528
+ arch: string;
1529
+ configDir: string;
1530
+ logPath: string;
1531
+ statusPath: string;
1532
+ database: DatabaseSnapshot;
1533
+ logTail: string;
1534
+ appVersion?: string;
1535
+ appRuntime?: string;
1536
+ packaged?: boolean;
1537
+ locale?: string;
1538
+ }> {
1539
+ const snapshot = getStartupDiagnosticsSnapshot();
1540
+ const version = await this.getVersion();
1541
+ const packaged = await this.isPackaged();
1542
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale;
1543
+ return {
1544
+ ...snapshot,
1545
+ logTail: getStartupDiagnosticLogTail(),
1546
+ appVersion: version.version,
1547
+ appRuntime: version.runtime,
1548
+ packaged: packaged.packaged,
1549
+ locale,
1550
+ };
1551
+ }
1552
+
1553
+ async openLogsFolder(): Promise<void> {
1554
+ const diagnostics = getStartupDiagnosticsSnapshot();
1555
+ const folderPath = path.dirname(diagnostics.logPath);
1556
+ Utils.openPath(folderPath);
1557
+ }
1558
+
1559
+ async createBugReportBundle(options: {
1560
+ reportMarkdown: string;
1561
+ reportJson: Record<string, unknown>;
1562
+ prefix?: string;
1563
+ }): Promise<{
1564
+ directory: string;
1565
+ reportMarkdownPath: string;
1566
+ reportJsonPath: string;
1567
+ startupLogPath: string | null;
1568
+ startupStatusPath: string | null;
1569
+ }> {
1570
+ return createBugReportBundle(options);
1571
+ }
1572
+
1573
+ async checkForUpdates(): Promise<DesktopUpdaterSnapshot> {
1574
+ const availability = this.getUpdaterAvailability();
1575
+ if (!availability.canAutoUpdate) {
1576
+ return this.buildUpdaterSnapshot(undefined, availability);
1577
+ }
1578
+
1579
+ try {
1580
+ const result = await Updater.checkForUpdate();
1581
+ if (result.updateAvailable) {
1582
+ void this.downloadUpdateWithRetry().catch((error: unknown) => {
1583
+ logger.warn(
1584
+ `[Desktop] Update download failed after retries: ${error instanceof Error ? error.message : String(error)}`,
1585
+ );
1586
+ });
1587
+ }
1588
+ return await this.getUpdaterState();
1589
+ } catch (error) {
1590
+ return this.buildUpdaterSnapshot(error, availability);
1591
+ }
1592
+ }
1593
+
1594
+ async getUpdaterState(): Promise<DesktopUpdaterSnapshot> {
1595
+ return this.buildUpdaterSnapshot();
1596
+ }
1597
+
1598
+ private async downloadUpdateWithRetry(
1599
+ maxAttempts = 3,
1600
+ baseDelayMs = 2_000,
1601
+ ): Promise<void> {
1602
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1603
+ try {
1604
+ await Updater.downloadUpdate();
1605
+ return;
1606
+ } catch (error) {
1607
+ const isLastAttempt = attempt === maxAttempts;
1608
+ if (isLastAttempt) throw error;
1609
+ const delay = baseDelayMs * 2 ** (attempt - 1);
1610
+ logger.warn(
1611
+ `[Desktop] Update download attempt ${attempt}/${maxAttempts} failed, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`,
1612
+ );
1613
+ await new Promise((resolve) => setTimeout(resolve, delay));
1614
+ }
1615
+ }
1616
+ }
1617
+
1618
+ async applyUpdate(): Promise<void> {
1619
+ const availability = this.getUpdaterAvailability();
1620
+ if (!availability.canAutoUpdate) {
1621
+ throw new Error(
1622
+ availability.autoUpdateDisabledReason ??
1623
+ "Auto-update is unavailable for this installation.",
1624
+ );
1625
+ }
1626
+
1627
+ Updater.applyUpdate();
1628
+ }
1629
+
1630
+ async getBuildInfo(): Promise<DesktopBuildInfo> {
1631
+ const config = await BuildConfig.get();
1632
+ return {
1633
+ platform: process.platform,
1634
+ arch: process.arch,
1635
+ defaultRenderer: config.defaultRenderer,
1636
+ availableRenderers: config.availableRenderers,
1637
+ cefVersion: config.cefVersion,
1638
+ bunVersion: config.bunVersion,
1639
+ runtime: config.runtime,
1640
+ };
1641
+ }
1642
+
1643
+ async getDockIconVisibility(): Promise<{ visible: boolean }> {
1644
+ if (process.platform !== "darwin") {
1645
+ return { visible: true };
1646
+ }
1647
+
1648
+ return {
1649
+ visible: Utils.isDockIconVisible(),
1650
+ };
1651
+ }
1652
+
1653
+ async setDockIconVisibility(options: {
1654
+ visible: boolean;
1655
+ }): Promise<{ visible: boolean }> {
1656
+ if (process.platform === "darwin") {
1657
+ Utils.setDockIconVisible(options.visible);
1658
+ }
1659
+
1660
+ return this.getDockIconVisibility();
1661
+ }
1662
+
1663
+ /**
1664
+ * Enable tray-first mode: the Dock icon is hidden until a main window is
1665
+ * shown, then follows window presence (hidden when the window is hidden or
1666
+ * closed). Call once at startup. No-op off macOS (setDockIconVisibility
1667
+ * guards the native call).
1668
+ */
1669
+ setTrayFirstMode(enabled: boolean): void {
1670
+ this.trayFirstMode = enabled;
1671
+ if (enabled) {
1672
+ void this.setDockIconVisibility({ visible: false }).catch(() => {});
1673
+ }
1674
+ }
1675
+
1676
+ /**
1677
+ * Signal that a main window has been attached/shown. Reveals the Dock icon in
1678
+ * tray-first mode regardless of how the window was opened — including
1679
+ * createMainWindow()/attachMainWindow() paths (boot, restoreWindow() from a
1680
+ * deep link) that bypass showWindow()/showMainWindow(). No-op outside
1681
+ * tray-first.
1682
+ */
1683
+ markMainWindowShown(): void {
1684
+ this.syncTrayFirstDock(true);
1685
+ }
1686
+
1687
+ /**
1688
+ * Toggle the Dock icon to match window presence, only in tray-first mode.
1689
+ * Routed through every show/hide path so reopen-from-tray (which bypasses
1690
+ * the window create path) keeps the Dock icon correct.
1691
+ */
1692
+ private syncTrayFirstDock(visible: boolean): void {
1693
+ if (!this.trayFirstMode) return;
1694
+ void this.setDockIconVisibility({ visible }).catch(() => {});
1695
+ }
1696
+
1697
+ async showSelectionContextMenu(options: {
1698
+ text: string;
1699
+ }): Promise<{ shown: boolean }> {
1700
+ const text = options.text.trim();
1701
+ if (!text) {
1702
+ return { shown: false };
1703
+ }
1704
+
1705
+ const menu: ApplicationMenuItemConfig[] = [
1706
+ {
1707
+ type: "normal",
1708
+ label: "Ask Agent",
1709
+ action: "ask-agent",
1710
+ data: { text },
1711
+ },
1712
+ {
1713
+ type: "normal",
1714
+ label: "Quote in Chat",
1715
+ action: "quote-in-chat",
1716
+ data: { text },
1717
+ },
1718
+ {
1719
+ type: "normal",
1720
+ label: "Create Skill",
1721
+ action: "create-skill",
1722
+ data: { text },
1723
+ },
1724
+ {
1725
+ type: "normal",
1726
+ label: "Save as Command",
1727
+ action: "save-as-command",
1728
+ data: { text },
1729
+ },
1730
+ { type: "separator" },
1731
+ {
1732
+ type: "normal",
1733
+ label: "Copy Selection",
1734
+ action: "copy-selection",
1735
+ data: { text },
1736
+ },
1737
+ ];
1738
+
1739
+ ContextMenu.showContextMenu(menu);
1740
+ return { shown: true };
1741
+ }
1742
+
1743
+ async getSessionSnapshot(options: {
1744
+ partition: string;
1745
+ }): Promise<DesktopSessionSnapshot> {
1746
+ return this.readSessionSnapshot(options.partition);
1747
+ }
1748
+
1749
+ async clearSessionData(options: {
1750
+ partition: string;
1751
+ storageTypes?: DesktopSessionStorageType[] | "all";
1752
+ clearCookies?: boolean;
1753
+ }): Promise<DesktopSessionSnapshot> {
1754
+ const session = this.getSession(options.partition);
1755
+ const shouldClearCookies =
1756
+ options.clearCookies === true ||
1757
+ options.storageTypes === "all" ||
1758
+ options.storageTypes?.includes("cookies");
1759
+
1760
+ if (shouldClearCookies) {
1761
+ session.cookies.clear();
1762
+ }
1763
+
1764
+ if (options.storageTypes === "all") {
1765
+ session.clearStorageData("all");
1766
+ } else if (options.storageTypes) {
1767
+ const storageTypes = options.storageTypes.filter(
1768
+ (type) => type !== "cookies",
1769
+ );
1770
+ if (storageTypes.length > 0) {
1771
+ session.clearStorageData(
1772
+ storageTypes as Exclude<DesktopSessionStorageType, "cookies">[],
1773
+ );
1774
+ }
1775
+ }
1776
+
1777
+ return this.readSessionSnapshot(options.partition);
1778
+ }
1779
+
1780
+ async getWebGpuBrowserStatus(): Promise<
1781
+ ReturnType<typeof checkWebGpuSupport>
1782
+ > {
1783
+ const config = await BuildConfig.get();
1784
+ return checkWebGpuSupport(this.resolvePreferredBrowserRenderer(config));
1785
+ }
1786
+
1787
+ async openReleaseNotesWindow(options: {
1788
+ url: string;
1789
+ title?: string;
1790
+ }): Promise<DesktopReleaseNotesWindowInfo> {
1791
+ const url = this.normalizeReleaseNotesUrl(options.url);
1792
+ const title =
1793
+ options.title?.trim() || `${getBrandConfig().appName} Release Notes`;
1794
+
1795
+ if (this.releaseNotesWindow && this.releaseNotesView) {
1796
+ this.releaseNotesWindow.setTitle(title);
1797
+ if (this.releaseNotesView.url !== url) {
1798
+ this.releaseNotesView.loadURL(url);
1799
+ }
1800
+ this.releaseNotesWindow.focus();
1801
+ return {
1802
+ url,
1803
+ windowId: this.releaseNotesWindow.id,
1804
+ webviewId: this.releaseNotesView.id,
1805
+ };
1806
+ }
1807
+
1808
+ const buildConfig = await BuildConfig.get();
1809
+ const renderer = this.resolvePreferredBrowserRenderer(buildConfig);
1810
+ const win = new Electrobun.BrowserWindow({
1811
+ title,
1812
+ frame: {
1813
+ x: 170,
1814
+ y: 110,
1815
+ width: 1180,
1816
+ height: 860,
1817
+ },
1818
+ renderer,
1819
+ transparent: false,
1820
+ titleBarStyle: "default",
1821
+ });
1822
+
1823
+ // BrowserWindow always creates a default webview. Remove it so the
1824
+ // manual BrowserView becomes the only live browsing surface.
1825
+ win.webview.remove();
1826
+
1827
+ const view = new BrowserView({
1828
+ url,
1829
+ renderer,
1830
+ windowId: win.id,
1831
+ partition: RELEASE_NOTES_PARTITION,
1832
+ sandbox: true,
1833
+ navigationRules: JSON.stringify(
1834
+ this.buildReleaseNotesNavigationRules(url),
1835
+ ),
1836
+ frame: {
1837
+ x: 0,
1838
+ y: 0,
1839
+ width: win.frame.width,
1840
+ height: win.frame.height,
1841
+ },
1842
+ });
1843
+
1844
+ win.on("close", () => {
1845
+ this.releaseNotesView?.remove();
1846
+ this.releaseNotesWindow = null;
1847
+ this.releaseNotesView = null;
1848
+ });
1849
+
1850
+ this.releaseNotesWindow = win;
1851
+ this.releaseNotesView = view;
1852
+ win.focus();
1853
+
1854
+ return {
1855
+ url,
1856
+ windowId: win.id,
1857
+ webviewId: view.id,
1858
+ };
1859
+ }
1860
+
1861
+ // MARK: - Clipboard
1862
+
1863
+ async writeToClipboard(options: ClipboardWriteOptions): Promise<void> {
1864
+ if (options.text) {
1865
+ Utils.clipboardWriteText(options.text);
1866
+ } else if (options.image) {
1867
+ // clipboardWriteImage expects a Uint8Array — decode base64 before passing.
1868
+ const bytes = Buffer.from(options.image, "base64");
1869
+ Utils.clipboardWriteImage(new Uint8Array(bytes));
1870
+ }
1871
+ // html/rtf not supported by Electrobun clipboard — drop silently
1872
+ }
1873
+
1874
+ async readFromClipboard(): Promise<ClipboardReadResult> {
1875
+ const text = Utils.clipboardReadText();
1876
+ let hasImage = false;
1877
+ try {
1878
+ const imgData = Utils.clipboardReadImage();
1879
+ hasImage = !!imgData && imgData.length > 0;
1880
+ } catch {
1881
+ // clipboardReadImage may throw if no image data
1882
+ }
1883
+
1884
+ return {
1885
+ text: text || undefined,
1886
+ // html/rtf not supported by Electrobun clipboard
1887
+ hasImage,
1888
+ };
1889
+ }
1890
+
1891
+ async clearClipboard(): Promise<void> {
1892
+ Utils.clipboardClear();
1893
+ }
1894
+
1895
+ async clipboardAvailableFormats(): Promise<{ formats: string[] }> {
1896
+ const formats = Utils.clipboardAvailableFormats();
1897
+ return { formats: Array.isArray(formats) ? formats : [] };
1898
+ }
1899
+
1900
+ // MARK: - Shell
1901
+
1902
+ /**
1903
+ * Open an external URL in the default browser.
1904
+ * SECURITY: restricted to http/https to prevent opening arbitrary protocols.
1905
+ */
1906
+ async openExternal(options: OpenExternalOptions): Promise<void> {
1907
+ const url = typeof options.url === "string" ? options.url.trim() : "";
1908
+ try {
1909
+ const parsed = new URL(url);
1910
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1911
+ throw new Error(
1912
+ `Blocked openExternal for non-http(s) URL: ${parsed.protocol}`,
1913
+ );
1914
+ }
1915
+ } catch (err) {
1916
+ if (err instanceof TypeError) {
1917
+ throw new Error(`Invalid URL passed to openExternal: ${url}`);
1918
+ }
1919
+ throw err;
1920
+ }
1921
+
1922
+ if (this.openExternalHandler) {
1923
+ try {
1924
+ const handled = await this.openExternalHandler(url);
1925
+ if (handled) {
1926
+ return;
1927
+ }
1928
+ } catch (err) {
1929
+ logger.warn(
1930
+ `[Desktop] openExternal handler failed: ${err instanceof Error ? err.message : String(err)}`,
1931
+ );
1932
+ }
1933
+ }
1934
+
1935
+ Utils.openExternal(url);
1936
+ }
1937
+
1938
+ /**
1939
+ * Reveal a file in the OS file manager.
1940
+ * SECURITY: requires an absolute path.
1941
+ */
1942
+ async showItemInFolder(options: ShowItemInFolderOptions): Promise<void> {
1943
+ const p = typeof options.path === "string" ? options.path.trim() : "";
1944
+ if (!p || !path.isAbsolute(p)) {
1945
+ throw new Error("showItemInFolder requires an absolute path");
1946
+ }
1947
+ Utils.showItemInFolder(p);
1948
+ }
1949
+
1950
+ async openPath(options: { path: string }): Promise<void> {
1951
+ const p = typeof options.path === "string" ? options.path.trim() : "";
1952
+ if (!p) {
1953
+ throw new Error("openPath requires a non-empty path");
1954
+ }
1955
+ Utils.openPath(p);
1956
+ }
1957
+
1958
+ async beep(): Promise<void> {
1959
+ try {
1960
+ if (process.platform === "darwin") {
1961
+ Bun.spawn(["afplay", "/System/Library/Sounds/Funk.aiff"], {
1962
+ stdout: "ignore",
1963
+ stderr: "ignore",
1964
+ });
1965
+ } else if (process.platform === "linux") {
1966
+ // Try paplay (PulseAudio), fall back to terminal bell
1967
+ try {
1968
+ const proc = Bun.spawn(
1969
+ ["paplay", "/usr/share/sounds/freedesktop/stereo/bell.oga"],
1970
+ { stdout: "ignore", stderr: "ignore" },
1971
+ );
1972
+ await proc.exited;
1973
+ } catch {
1974
+ process.stdout.write("\x07");
1975
+ }
1976
+ } else if (process.platform === "win32") {
1977
+ const proc = Bun.spawn(
1978
+ ["powershell", "-NoProfile", "-Command", "[Console]::Beep(800, 200)"],
1979
+ { stdout: "ignore", stderr: "ignore" },
1980
+ );
1981
+ await proc.exited;
1982
+ }
1983
+ } catch {
1984
+ // beep is best-effort never throw
1985
+ }
1986
+ }
1987
+
1988
+ // MARK: - Screen / Display
1989
+
1990
+ async getPrimaryDisplay(): Promise<DisplayInfo> {
1991
+ const display = Screen.getPrimaryDisplay();
1992
+ return {
1993
+ id: display.id,
1994
+ bounds: display.bounds,
1995
+ workArea: display.workArea,
1996
+ scaleFactor: display.scaleFactor,
1997
+ isPrimary: display.isPrimary,
1998
+ };
1999
+ }
2000
+
2001
+ async getAllDisplays(): Promise<{ displays: DisplayInfo[] }> {
2002
+ const displays = Screen.getAllDisplays();
2003
+ return {
2004
+ displays: displays.map((d) => ({
2005
+ id: d.id,
2006
+ bounds: d.bounds,
2007
+ workArea: d.workArea,
2008
+ scaleFactor: d.scaleFactor,
2009
+ isPrimary: d.isPrimary,
2010
+ })),
2011
+ };
2012
+ }
2013
+
2014
+ async getCursorPosition(): Promise<CursorPosition> {
2015
+ return Screen.getCursorScreenPoint();
2016
+ }
2017
+
2018
+ // MARK: - Message Box
2019
+
2020
+ async showMessageBox(options: MessageBoxOptions): Promise<MessageBoxResult> {
2021
+ const autoConfirm =
2022
+ process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_DIALOGS === "1" ||
2023
+ process.env.ELIZA_DESKTOP_TEST_AUTO_CONFIRM_RESET === "1";
2024
+ if (autoConfirm) {
2025
+ return { response: options.defaultId ?? 0 };
2026
+ }
2027
+ const result = await Utils.showMessageBox({
2028
+ type: options.type ?? "info",
2029
+ title: options.title,
2030
+ message: options.message,
2031
+ detail: options.detail,
2032
+ buttons: options.buttons ?? ["OK"],
2033
+ defaultId: options.defaultId ?? 0,
2034
+ cancelId: options.cancelId,
2035
+ });
2036
+ return { response: result.response };
2037
+ }
2038
+
2039
+ // MARK: - File Dialogs
2040
+
2041
+ /**
2042
+ * Show a native file/directory open picker.
2043
+ * Maps to Electrobun's Utils.openFileDialog.
2044
+ */
2045
+ async showOpenDialog(options: FileDialogOptions): Promise<FileDialogResult> {
2046
+ const filePaths = await Utils.openFileDialog({
2047
+ startingFolder: options.defaultPath,
2048
+ allowedFileTypes: options.allowedFileTypes,
2049
+ canChooseFiles: options.canChooseFiles ?? true,
2050
+ canChooseDirectory: options.canChooseDirectory ?? false,
2051
+ allowsMultipleSelection: options.allowsMultipleSelection ?? false,
2052
+ });
2053
+ const canceled = filePaths.length === 0 || filePaths[0] === "";
2054
+ return { canceled, filePaths: canceled ? [] : filePaths };
2055
+ }
2056
+
2057
+ /**
2058
+ * Show a native directory picker for save operations.
2059
+ * Electrobun has no separate save dialog — we pick a directory and the
2060
+ * caller appends the filename. Returns the chosen directory path.
2061
+ */
2062
+ async showSaveDialog(options: FileDialogOptions): Promise<FileDialogResult> {
2063
+ const filePaths = await Utils.openFileDialog({
2064
+ startingFolder: options.defaultPath,
2065
+ allowedFileTypes: options.allowedFileTypes,
2066
+ canChooseFiles: false,
2067
+ canChooseDirectory: true,
2068
+ allowsMultipleSelection: false,
2069
+ });
2070
+ const canceled = filePaths.length === 0 || filePaths[0] === "";
2071
+ return { canceled, filePaths: canceled ? [] : filePaths };
2072
+ }
2073
+
2074
+ /**
2075
+ * Pick a workspace folder for store-distributed builds. Maps to a directory-only
2076
+ * NSOpenPanel on macOS (via Electrobun's openFileDialog).
2077
+ *
2078
+ * The `bookmark` field is the OS-specific persistence handle: on macOS, a
2079
+ * base64 NSURLBookmarkCreationOptions.WithSecurityScope blob the caller
2080
+ * stores and re-resolves on next launch. Non-macOS platforms return null
2081
+ * because portals / AppContainer do not use NSURL bookmarks.
2082
+ */
2083
+ async pickWorkspaceFolder(options: {
2084
+ defaultPath?: string;
2085
+ promptTitle?: string;
2086
+ }): Promise<{ canceled: boolean; path: string; bookmark: string | null }> {
2087
+ const filePaths = await Utils.openFileDialog({
2088
+ startingFolder: options.defaultPath,
2089
+ canChooseFiles: false,
2090
+ canChooseDirectory: true,
2091
+ allowsMultipleSelection: false,
2092
+ });
2093
+ const canceled = filePaths.length === 0 || filePaths[0] === "";
2094
+ if (canceled) {
2095
+ return { canceled: true, path: "", bookmark: null };
2096
+ }
2097
+ const selectedPath = filePaths[0] ?? "";
2098
+ if (!selectedPath) {
2099
+ return { canceled: true, path: "", bookmark: null };
2100
+ }
2101
+ const bookmark =
2102
+ process.platform === "darwin"
2103
+ ? createSecurityScopedBookmark(selectedPath)
2104
+ : null;
2105
+ // Bridge to the agent runtime via the shared state-dir JSON file so
2106
+ // the separate Node process honors the user's pick when resolving
2107
+ // ELIZA_WORKSPACE_DIR at boot. Renderer-side localStorage is a
2108
+ // separate copy for its own UX (button states, re-prompt logic).
2109
+ try {
2110
+ writeWorkspaceFolderConfig({ path: selectedPath, bookmark });
2111
+ } catch (err) {
2112
+ logger.warn(
2113
+ `[desktop:pickWorkspaceFolder] writeWorkspaceFolderConfig failed: ${
2114
+ err instanceof Error ? err.message : String(err)
2115
+ }`,
2116
+ );
2117
+ }
2118
+ return { canceled: false, path: selectedPath, bookmark };
2119
+ }
2120
+
2121
+ resolveWorkspaceFolderBookmark(options: { bookmark: string }): {
2122
+ ok: boolean;
2123
+ path: string;
2124
+ stale?: boolean;
2125
+ error?: string;
2126
+ } {
2127
+ if (process.platform !== "darwin") {
2128
+ return { ok: true, path: "" };
2129
+ }
2130
+ const path = startAccessingSecurityScopedBookmark(options.bookmark);
2131
+ if (!path) {
2132
+ // Bookmark went stale (target moved/trashed). Wipe the shared
2133
+ // config so the agent runtime falls back to the container
2134
+ // default on next boot until the user re-picks.
2135
+ try {
2136
+ clearWorkspaceFolderConfig();
2137
+ } catch (err) {
2138
+ logger.warn(
2139
+ `[desktop:resolveWorkspaceFolderBookmark] clearWorkspaceFolderConfig failed: ${
2140
+ err instanceof Error ? err.message : String(err)
2141
+ }`,
2142
+ );
2143
+ }
2144
+ return {
2145
+ ok: false,
2146
+ path: "",
2147
+ error: "Unable to resolve security-scoped bookmark.",
2148
+ };
2149
+ }
2150
+ // Refresh the shared config with the freshly-resolved path (it
2151
+ // may differ from the originally-picked path if the user renamed
2152
+ // the folder since the bookmark was created).
2153
+ try {
2154
+ writeWorkspaceFolderConfig({ path, bookmark: options.bookmark });
2155
+ } catch (err) {
2156
+ logger.warn(
2157
+ `[desktop:resolveWorkspaceFolderBookmark] writeWorkspaceFolderConfig failed: ${
2158
+ err instanceof Error ? err.message : String(err)
2159
+ }`,
2160
+ );
2161
+ }
2162
+ return { ok: true, path };
2163
+ }
2164
+
2165
+ releaseWorkspaceFolderBookmarks(): { ok: true } {
2166
+ stopAccessingSecurityScopedBookmarks();
2167
+ return { ok: true };
2168
+ }
2169
+
2170
+ // MARK: - Helpers
2171
+
2172
+ /**
2173
+ * Resolve an icon path, trying absolute, then relative to known asset dirs.
2174
+ */
2175
+ private resolveIconPath(iconPath: string): string {
2176
+ if (path.isAbsolute(iconPath)) {
2177
+ return iconPath;
2178
+ }
2179
+
2180
+ // Try relative to the electrobun assets directory
2181
+ const assetsPath = path.join(import.meta.dir, "../../assets", iconPath);
2182
+ if (fs.existsSync(assetsPath)) {
2183
+ return assetsPath;
2184
+ }
2185
+
2186
+ // Try relative to cwd
2187
+ const cwdPath = path.join(process.cwd(), iconPath);
2188
+ if (fs.existsSync(cwdPath)) {
2189
+ return cwdPath;
2190
+ }
2191
+
2192
+ // Return as-is and let Electrobun handle it
2193
+ return iconPath;
2194
+ }
2195
+
2196
+ private getSession(partition: string) {
2197
+ const normalized = partition.trim() || "persist:default";
2198
+ if (normalized === "persist:default") {
2199
+ return Session.defaultSession;
2200
+ }
2201
+ return Session.fromPartition(normalized);
2202
+ }
2203
+
2204
+ private readSessionSnapshot(partition: string): DesktopSessionSnapshot {
2205
+ const session = this.getSession(partition);
2206
+ const cookies = session.cookies.get().map((cookie) => ({
2207
+ name: cookie.name,
2208
+ domain: cookie.domain,
2209
+ path: cookie.path,
2210
+ secure: cookie.secure,
2211
+ httpOnly: cookie.httpOnly,
2212
+ session: (cookie as typeof cookie & { session?: boolean }).session,
2213
+ expirationDate: cookie.expirationDate,
2214
+ }));
2215
+
2216
+ return {
2217
+ partition: session.partition,
2218
+ persistent: session.partition.startsWith("persist:"),
2219
+ cookieCount: cookies.length,
2220
+ cookies,
2221
+ };
2222
+ }
2223
+
2224
+ private normalizeReleaseNotesUrl(url: string): string {
2225
+ const trimmed = url.trim() || DEFAULT_RELEASE_NOTES_URL;
2226
+ const parsed = new URL(trimmed);
2227
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2228
+ throw new Error("Release notes URL must use http or https");
2229
+ }
2230
+ return parsed.toString();
2231
+ }
2232
+
2233
+ private buildReleaseNotesNavigationRules(url: string): string[] {
2234
+ const parsed = new URL(url);
2235
+ const origin = parsed.origin.replace(/\/+$/, "");
2236
+ return [parsed.toString(), `${origin}/*`];
2237
+ }
2238
+
2239
+ private async buildUpdaterSnapshot(
2240
+ error?: unknown,
2241
+ availability = this.getUpdaterAvailability(),
2242
+ ): Promise<DesktopUpdaterSnapshot> {
2243
+ let currentVersion = "unknown";
2244
+ let currentHash: string | undefined;
2245
+ let channel: string | undefined;
2246
+ let baseUrl: string | undefined;
2247
+ let snapshotError =
2248
+ error instanceof Error ? error.message : error ? String(error) : null;
2249
+
2250
+ try {
2251
+ const localInfo = await Updater.getLocallocalInfo();
2252
+ currentVersion = localInfo.version;
2253
+ currentHash = localInfo.hash;
2254
+ channel = localInfo.channel;
2255
+ baseUrl = localInfo.baseUrl;
2256
+ } catch (localError) {
2257
+ if (!snapshotError) {
2258
+ snapshotError =
2259
+ localError instanceof Error
2260
+ ? localError.message
2261
+ : String(localError ?? "Unknown updater error");
2262
+ }
2263
+ }
2264
+
2265
+ const updateInfo =
2266
+ (Updater.updateInfo() as Partial<{
2267
+ version: string;
2268
+ hash: string;
2269
+ updateAvailable: boolean;
2270
+ updateReady: boolean;
2271
+ error: string;
2272
+ }>) ?? {};
2273
+ const lastStatusEntry = Updater.getStatusHistory().at(-1) ?? null;
2274
+
2275
+ return {
2276
+ currentVersion,
2277
+ currentHash,
2278
+ channel,
2279
+ baseUrl,
2280
+ appBundlePath: availability.appBundlePath,
2281
+ canAutoUpdate: availability.canAutoUpdate,
2282
+ autoUpdateDisabledReason: availability.autoUpdateDisabledReason,
2283
+ updateAvailable: Boolean(updateInfo.updateAvailable),
2284
+ updateReady: Boolean(updateInfo.updateReady),
2285
+ latestVersion: updateInfo.version ?? null,
2286
+ latestHash: updateInfo.hash ?? null,
2287
+ error: updateInfo.error || snapshotError,
2288
+ lastStatus: lastStatusEntry
2289
+ ? {
2290
+ status: lastStatusEntry.status,
2291
+ message: lastStatusEntry.message,
2292
+ timestamp: lastStatusEntry.timestamp,
2293
+ }
2294
+ : null,
2295
+ };
2296
+ }
2297
+
2298
+ private getUpdaterAvailability(): {
2299
+ appBundlePath: string | null;
2300
+ canAutoUpdate: boolean;
2301
+ autoUpdateDisabledReason: string | null;
2302
+ } {
2303
+ const brand = getBrandConfig();
2304
+ return resolveDesktopUpdateAvailability({
2305
+ platform: process.platform,
2306
+ execPath: process.execPath,
2307
+ homeDir: Utils.paths.home,
2308
+ appName: brand.appName,
2309
+ buildVariant: brand.buildVariant,
2310
+ });
2311
+ }
2312
+
2313
+ private resolvePreferredBrowserRenderer(
2314
+ buildInfo: Awaited<ReturnType<typeof BuildConfig.get>>,
2315
+ ): "native" | "cef" {
2316
+ if (
2317
+ process.platform === "linux" &&
2318
+ buildInfo.availableRenderers.includes("cef")
2319
+ ) {
2320
+ return "cef";
2321
+ }
2322
+
2323
+ return buildInfo.defaultRenderer;
2324
+ }
2325
+
2326
+ /**
2327
+ * Clean up all resources.
2328
+ */
2329
+ dispose(): void {
2330
+ if (this._focusPoller) {
2331
+ clearInterval(this._focusPoller);
2332
+ this._focusPoller = null;
2333
+ }
2334
+ this.teardownWindowEvents(this.mainWindow);
2335
+ this.mainWindow = null;
2336
+ this.releaseNotesView?.remove();
2337
+ this.releaseNotesView = null;
2338
+ this.releaseNotesWindow = null;
2339
+ this.unregisterAllShortcuts();
2340
+ void this.destroyTray();
2341
+ this.trayMenuItems.clear();
2342
+ this.sendToWebview = null;
2343
+ }
2304
2344
  }
2305
2345
 
2306
2346
  // ============================================================================
@@ -2310,8 +2350,8 @@ X-GNOME-Autostart-enabled=true
2310
2350
  let desktopManager: DesktopManager | null = null;
2311
2351
 
2312
2352
  export function getDesktopManager(): DesktopManager {
2313
- if (!desktopManager) {
2314
- desktopManager = new DesktopManager();
2315
- }
2316
- return desktopManager;
2353
+ if (!desktopManager) {
2354
+ desktopManager = new DesktopManager();
2355
+ }
2356
+ return desktopManager;
2317
2357
  }