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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1534) hide show
  1. package/agent-bridge.d.ts +27 -0
  2. package/agent-bridge.d.ts.map +1 -0
  3. package/agent-bridge.js +26 -0
  4. package/api/__tests__/sandbox-test-helpers.js +1 -1
  5. package/api/auth/audit.js +1 -1
  6. package/api/auth/auth-context.js +1 -1
  7. package/api/auth/bootstrap-token.js +2 -2
  8. package/api/auth/index.d.ts +9 -10
  9. package/api/auth/index.d.ts.map +1 -1
  10. package/api/auth/index.js +9 -10
  11. package/api/auth/passwords.js +2 -2
  12. package/api/auth/sensitive-rate-limit.d.ts +1 -4
  13. package/api/auth/sensitive-rate-limit.d.ts.map +1 -1
  14. package/api/auth/sensitive-rate-limit.js +6 -6
  15. package/api/auth/sessions.js +2 -2
  16. package/api/auth-bootstrap-routes.d.ts +6 -13
  17. package/api/auth-bootstrap-routes.d.ts.map +1 -1
  18. package/api/auth-bootstrap-routes.js +14 -27
  19. package/api/auth-pairing-routes.d.ts +17 -0
  20. package/api/auth-pairing-routes.d.ts.map +1 -0
  21. package/api/auth-pairing-routes.js +300 -0
  22. package/api/auth-session-routes.d.ts.map +1 -1
  23. package/api/auth-session-routes.js +36 -15
  24. package/api/auth.d.ts +12 -19
  25. package/api/auth.d.ts.map +1 -1
  26. package/api/auth.js +32 -27
  27. package/api/automations-compat-routes.d.ts.map +1 -1
  28. package/api/automations-compat-routes.js +5 -5
  29. package/api/background-tasks-routes.d.ts +4 -0
  30. package/api/background-tasks-routes.d.ts.map +1 -0
  31. package/api/background-tasks-routes.js +63 -0
  32. package/api/catalog-routes.js +3 -3
  33. package/api/cloud-pair-route.d.ts +26 -0
  34. package/api/cloud-pair-route.d.ts.map +1 -0
  35. package/api/cloud-pair-route.js +222 -0
  36. package/api/cloud-voice-routes.d.ts +52 -0
  37. package/api/cloud-voice-routes.d.ts.map +1 -0
  38. package/api/cloud-voice-routes.js +50 -0
  39. package/api/compat-route-shared.d.ts +2 -2
  40. package/api/compat-route-shared.d.ts.map +1 -1
  41. package/api/compat-route-shared.js +11 -7
  42. package/api/credential-resolver.d.ts +2 -2
  43. package/api/credential-resolver.d.ts.map +1 -1
  44. package/api/credential-resolver.js +8 -2
  45. package/api/database-rows-compat-routes.d.ts.map +1 -1
  46. package/api/database-rows-compat-routes.js +69 -31
  47. package/api/dev-boot-history.d.ts +26 -0
  48. package/api/dev-boot-history.d.ts.map +1 -0
  49. package/api/dev-boot-history.js +69 -0
  50. package/api/dev-compat-routes.d.ts +5 -0
  51. package/api/dev-compat-routes.d.ts.map +1 -1
  52. package/api/dev-compat-routes.js +127 -4
  53. package/api/dev-console-log.d.ts +2 -2
  54. package/api/dev-console-log.d.ts.map +1 -1
  55. package/api/dev-console-log.js +8 -5
  56. package/api/dev-route-catalog.d.ts +58 -0
  57. package/api/dev-route-catalog.d.ts.map +1 -0
  58. package/api/dev-route-catalog.js +447 -0
  59. package/api/dev-stack.d.ts.map +1 -1
  60. package/api/dev-stack.js +6 -9
  61. package/api/first-run-routes.d.ts +4 -0
  62. package/api/first-run-routes.d.ts.map +1 -0
  63. package/api/first-run-routes.js +208 -0
  64. package/api/first-run-tts-route.d.ts +19 -0
  65. package/api/first-run-tts-route.d.ts.map +1 -0
  66. package/api/first-run-tts-route.js +59 -0
  67. package/api/internal-routes.d.ts +23 -0
  68. package/api/internal-routes.d.ts.map +1 -0
  69. package/api/internal-routes.js +203 -0
  70. package/api/ios-local-agent-transport.d.ts +36 -0
  71. package/api/ios-local-agent-transport.d.ts.map +1 -0
  72. package/api/ios-local-agent-transport.js +566 -0
  73. package/api/onboarding-voice-lines.d.ts +23 -0
  74. package/api/onboarding-voice-lines.d.ts.map +1 -0
  75. package/api/onboarding-voice-lines.js +8 -0
  76. package/api/perf-instrument.d.ts +43 -0
  77. package/api/perf-instrument.d.ts.map +1 -0
  78. package/api/perf-instrument.js +113 -0
  79. package/api/response.d.ts.map +1 -1
  80. package/api/response.js +14 -14
  81. package/api/runtime-mode-routes.d.ts.map +1 -1
  82. package/api/runtime-mode-routes.js +2 -2
  83. package/api/secrets-inventory-routes.js +2 -2
  84. package/api/secrets-manager-routes.d.ts +1 -1
  85. package/api/secrets-manager-routes.d.ts.map +1 -1
  86. package/api/secrets-manager-routes.js +9 -10
  87. package/api/sensitive-request-routes.d.ts.map +1 -1
  88. package/api/sensitive-request-routes.js +5 -5
  89. package/api/sensitive-request-store.d.ts +12 -3
  90. package/api/sensitive-request-store.d.ts.map +1 -1
  91. package/api/server-cors.d.ts.map +1 -1
  92. package/api/server-cors.js +13 -2
  93. package/api/server-first-run-helpers.d.ts +26 -0
  94. package/api/server-first-run-helpers.d.ts.map +1 -0
  95. package/api/server-first-run-helpers.js +271 -0
  96. package/api/server-security.js +1 -1
  97. package/api/server-startup.d.ts.map +1 -1
  98. package/api/server-startup.js +3 -4
  99. package/api/server-wallet-trade.js +1 -1
  100. package/api/server.d.ts +4 -4
  101. package/api/server.d.ts.map +1 -1
  102. package/api/server.js +222 -88
  103. package/api/setup-contract.d.ts +63 -0
  104. package/api/setup-contract.d.ts.map +1 -0
  105. package/api/setup-contract.js +39 -0
  106. package/api/training-benchmarks.d.ts +97 -0
  107. package/api/training-benchmarks.d.ts.map +1 -0
  108. package/api/training-benchmarks.js +307 -0
  109. package/api/workbench-compat-routes.js +2 -2
  110. package/benchmark/cerebras-autowire.d.ts +28 -0
  111. package/benchmark/cerebras-autowire.d.ts.map +1 -0
  112. package/benchmark/cerebras-autowire.js +62 -0
  113. package/benchmark/lifeops-bench-handler.d.ts +36 -0
  114. package/benchmark/lifeops-bench-handler.d.ts.map +1 -1
  115. package/benchmark/lifeops-bench-handler.js +63 -1
  116. package/benchmark/lifeops-fake-backend.d.ts +39 -0
  117. package/benchmark/lifeops-fake-backend.d.ts.map +1 -1
  118. package/benchmark/lifeops-fake-backend.js +993 -21
  119. package/benchmark/mock-plugin.d.ts.map +1 -1
  120. package/benchmark/mock-plugin.js +0 -24
  121. package/benchmark/plugin.d.ts +2 -1
  122. package/benchmark/plugin.d.ts.map +1 -1
  123. package/benchmark/plugin.js +989 -68
  124. package/benchmark/replay-capture.d.ts +2 -2
  125. package/benchmark/replay-capture.d.ts.map +1 -1
  126. package/benchmark/replay-capture.js +3 -3
  127. package/benchmark/server-utils.d.ts +162 -9
  128. package/benchmark/server-utils.d.ts.map +1 -1
  129. package/benchmark/server-utils.js +625 -62
  130. package/benchmark/server.d.ts.map +1 -1
  131. package/benchmark/server.js +1962 -118
  132. package/boot-profile.d.ts +3 -0
  133. package/boot-profile.d.ts.map +1 -0
  134. package/boot-profile.js +30 -0
  135. package/browser.d.ts +23 -1
  136. package/browser.d.ts.map +1 -1
  137. package/browser.js +20 -1
  138. package/cli/argv.js +1 -1
  139. package/cli/banner.js +1 -1
  140. package/cli/command-format.js +2 -2
  141. package/cli/doctor/checks.d.ts.map +1 -1
  142. package/cli/doctor/checks.js +6 -6
  143. package/cli/plugins-cli.d.ts.map +1 -1
  144. package/cli/plugins-cli.js +77 -32
  145. package/cli/profile.d.ts.map +1 -1
  146. package/cli/profile.js +5 -4
  147. package/cli/program/build-program.js +4 -4
  148. package/cli/program/command-registry.d.ts.map +1 -1
  149. package/cli/program/command-registry.js +13 -11
  150. package/cli/program/help.js +5 -5
  151. package/cli/program/preaction.js +5 -5
  152. package/cli/program/register.auth.d.ts.map +1 -1
  153. package/cli/program/register.auth.js +6 -12
  154. package/cli/program/register.capability-router.d.ts +29 -0
  155. package/cli/program/register.capability-router.d.ts.map +1 -0
  156. package/cli/program/register.capability-router.js +568 -0
  157. package/cli/program/register.config.js +1 -1
  158. package/cli/program/register.configure.d.ts.map +1 -1
  159. package/cli/program/register.configure.js +1 -1
  160. package/cli/program/register.dashboard.d.ts.map +1 -1
  161. package/cli/program/register.dashboard.js +6 -7
  162. package/cli/program/register.db.d.ts.map +1 -1
  163. package/cli/program/register.db.js +3 -4
  164. package/cli/program/register.doctor.js +7 -7
  165. package/cli/program/register.setup.d.ts.map +1 -1
  166. package/cli/program/register.setup.js +14 -10
  167. package/cli/program/register.start.d.ts.map +1 -1
  168. package/cli/program/register.start.js +5 -3
  169. package/cli/program/register.subclis.js +3 -3
  170. package/cli/program/register.update.d.ts +6 -0
  171. package/cli/program/register.update.d.ts.map +1 -1
  172. package/cli/program/register.update.js +58 -6
  173. package/cli/program.js +1 -1
  174. package/cli/run-main.js +4 -4
  175. package/config/app-config.d.ts +2 -0
  176. package/config/app-config.d.ts.map +1 -0
  177. package/config/app-config.js +1 -0
  178. package/connectors/capacitor-jsc.d.ts.map +1 -1
  179. package/connectors/capacitor-jsc.js +16 -10
  180. package/connectors/capacitor-quickjs.d.ts.map +1 -1
  181. package/connectors/capacitor-quickjs.js +18 -13
  182. package/connectors/capacitor-sqlite.d.ts.map +1 -1
  183. package/connectors/capacitor-sqlite.js +27 -12
  184. package/dispatch/approval-queue.d.ts +37 -0
  185. package/dispatch/approval-queue.d.ts.map +1 -0
  186. package/dispatch/approval-queue.js +25 -0
  187. package/dispatch/channel-registry.d.ts +30 -0
  188. package/dispatch/channel-registry.d.ts.map +1 -0
  189. package/dispatch/channel-registry.js +22 -0
  190. package/dispatch/connector-registry.d.ts +39 -0
  191. package/dispatch/connector-registry.d.ts.map +1 -0
  192. package/dispatch/connector-registry.js +24 -0
  193. package/dispatch/index.d.ts +14 -0
  194. package/dispatch/index.d.ts.map +1 -0
  195. package/dispatch/index.js +13 -0
  196. package/dispatch/send-policy.d.ts +36 -0
  197. package/dispatch/send-policy.d.ts.map +1 -0
  198. package/dispatch/send-policy.js +16 -0
  199. package/entry.js +28 -11
  200. package/first-run/first-run-config.d.ts +55 -0
  201. package/first-run/first-run-config.d.ts.map +1 -0
  202. package/first-run/first-run-config.js +178 -0
  203. package/first-run/runtime-target.d.ts +4 -0
  204. package/first-run/runtime-target.d.ts.map +1 -0
  205. package/first-run/runtime-target.js +13 -0
  206. package/index.d.ts +16 -3
  207. package/index.d.ts.map +1 -1
  208. package/index.js +57 -33
  209. package/package.json +159 -50
  210. package/packaging/debian/apt-repo-config/README.md +18 -0
  211. package/packaging/debian/apt-repo-config/conf/distributions +11 -0
  212. package/packaging/flatpak/README.md +26 -16
  213. package/packaging/flatpak/ai.elizaos.App.metainfo.xml +17 -12
  214. package/packaging/flatpak/ai.elizaos.App.store.yml +5 -5
  215. package/packaging/flatpak/ai.elizaos.App.yml +10 -24
  216. package/packaging/flatpak/elizaos-app-wrapper.store.sh +2 -2
  217. package/packaging/flatpak/generate-sources.sh +74 -0
  218. package/packaging/flatpak/node-sources.json +7930 -0
  219. package/packaging/inno/build-inno.ps1 +34 -9
  220. package/packaging/msix/AppxManifest.store.xml +1 -1
  221. package/packaging/msix/README.md +39 -19
  222. package/packaging/msix/build-msix.ps1 +44 -14
  223. package/packaging/snap/snapcraft.yaml +22 -21
  224. package/packaging/test-packaging.sh +2 -2
  225. package/permissions/types.d.ts +1 -1
  226. package/permissions/types.js +1 -1
  227. package/platform/elizaos-agent-browser-stub.d.ts +144 -0
  228. package/platform/elizaos-agent-browser-stub.d.ts.map +1 -0
  229. package/platform/elizaos-agent-browser-stub.js +158 -0
  230. package/platform/elizaos-plugin-elizacloud-browser-stub.d.ts +34 -0
  231. package/platform/elizaos-plugin-elizacloud-browser-stub.d.ts.map +1 -0
  232. package/platform/elizaos-plugin-elizacloud-browser-stub.js +51 -0
  233. package/platform/empty-node-module.d.ts +148 -0
  234. package/platform/empty-node-module.d.ts.map +1 -1
  235. package/platform/empty-node-module.js +140 -3
  236. package/platform/ios-runtime-backends.d.ts +83 -0
  237. package/platform/ios-runtime-backends.d.ts.map +1 -0
  238. package/platform/ios-runtime-backends.js +133 -0
  239. package/platform/ios-runtime-bridge.d.ts +15 -0
  240. package/platform/ios-runtime-bridge.d.ts.map +1 -0
  241. package/platform/ios-runtime-bridge.js +527 -0
  242. package/platform/native-library-policy.d.ts +23 -0
  243. package/platform/native-library-policy.d.ts.map +1 -0
  244. package/platform/native-library-policy.js +112 -0
  245. package/platform/native-plugin-entrypoints.d.ts +19 -0
  246. package/platform/native-plugin-entrypoints.d.ts.map +1 -0
  247. package/platform/native-plugin-entrypoints.js +29 -0
  248. package/platforms/android/README.md +68 -10
  249. package/platforms/android/app/build.gradle +268 -3
  250. package/platforms/android/app/capacitor.build.gradle +18 -1
  251. package/platforms/android/app/proguard-rules.pro +17 -2
  252. package/platforms/android/app/src/androidTest/java/ai/elizaos/app/ElizaOsInstrumentedTest.java +1 -1
  253. package/platforms/android/app/src/main/AndroidManifest.xml +334 -17
  254. package/platforms/android/app/src/main/assets/runners/eliza-tasks.js +177 -0
  255. package/platforms/android/app/src/main/elizavoice-jni/CMakeLists.txt +75 -0
  256. package/platforms/android/app/src/main/elizavoice-jni/elizavoice-jni.cpp +1045 -0
  257. package/platforms/android/app/src/main/java/ai/elizaos/app/AgentPlugin.java +111 -171
  258. package/platforms/android/app/src/main/java/ai/elizaos/app/AndroidVirtualizationBridge.java +284 -0
  259. package/platforms/android/app/src/main/java/ai/elizaos/app/BatteryOptimizationPlugin.java +95 -0
  260. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAccessibilityService.java +55 -0
  261. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAgentService.java +1198 -141
  262. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAndroidSystemBridge.java +83 -0
  263. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAssistActivity.java +50 -1
  264. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBootReceiver.java +90 -8
  265. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBrowserActivity.java +2 -2
  266. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaCalendarActivity.java +1 -1
  267. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaCameraActivity.java +1 -1
  268. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaClockActivity.java +2 -2
  269. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaContactsActivity.java +1 -1
  270. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaDialActivity.java +1 -1
  271. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaInCallService.java +1 -1
  272. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaMmsReceiver.java +1 -1
  273. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaNativeBridge.java +22 -0
  274. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaNotificationListenerService.java +45 -0
  275. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaQuickActionsWidgetProvider.java +68 -0
  276. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaShareActivity.java +132 -0
  277. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsComposeActivity.java +1 -1
  278. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsGatewayService.java +268 -0
  279. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaSmsReceiver.java +12 -1
  280. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaTasksWorker.java +194 -0
  281. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceCaptureService.java +198 -0
  282. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceNative.java +160 -0
  283. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoicePlugin.java +450 -0
  284. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceTileService.java +39 -0
  285. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaWorkScheduler.java +60 -0
  286. package/platforms/android/app/src/main/java/ai/elizaos/app/GatewayConnectionService.java +53 -19
  287. package/platforms/android/app/src/main/java/ai/elizaos/app/MainActivity.java +160 -33
  288. package/platforms/android/app/src/main/java/ai/elizaos/app/ResourceProbePlugin.java +169 -0
  289. package/platforms/android/app/src/main/java/ai/elizaos/app/VoiceCapturePlugin.java +119 -0
  290. package/platforms/android/app/src/main/res/drawable/eliza_widget_background.xml +10 -0
  291. package/platforms/android/app/src/main/res/drawable/eliza_widget_button_background.xml +13 -0
  292. package/platforms/android/app/src/main/res/drawable/splash.png +0 -0
  293. package/platforms/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  294. package/platforms/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  295. package/platforms/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  296. package/platforms/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  297. package/platforms/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  298. package/platforms/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  299. package/platforms/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  300. package/platforms/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  301. package/platforms/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  302. package/platforms/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  303. package/platforms/android/app/src/main/res/layout/eliza_quick_actions_widget.xml +86 -0
  304. package/platforms/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +2 -1
  305. package/platforms/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +2 -1
  306. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  307. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  308. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
  309. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  310. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  311. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  312. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
  313. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  314. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  315. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  316. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
  317. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  318. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  319. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  320. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
  321. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  322. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  323. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  324. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
  325. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  326. package/platforms/android/app/src/main/res/values/android_app_actions.xml +48 -0
  327. package/platforms/android/app/src/main/res/values/colors.xml +8 -0
  328. package/platforms/android/app/src/main/res/values/ic_launcher_background.xml +2 -2
  329. package/platforms/android/app/src/main/res/values/strings.xml +2 -2
  330. package/platforms/android/app/src/main/res/values/styles.xml +25 -1
  331. package/platforms/android/app/src/main/res/xml/eliza_accessibility_service.xml +9 -0
  332. package/platforms/android/app/src/main/res/xml/eliza_quick_actions_widget.xml +13 -0
  333. package/platforms/android/app/src/main/res/xml/shortcuts.xml +121 -0
  334. package/platforms/android/build.gradle +2 -2
  335. package/platforms/android/capacitor-cordova-android-plugins/build.gradle +9 -3
  336. package/platforms/android/capacitor-cordova-android-plugins/cordova.variables.gradle +6 -2
  337. package/platforms/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml +7 -2
  338. package/platforms/android/capacitor-cordova-android-plugins/src/main/java/.gitkeep +0 -1
  339. package/platforms/android/capacitor.settings.gradle +66 -16
  340. package/platforms/android/gradle.properties +1 -0
  341. package/platforms/android/update-manifest/generate-manifest.mjs +97 -0
  342. package/platforms/android/update-manifest/schema.json +26 -0
  343. package/platforms/apple-store-entitlements.reviewed.json +155 -0
  344. package/platforms/electrobun/.generated/brand-config.json +3 -2
  345. package/platforms/electrobun/LICENSE +21 -0
  346. package/platforms/electrobun/README.md +15 -1
  347. package/platforms/electrobun/assets/appIcon.icns +0 -0
  348. package/platforms/electrobun/assets/appIcon.ico +0 -0
  349. package/platforms/electrobun/assets/appIcon.iconset/icon_128x128.png +0 -0
  350. package/platforms/electrobun/assets/appIcon.iconset/icon_128x128@2x.png +0 -0
  351. package/platforms/electrobun/assets/appIcon.iconset/icon_16x16.png +0 -0
  352. package/platforms/electrobun/assets/appIcon.iconset/icon_16x16@2x.png +0 -0
  353. package/platforms/electrobun/assets/appIcon.iconset/icon_256x256.png +0 -0
  354. package/platforms/electrobun/assets/appIcon.iconset/icon_256x256@2x.png +0 -0
  355. package/platforms/electrobun/assets/appIcon.iconset/icon_32x32.png +0 -0
  356. package/platforms/electrobun/assets/appIcon.iconset/icon_32x32@2x.png +0 -0
  357. package/platforms/electrobun/assets/appIcon.iconset/icon_512x512.png +0 -0
  358. package/platforms/electrobun/assets/brand-config.json +6 -6
  359. package/platforms/electrobun/biome.json +9 -9
  360. package/platforms/electrobun/docs/capability-collapse-matrix.json +318 -0
  361. package/platforms/electrobun/docs/capability-collapse-matrix.md +129 -0
  362. package/platforms/electrobun/docs/capability-routing.md +86 -0
  363. package/platforms/electrobun/docs/convergence-audit.json +3505 -0
  364. package/platforms/electrobun/docs/convergence-audit.md +694 -0
  365. package/platforms/electrobun/docs/database-boot-policy.md +90 -0
  366. package/platforms/electrobun/docs/riscv64-port.md +175 -0
  367. package/platforms/electrobun/docs/startup-first-run-cleanup.md +18 -0
  368. package/platforms/electrobun/docs/trace-first-annotations.md +52 -0
  369. package/platforms/electrobun/docs/ui-boundary-audit.json +580 -0
  370. package/platforms/electrobun/docs/ui-boundary-audit.md +257 -0
  371. package/platforms/electrobun/electrobun.config.ts +592 -364
  372. package/platforms/electrobun/entitlements/JUSTIFICATIONS.md +141 -0
  373. package/platforms/electrobun/entitlements/README.md +34 -6
  374. package/platforms/electrobun/entitlements/mas-bun.entitlements +15 -0
  375. package/platforms/electrobun/entitlements/mas.entitlements +6 -4
  376. package/platforms/electrobun/native/macos/window-effects.mm +1522 -0
  377. package/platforms/electrobun/package.json +18 -12
  378. package/platforms/electrobun/remotes/fs/README.md +70 -0
  379. package/platforms/electrobun/remotes/fs/electrobun.config.ts +38 -0
  380. package/platforms/electrobun/remotes/fs/package.json +12 -0
  381. package/platforms/electrobun/remotes/fs/plugin.json +25 -0
  382. package/platforms/electrobun/remotes/fs/src/bun/errors.ts +57 -0
  383. package/platforms/electrobun/remotes/fs/src/bun/file-limits.ts +50 -0
  384. package/platforms/electrobun/remotes/fs/src/bun/fs-service.ts +389 -0
  385. package/platforms/electrobun/remotes/fs/src/bun/path-guard.ts +270 -0
  386. package/platforms/electrobun/remotes/fs/src/bun/protocol.ts +149 -0
  387. package/platforms/electrobun/remotes/fs/src/bun/worker.ts +174 -0
  388. package/platforms/electrobun/remotes/fs/src/dev/phase5-smoke.ts +171 -0
  389. package/platforms/electrobun/remotes/fs/src/web/index.html +8 -0
  390. package/platforms/electrobun/remotes/git/README.md +75 -0
  391. package/platforms/electrobun/remotes/git/electrobun.config.ts +44 -0
  392. package/platforms/electrobun/remotes/git/package.json +12 -0
  393. package/platforms/electrobun/remotes/git/plugin.json +31 -0
  394. package/platforms/electrobun/remotes/git/src/bun/errors.ts +69 -0
  395. package/platforms/electrobun/remotes/git/src/bun/git-command.ts +156 -0
  396. package/platforms/electrobun/remotes/git/src/bun/git-service.ts +446 -0
  397. package/platforms/electrobun/remotes/git/src/bun/operation-history.ts +124 -0
  398. package/platforms/electrobun/remotes/git/src/bun/protocol.ts +252 -0
  399. package/platforms/electrobun/remotes/git/src/bun/worker.ts +316 -0
  400. package/platforms/electrobun/remotes/git/src/dev/phase7-smoke.ts +141 -0
  401. package/platforms/electrobun/remotes/git/src/web/index.html +8 -0
  402. package/platforms/electrobun/remotes/local-model/README.md +138 -0
  403. package/platforms/electrobun/remotes/local-model/electrobun.config.ts +46 -0
  404. package/platforms/electrobun/remotes/local-model/package.json +12 -0
  405. package/platforms/electrobun/remotes/local-model/plugin.json +33 -0
  406. package/platforms/electrobun/remotes/local-model/src/bun/download-state.ts +115 -0
  407. package/platforms/electrobun/remotes/local-model/src/bun/eliza1-catalog.ts +425 -0
  408. package/platforms/electrobun/remotes/local-model/src/bun/errors.ts +74 -0
  409. package/platforms/electrobun/remotes/local-model/src/bun/hf-eliza1-client.ts +169 -0
  410. package/platforms/electrobun/remotes/local-model/src/bun/local-inference-api-client.ts +245 -0
  411. package/platforms/electrobun/remotes/local-model/src/bun/model-service.ts +490 -0
  412. package/platforms/electrobun/remotes/local-model/src/bun/protocol.ts +301 -0
  413. package/platforms/electrobun/remotes/local-model/src/bun/worker.ts +248 -0
  414. package/platforms/electrobun/remotes/local-model/src/dev/phase8-smoke.ts +117 -0
  415. package/platforms/electrobun/remotes/local-model/src/web/index.html +13 -0
  416. package/platforms/electrobun/remotes/pty/README.md +65 -0
  417. package/platforms/electrobun/remotes/pty/electrobun.config.ts +47 -0
  418. package/platforms/electrobun/remotes/pty/package.json +12 -0
  419. package/platforms/electrobun/remotes/pty/plugin.json +34 -0
  420. package/platforms/electrobun/remotes/pty/src/bun/errors.ts +57 -0
  421. package/platforms/electrobun/remotes/pty/src/bun/output-buffer.ts +127 -0
  422. package/platforms/electrobun/remotes/pty/src/bun/protocol.ts +192 -0
  423. package/platforms/electrobun/remotes/pty/src/bun/pty-service.ts +562 -0
  424. package/platforms/electrobun/remotes/pty/src/bun/worker.ts +218 -0
  425. package/platforms/electrobun/remotes/pty/src/dev/phase6-smoke.ts +127 -0
  426. package/platforms/electrobun/remotes/pty/src/web/index.html +8 -0
  427. package/platforms/electrobun/remotes/runtime/README.md +370 -0
  428. package/platforms/electrobun/remotes/runtime/electrobun.config.ts +48 -0
  429. package/platforms/electrobun/remotes/runtime/package.json +14 -0
  430. package/platforms/electrobun/remotes/runtime/plugin.json +30 -0
  431. package/platforms/electrobun/remotes/runtime/src/bun/api-client.ts +620 -0
  432. package/platforms/electrobun/remotes/runtime/src/bun/errors.ts +45 -0
  433. package/platforms/electrobun/remotes/runtime/src/bun/log-buffer.ts +33 -0
  434. package/platforms/electrobun/remotes/runtime/src/bun/protocol.ts +366 -0
  435. package/platforms/electrobun/remotes/runtime/src/bun/route-discovery.ts +419 -0
  436. package/platforms/electrobun/remotes/runtime/src/bun/runtime-manager.ts +423 -0
  437. package/platforms/electrobun/remotes/runtime/src/bun/sse-parser.ts +99 -0
  438. package/platforms/electrobun/remotes/runtime/src/bun/stream-manager.ts +887 -0
  439. package/platforms/electrobun/remotes/runtime/src/bun/worker.ts +1231 -0
  440. package/platforms/electrobun/remotes/runtime/src/dev/phase1-smoke.ts +34 -0
  441. package/platforms/electrobun/remotes/runtime/src/dev/phase2-smoke.ts +86 -0
  442. package/platforms/electrobun/remotes/runtime/src/dev/phase3-smoke.ts +141 -0
  443. package/platforms/electrobun/remotes/runtime/src/web/index.css +187 -0
  444. package/platforms/electrobun/remotes/runtime/src/web/index.html +76 -0
  445. package/platforms/electrobun/remotes/runtime/src/web/index.ts +192 -0
  446. package/platforms/electrobun/remotes/surface/README.md +201 -0
  447. package/platforms/electrobun/remotes/surface/electrobun.config.ts +38 -0
  448. package/platforms/electrobun/remotes/surface/package.json +12 -0
  449. package/platforms/electrobun/remotes/surface/plugin.json +28 -0
  450. package/platforms/electrobun/remotes/surface/src/bun/worker.ts +132 -0
  451. package/platforms/electrobun/remotes/surface/src/dev/phase4-smoke.ts +566 -0
  452. package/platforms/electrobun/remotes/surface/src/protocol/event-types.ts +84 -0
  453. package/platforms/electrobun/remotes/surface/src/protocol/runtime-client.ts +673 -0
  454. package/platforms/electrobun/remotes/surface/src/web/app.ts +595 -0
  455. package/platforms/electrobun/remotes/surface/src/web/index.css +460 -0
  456. package/platforms/electrobun/remotes/surface/src/web/index.html +466 -0
  457. package/platforms/electrobun/remotes/surface/src/web/index.ts +5 -0
  458. package/platforms/electrobun/remotes/surface/src/web/render.ts +455 -0
  459. package/platforms/electrobun/remotes/surface/src/web/state.ts +427 -0
  460. package/platforms/electrobun/scripts/build-macos-effects.sh +4 -0
  461. package/platforms/electrobun/scripts/ensure-build-folder.ts +28 -0
  462. package/platforms/electrobun/scripts/ensure-whisper-gguf.sh +55 -0
  463. package/platforms/electrobun/scripts/ensure-whisper-model.sh +22 -80
  464. package/platforms/electrobun/scripts/generate-convergence-audit.ts +1203 -0
  465. package/platforms/electrobun/scripts/local-adhoc-sign-macos.ts +159 -159
  466. package/platforms/electrobun/scripts/postwrap-diagnostics.ts +424 -339
  467. package/platforms/electrobun/scripts/postwrap-sign-runtime-macos.ts +302 -271
  468. package/platforms/electrobun/scripts/smoke-test-windows.ps1 +17 -16
  469. package/platforms/electrobun/scripts/smoke-test.sh +5 -7
  470. package/platforms/electrobun/scripts/sync-web-assets.mjs +13 -13
  471. package/platforms/electrobun/scripts/verify-rpc-handlers.ts +109 -110
  472. package/platforms/electrobun/scripts/verify-windows-installer-proof.ps1 +3 -8
  473. package/platforms/electrobun/src/__stubs__/bun-ffi.ts +31 -31
  474. package/platforms/electrobun/src/__stubs__/electrobun-bun.ts +1 -1
  475. package/platforms/electrobun/src/agent-ready-state.ts +8 -8
  476. package/platforms/electrobun/src/agent-reset-from-main.test.ts +162 -0
  477. package/platforms/electrobun/src/agent-reset-from-main.ts +62 -62
  478. package/platforms/electrobun/src/agent-status-rpc.test.ts +95 -0
  479. package/platforms/electrobun/src/agent-status-rpc.ts +156 -0
  480. package/platforms/electrobun/src/api-base.test.ts +247 -0
  481. package/platforms/electrobun/src/api-base.ts +202 -93
  482. package/platforms/electrobun/src/application-menu-action-registry.ts +9 -9
  483. package/platforms/electrobun/src/application-menu.ts +348 -348
  484. package/platforms/electrobun/src/background-notice.ts +36 -36
  485. package/platforms/electrobun/src/boot-progress.test.ts +188 -0
  486. package/platforms/electrobun/src/boot-progress.ts +111 -0
  487. package/platforms/electrobun/src/brand-config.test.ts +39 -0
  488. package/platforms/electrobun/src/brand-config.ts +141 -129
  489. package/platforms/electrobun/src/bridge/browser-tabs-renderer-registry.ts +28 -28
  490. package/platforms/electrobun/src/bridge/electrobun-boot-config.ts +42 -0
  491. package/platforms/electrobun/src/bridge/electrobun-crypto-ready.ts +120 -0
  492. package/platforms/electrobun/src/bridge/electrobun-direct-rpc.ts +342 -357
  493. package/platforms/electrobun/src/bridge/electrobun-stub.ts +13 -13
  494. package/platforms/electrobun/src/browser-workspace-bridge-server.ts +285 -243
  495. package/platforms/electrobun/src/cloud-auth-window.ts +136 -136
  496. package/platforms/electrobun/src/cloud-disconnect-from-main.ts +90 -90
  497. package/platforms/electrobun/src/config-and-auth-rpc.test.ts +256 -0
  498. package/platforms/electrobun/src/config-and-auth-rpc.ts +302 -0
  499. package/platforms/electrobun/src/conversations-and-character-rpc.test.ts +185 -0
  500. package/platforms/electrobun/src/conversations-and-character-rpc.ts +131 -0
  501. package/platforms/electrobun/src/dashboard-rpc.test.ts +200 -0
  502. package/platforms/electrobun/src/dashboard-rpc.ts +344 -0
  503. package/platforms/electrobun/src/database/database-lock.ts +141 -0
  504. package/platforms/electrobun/src/database/database-mode.ts +149 -0
  505. package/platforms/electrobun/src/database/database-recovery.ts +72 -0
  506. package/platforms/electrobun/src/database/database-snapshot.ts +190 -0
  507. package/platforms/electrobun/src/database/database.test.ts +196 -0
  508. package/platforms/electrobun/src/database/index.ts +5 -0
  509. package/platforms/electrobun/src/database/pglite-paths.ts +100 -0
  510. package/platforms/electrobun/src/desktop-deep-link-events.test.ts +30 -0
  511. package/platforms/electrobun/src/desktop-deep-link-events.ts +17 -0
  512. package/platforms/electrobun/src/desktop-http-request.test.ts +73 -73
  513. package/platforms/electrobun/src/desktop-http-request.ts +85 -85
  514. package/platforms/electrobun/src/desktop-pill-config.test.ts +27 -0
  515. package/platforms/electrobun/src/desktop-pill-config.ts +40 -0
  516. package/platforms/electrobun/src/desktop-test-bridge-server.ts +204 -204
  517. package/platforms/electrobun/src/desktop-tray-config.test.ts +87 -0
  518. package/platforms/electrobun/src/desktop-tray-config.ts +84 -0
  519. package/platforms/electrobun/src/devtools-layout.ts +41 -41
  520. package/platforms/electrobun/src/diagnostic-format.test.ts +71 -0
  521. package/platforms/electrobun/src/diagnostic-format.ts +75 -36
  522. package/platforms/electrobun/src/dynamic-view-rpc-schema.test.ts +37 -0
  523. package/platforms/electrobun/src/dynamic-views/README.md +44 -0
  524. package/platforms/electrobun/src/dynamic-views/demo/agent-run-trace.html +135 -0
  525. package/platforms/electrobun/src/dynamic-views/errors.ts +29 -0
  526. package/platforms/electrobun/src/dynamic-views/host.test.ts +353 -0
  527. package/platforms/electrobun/src/dynamic-views/host.ts +332 -0
  528. package/platforms/electrobun/src/dynamic-views/index.ts +57 -0
  529. package/platforms/electrobun/src/dynamic-views/kiosk-canvas.ts +89 -0
  530. package/platforms/electrobun/src/dynamic-views/registry.test.ts +139 -0
  531. package/platforms/electrobun/src/dynamic-views/registry.ts +196 -0
  532. package/platforms/electrobun/src/dynamic-views/session-manager.test.ts +355 -0
  533. package/platforms/electrobun/src/dynamic-views/session-manager.ts +348 -0
  534. package/platforms/electrobun/src/dynamic-views/types.ts +105 -0
  535. package/platforms/electrobun/src/electrobun-boot-config.test.ts +50 -0
  536. package/platforms/electrobun/src/electrobun-config.test.ts +62 -0
  537. package/platforms/electrobun/src/electrobun-crypto-ready.test.ts +65 -0
  538. package/platforms/electrobun/src/electrobun-window-options.ts +25 -0
  539. package/platforms/electrobun/src/extension-rpc.test.ts +88 -0
  540. package/platforms/electrobun/src/extension-rpc.ts +102 -0
  541. package/platforms/electrobun/src/fatal-shutdown.test.ts +10 -10
  542. package/platforms/electrobun/src/fatal-shutdown.ts +1 -1
  543. package/platforms/electrobun/src/first-party-remotes.test.ts +169 -0
  544. package/platforms/electrobun/src/first-party-remotes.ts +297 -0
  545. package/platforms/electrobun/src/first-run-rpc.test.ts +192 -0
  546. package/platforms/electrobun/src/first-run-rpc.ts +146 -0
  547. package/platforms/electrobun/src/floating-chat-window.ts +181 -181
  548. package/platforms/electrobun/src/inbox-rpc.test.ts +123 -0
  549. package/platforms/electrobun/src/inbox-rpc.ts +158 -0
  550. package/platforms/electrobun/src/index.ts +2555 -2096
  551. package/platforms/electrobun/src/kiosk-mode.ts +50 -0
  552. package/platforms/electrobun/src/launch/index.ts +4 -0
  553. package/platforms/electrobun/src/launch/launch-dynamic-view.ts +37 -0
  554. package/platforms/electrobun/src/launch/launch-orchestrator.test.ts +224 -0
  555. package/platforms/electrobun/src/launch/launch-orchestrator.ts +456 -0
  556. package/platforms/electrobun/src/launch/launch-store.test.ts +97 -0
  557. package/platforms/electrobun/src/launch/launch-store.ts +134 -0
  558. package/platforms/electrobun/src/launch/types.ts +103 -0
  559. package/platforms/electrobun/src/launch/views/launch-diagnostics.html +205 -0
  560. package/platforms/electrobun/src/lifecycle/agent-ready-publish.test.ts +50 -0
  561. package/platforms/electrobun/src/lifecycle/agent-ready-publish.ts +27 -0
  562. package/platforms/electrobun/src/lifecycle/api-base-owner.ts +42 -31
  563. package/platforms/electrobun/src/lifecycle/desktop-session-prime.ts +44 -44
  564. package/platforms/electrobun/src/logger.ts +14 -14
  565. package/platforms/electrobun/src/main-window-runtime.ts +83 -83
  566. package/platforms/electrobun/src/main-window-session.test.ts +109 -0
  567. package/platforms/electrobun/src/main-window-session.ts +87 -51
  568. package/platforms/electrobun/src/menu-reset-from-main.ts +158 -158
  569. package/platforms/electrobun/src/native/agent-env.test.ts +52 -0
  570. package/platforms/electrobun/src/native/agent-runtime-layout.test.ts +42 -0
  571. package/platforms/electrobun/src/native/agent-state-dir.test.ts +91 -0
  572. package/platforms/electrobun/src/native/agent.ts +2122 -1682
  573. package/platforms/electrobun/src/native/auth-bridge.test.ts +67 -0
  574. package/platforms/electrobun/src/native/auth-bridge.ts +464 -360
  575. package/platforms/electrobun/src/native/browser-workspace.ts +723 -471
  576. package/platforms/electrobun/src/native/camera.ts +50 -50
  577. package/platforms/electrobun/src/native/canvas.ts +444 -445
  578. package/platforms/electrobun/src/native/credentials.ts +673 -616
  579. package/platforms/electrobun/src/native/desktop-window.test.ts +300 -0
  580. package/platforms/electrobun/src/native/desktop.ts +2196 -2156
  581. package/platforms/electrobun/src/native/editor-bridge.ts +201 -201
  582. package/platforms/electrobun/src/native/file-watcher.ts +154 -154
  583. package/platforms/electrobun/src/native/gateway.ts +179 -180
  584. package/platforms/electrobun/src/native/gpu-window.ts +256 -256
  585. package/platforms/electrobun/src/native/index.ts +76 -74
  586. package/platforms/electrobun/src/native/location.test.ts +44 -0
  587. package/platforms/electrobun/src/native/location.ts +90 -80
  588. package/platforms/electrobun/src/native/loopback-port.ts +60 -60
  589. package/platforms/electrobun/src/native/mac-window-effects.ts +166 -104
  590. package/platforms/electrobun/src/native/music-player.ts +38 -38
  591. package/platforms/electrobun/src/native/permissions-shared.ts +249 -150
  592. package/platforms/electrobun/src/native/permissions.ts +301 -208
  593. package/platforms/electrobun/src/native/power-state.ts +129 -129
  594. package/platforms/electrobun/src/native/remote-plugin-host.test.ts +1394 -0
  595. package/platforms/electrobun/src/native/remote-plugin-host.ts +1531 -0
  596. package/platforms/electrobun/src/native/screencapture.ts +667 -573
  597. package/platforms/electrobun/src/native/steward.ts +207 -204
  598. package/platforms/electrobun/src/native/swabble.ts +68 -324
  599. package/platforms/electrobun/src/native/talkmode.ts +253 -422
  600. package/platforms/electrobun/src/native/webgpu-browser-support.test.ts +18 -0
  601. package/platforms/electrobun/src/native/webgpu-browser-support.ts +165 -147
  602. package/platforms/electrobun/src/native/whisper-env.test.ts +71 -0
  603. package/platforms/electrobun/src/native/whisper-env.ts +68 -0
  604. package/platforms/electrobun/src/native-onboarding.ts +270 -0
  605. package/platforms/electrobun/src/onboarding-overlay-window.ts +141 -0
  606. package/platforms/electrobun/src/persisted-deployment.ts +91 -0
  607. package/platforms/electrobun/src/pill-window.test.ts +91 -0
  608. package/platforms/electrobun/src/pill-window.ts +99 -0
  609. package/platforms/electrobun/src/preload-validation.ts +44 -44
  610. package/platforms/electrobun/src/preload.js +1 -1
  611. package/platforms/electrobun/src/print-electrobun-dev-settings-banner.ts +120 -120
  612. package/platforms/electrobun/src/renderer-api-proxy.test.ts +73 -0
  613. package/platforms/electrobun/src/renderer-api-proxy.ts +86 -0
  614. package/platforms/electrobun/src/renderer-static.test.ts +53 -0
  615. package/platforms/electrobun/src/renderer-static.ts +144 -57
  616. package/platforms/electrobun/src/rpc-handler-slices.ts +121 -0
  617. package/platforms/electrobun/src/rpc-handlers.test.ts +267 -0
  618. package/platforms/electrobun/src/rpc-handlers.ts +1306 -913
  619. package/platforms/electrobun/src/rpc-parse-utils.ts +57 -0
  620. package/platforms/electrobun/src/rpc-port-resolver.test.ts +45 -0
  621. package/platforms/electrobun/src/rpc-port-resolver.ts +31 -0
  622. package/platforms/electrobun/src/rpc-schema.ts +2556 -1619
  623. package/platforms/electrobun/src/runtime-layout.ts +105 -105
  624. package/platforms/electrobun/src/runtime-permissions.ts +95 -95
  625. package/platforms/electrobun/src/runtime-rpc.test.ts +126 -0
  626. package/platforms/electrobun/src/runtime-rpc.ts +237 -0
  627. package/platforms/electrobun/src/screenshot-dev-server.ts +87 -87
  628. package/platforms/electrobun/src/settings-mutations-rpc.test.ts +193 -0
  629. package/platforms/electrobun/src/settings-mutations-rpc.ts +220 -0
  630. package/platforms/electrobun/src/startup-trace.ts +274 -270
  631. package/platforms/electrobun/src/subscription-rpc.test.ts +89 -0
  632. package/platforms/electrobun/src/subscription-rpc.ts +192 -0
  633. package/platforms/electrobun/src/surface-windows.test.ts +355 -0
  634. package/platforms/electrobun/src/surface-windows.ts +410 -410
  635. package/platforms/electrobun/src/trace/README.md +73 -0
  636. package/platforms/electrobun/src/trace/errors.ts +21 -0
  637. package/platforms/electrobun/src/trace/index.ts +40 -0
  638. package/platforms/electrobun/src/trace/trace-dynamic-view.ts +40 -0
  639. package/platforms/electrobun/src/trace/trace-host-requests.ts +473 -0
  640. package/platforms/electrobun/src/trace/trace-service.test.ts +186 -0
  641. package/platforms/electrobun/src/trace/trace-service.ts +324 -0
  642. package/platforms/electrobun/src/trace/trace-store.test.ts +141 -0
  643. package/platforms/electrobun/src/trace/trace-store.ts +551 -0
  644. package/platforms/electrobun/src/trace/types.ts +250 -0
  645. package/platforms/electrobun/src/trace/views/agent-run-trace.html +311 -0
  646. package/platforms/electrobun/src/types/web-speech.d.ts +28 -28
  647. package/platforms/electrobun/src/types.ts +5 -5
  648. package/platforms/electrobun/src/update-availability.test.ts +72 -0
  649. package/platforms/electrobun/src/update-availability.ts +90 -0
  650. package/platforms/electrobun/src/update-rpc.test.ts +83 -0
  651. package/platforms/electrobun/src/update-rpc.ts +123 -0
  652. package/platforms/electrobun/src/voice/README.md +184 -0
  653. package/platforms/electrobun/src/voice/errors.ts +42 -0
  654. package/platforms/electrobun/src/voice/index.ts +78 -0
  655. package/platforms/electrobun/src/voice/types.ts +316 -0
  656. package/platforms/electrobun/src/voice/voice-host-requests.ts +259 -0
  657. package/platforms/electrobun/src/voice/voice-latency-budget.test.ts +66 -0
  658. package/platforms/electrobun/src/voice/voice-latency-budget.ts +243 -0
  659. package/platforms/electrobun/src/voice/voice-live-validation.test.ts +352 -0
  660. package/platforms/electrobun/src/voice/voice-live-validation.ts +838 -0
  661. package/platforms/electrobun/src/voice/voice-pipeline.ts +250 -0
  662. package/platforms/electrobun/src/voice/voice-playback-adapter.ts +31 -0
  663. package/platforms/electrobun/src/voice/voice-runtime-adapter.test.ts +213 -0
  664. package/platforms/electrobun/src/voice/voice-runtime-adapter.ts +686 -0
  665. package/platforms/electrobun/src/voice/voice-service.test.ts +561 -0
  666. package/platforms/electrobun/src/voice/voice-service.ts +1027 -0
  667. package/platforms/electrobun/src/voice/voice-stream-coordinator.test.ts +115 -0
  668. package/platforms/electrobun/src/voice/voice-stream-coordinator.ts +270 -0
  669. package/platforms/electrobun/src/voice/voice-trace.ts +97 -0
  670. package/platforms/electrobun/src/voice/voice-tts-chunker.test.ts +91 -0
  671. package/platforms/electrobun/src/voice/voice-tts-chunker.ts +194 -0
  672. package/platforms/electrobun/src/windows-cef-profile.ts +88 -88
  673. package/platforms/electrobun/tsconfig.json +73 -13
  674. package/platforms/electrobun/update-channels.json +22 -0
  675. package/platforms/electrobun/vitest.electrobun.config.ts +72 -42
  676. package/platforms/ios/App/App/App.entitlements +4 -0
  677. package/platforms/ios/App/App/AppDelegate.swift +80 -18
  678. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ios-marketing-1024.png +0 -0
  679. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-20x20@1x.png +0 -0
  680. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-20x20@2x.png +0 -0
  681. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-29x29@1x.png +0 -0
  682. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-29x29@2x.png +0 -0
  683. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-40x40@1x.png +0 -0
  684. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-40x40@2x.png +0 -0
  685. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-76x76@1x.png +0 -0
  686. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-76x76@2x.png +0 -0
  687. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-ipad-83_5x83_5@2x.png +0 -0
  688. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-20x20@2x.png +0 -0
  689. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-20x20@3x.png +0 -0
  690. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-29x29@2x.png +0 -0
  691. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-29x29@3x.png +0 -0
  692. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-40x40@2x.png +0 -0
  693. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-40x40@3x.png +0 -0
  694. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-60x60@2x.png +0 -0
  695. package/platforms/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-iphone-60x60@3x.png +0 -0
  696. package/platforms/ios/App/App/Base.lproj/LaunchScreen.storyboard +1 -4
  697. package/platforms/ios/App/App/ComputerUseBridge.swift +589 -0
  698. package/platforms/ios/App/App/DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.entitlements +12 -0
  699. package/platforms/ios/App/App/DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.swift +34 -0
  700. package/platforms/ios/App/App/DeviceActivityMonitorExtension/Info.plist +29 -0
  701. package/platforms/ios/App/App/DeviceActivityReportExtension/DeviceActivityReportExtension.entitlements +12 -0
  702. package/platforms/ios/App/App/DeviceActivityReportExtension/DeviceActivityReportExtension.swift +53 -0
  703. package/platforms/ios/App/App/DeviceActivityReportExtension/Info.plist +27 -0
  704. package/platforms/ios/App/App/ElizaAppIntents.swift +183 -0
  705. package/platforms/ios/App/App/ElizaIntentPlugin.swift +342 -5
  706. package/platforms/ios/App/App/Info.plist +17 -1
  707. package/platforms/ios/App/App/runners/eliza-tasks.js +177 -0
  708. package/platforms/ios/App/App.xcodeproj/project.pbxproj +262 -6
  709. package/platforms/ios/App/BroadcastExtension/SampleHandler.swift +100 -0
  710. package/platforms/ios/App/Podfile +5 -0
  711. package/platforms/ios/App/Podfile.lock +83 -59
  712. package/register-runtime-hooks.js +11 -5
  713. package/registry/app-registry.d.ts +14 -0
  714. package/registry/app-registry.d.ts.map +1 -0
  715. package/registry/app-registry.js +29 -0
  716. package/registry/entries/apps/app-polymarket.json +31 -0
  717. package/registry/entries/apps/clawville.json +27 -0
  718. package/registry/entries/apps/companion.json +28 -0
  719. package/registry/entries/apps/database-viewer.json +27 -0
  720. package/registry/entries/apps/defense-of-the-agents.json +27 -0
  721. package/registry/entries/apps/documents.json +30 -0
  722. package/registry/entries/apps/feed.json +27 -0
  723. package/registry/entries/apps/hyperliquid.json +31 -0
  724. package/registry/entries/apps/log-viewer.json +27 -0
  725. package/registry/entries/apps/memory-viewer.json +27 -0
  726. package/registry/entries/apps/model-tester.json +31 -0
  727. package/registry/entries/apps/plugin-viewer.json +27 -0
  728. package/registry/entries/apps/relationship-viewer.json +27 -0
  729. package/registry/entries/apps/runtime-debugger.json +27 -0
  730. package/registry/entries/apps/shopify.json +31 -0
  731. package/registry/entries/apps/skills-viewer.json +27 -0
  732. package/registry/entries/apps/steward.json +31 -0
  733. package/registry/entries/apps/training.json +54 -0
  734. package/registry/entries/apps/trajectory-viewer.json +27 -0
  735. package/registry/entries/apps/vincent.json +31 -0
  736. package/registry/entries/connectors/bluebubbles.json +99 -0
  737. package/registry/entries/connectors/bluesky.json +173 -0
  738. package/registry/entries/connectors/discord.json +119 -0
  739. package/registry/entries/connectors/farcaster.json +174 -0
  740. package/registry/entries/connectors/feishu.json +79 -0
  741. package/registry/entries/connectors/google-chat.json +120 -0
  742. package/registry/entries/connectors/google.json +82 -0
  743. package/registry/entries/connectors/imessage.json +96 -0
  744. package/registry/entries/connectors/instagram.json +64 -0
  745. package/registry/entries/connectors/line.json +86 -0
  746. package/registry/entries/connectors/matrix.json +94 -0
  747. package/registry/entries/connectors/mattermost.json +110 -0
  748. package/registry/entries/connectors/msteams.json +104 -0
  749. package/registry/entries/connectors/nextcloud-talk.json +104 -0
  750. package/registry/entries/connectors/nostr.json +70 -0
  751. package/registry/entries/connectors/signal.json +81 -0
  752. package/registry/entries/connectors/slack.json +102 -0
  753. package/registry/entries/connectors/telegram.json +71 -0
  754. package/registry/entries/connectors/tlon.json +94 -0
  755. package/registry/entries/connectors/twitch.json +110 -0
  756. package/registry/entries/connectors/whatsapp.json +113 -0
  757. package/registry/entries/connectors/x.json +231 -0
  758. package/registry/entries/connectors/zalo.json +112 -0
  759. package/registry/entries/connectors/zalouser.json +122 -0
  760. package/registry/entries/plugins/agent-orchestrator.json +33 -0
  761. package/registry/entries/plugins/agent-skills.json +72 -0
  762. package/registry/entries/plugins/anthropic.json +73 -0
  763. package/registry/entries/plugins/app-control.json +23 -0
  764. package/registry/entries/plugins/auto-trader.json +203 -0
  765. package/registry/entries/plugins/background-runner.json +26 -0
  766. package/registry/entries/plugins/blooio.json +102 -0
  767. package/registry/entries/plugins/browser.json +75 -0
  768. package/registry/entries/plugins/cli.json +40 -0
  769. package/registry/entries/plugins/clipboard.json +44 -0
  770. package/registry/entries/plugins/coding-tools.json +71 -0
  771. package/registry/entries/plugins/commands.json +63 -0
  772. package/registry/entries/plugins/computeruse.json +74 -0
  773. package/registry/entries/plugins/copilot-proxy.json +93 -0
  774. package/registry/entries/plugins/directives.json +63 -0
  775. package/registry/entries/plugins/edge-tts.json +97 -0
  776. package/registry/entries/plugins/elevenlabs.json +169 -0
  777. package/registry/entries/plugins/elizacloud.json +208 -0
  778. package/registry/entries/plugins/evm.json +134 -0
  779. package/registry/entries/plugins/experience.json +34 -0
  780. package/registry/entries/plugins/facewear.json +131 -0
  781. package/registry/entries/plugins/form.json +26 -0
  782. package/registry/entries/plugins/github.json +93 -0
  783. package/registry/entries/plugins/gmail-watch.json +25 -0
  784. package/registry/entries/plugins/goals.json +77 -0
  785. package/registry/entries/plugins/google-genai.json +106 -0
  786. package/registry/entries/plugins/groq.json +93 -0
  787. package/registry/entries/plugins/hedera.json +48 -0
  788. package/registry/entries/plugins/inmemorydb.json +25 -0
  789. package/registry/entries/plugins/linear.json +51 -0
  790. package/registry/entries/plugins/local-inference.json +142 -0
  791. package/registry/entries/plugins/local-storage.json +36 -0
  792. package/registry/entries/plugins/localdb.json +25 -0
  793. package/registry/entries/plugins/mcp.json +44 -0
  794. package/registry/entries/plugins/memory.json +124 -0
  795. package/registry/entries/plugins/minecraft.json +79 -0
  796. package/registry/entries/plugins/moltbook.json +83 -0
  797. package/registry/entries/plugins/music.json +155 -0
  798. package/registry/entries/plugins/mysticism.json +48 -0
  799. package/registry/entries/plugins/nearai.json +82 -0
  800. package/registry/entries/plugins/ngrok.json +69 -0
  801. package/registry/entries/plugins/ollama.json +96 -0
  802. package/registry/entries/plugins/openai.json +189 -0
  803. package/registry/entries/plugins/openrouter.json +188 -0
  804. package/registry/entries/plugins/pdf.json +26 -0
  805. package/registry/entries/plugins/plugin-manager.json +23 -0
  806. package/registry/entries/plugins/prose.json +48 -0
  807. package/registry/entries/plugins/rlm.json +26 -0
  808. package/registry/entries/plugins/roblox.json +88 -0
  809. package/registry/entries/plugins/rss.json +64 -0
  810. package/registry/entries/plugins/s3-storage.json +91 -0
  811. package/registry/entries/plugins/scheduling.json +35 -0
  812. package/registry/entries/plugins/shell.json +94 -0
  813. package/registry/entries/plugins/social-alpha.json +72 -0
  814. package/registry/entries/plugins/tailscale.json +81 -0
  815. package/registry/entries/plugins/tee.json +53 -0
  816. package/registry/entries/plugins/todos.json +26 -0
  817. package/registry/entries/plugins/trajectory-logger.json +33 -0
  818. package/registry/entries/plugins/trust.json +39 -0
  819. package/registry/entries/plugins/tts.json +71 -0
  820. package/registry/entries/plugins/tunnel.json +45 -0
  821. package/registry/entries/plugins/twilio.json +168 -0
  822. package/registry/entries/plugins/vercel-ai-gateway.json +128 -0
  823. package/registry/entries/plugins/video.json +23 -0
  824. package/registry/entries/plugins/vision.json +43 -0
  825. package/registry/entries/plugins/webhooks.json +23 -0
  826. package/registry/entries/plugins/workflow.json +25 -0
  827. package/registry/entries/plugins/xai.json +75 -0
  828. package/registry/index.d.ts +2 -1
  829. package/registry/index.d.ts.map +1 -1
  830. package/registry/index.js +46 -12
  831. package/registry/loader.d.ts +2 -1
  832. package/registry/loader.d.ts.map +1 -1
  833. package/registry/loader.js +49 -2
  834. package/registry/schema.d.ts +244 -34
  835. package/registry/schema.d.ts.map +1 -1
  836. package/registry/schema.js +36 -0
  837. package/runtime/android-avf-microdroid-bridge.d.ts +29 -0
  838. package/runtime/android-avf-microdroid-bridge.d.ts.map +1 -0
  839. package/runtime/android-avf-microdroid-bridge.js +149 -0
  840. package/runtime/api-dev-settings-banner.d.ts.map +1 -1
  841. package/runtime/api-dev-settings-banner.js +5 -13
  842. package/runtime/app-core-runtime-hooks.d.ts +21 -0
  843. package/runtime/app-core-runtime-hooks.d.ts.map +1 -0
  844. package/runtime/app-core-runtime-hooks.js +10 -0
  845. package/runtime/autonomy-policy.d.ts +2 -0
  846. package/runtime/autonomy-policy.d.ts.map +1 -0
  847. package/runtime/autonomy-policy.js +4 -0
  848. package/runtime/desktop/AppWindowRenderer.d.ts +17 -0
  849. package/runtime/desktop/AppWindowRenderer.d.ts.map +1 -0
  850. package/runtime/desktop/AppWindowRenderer.js +360 -0
  851. package/runtime/desktop/DesktopSurfaceNavigationRuntime.d.ts +2 -0
  852. package/runtime/desktop/DesktopSurfaceNavigationRuntime.d.ts.map +1 -0
  853. package/runtime/desktop/DesktopSurfaceNavigationRuntime.js +41 -0
  854. package/runtime/desktop/DesktopTrayRuntime.d.ts +2 -0
  855. package/runtime/desktop/DesktopTrayRuntime.d.ts.map +1 -0
  856. package/runtime/desktop/DesktopTrayRuntime.js +174 -0
  857. package/runtime/desktop/DetachedShellRoot.d.ts +10 -0
  858. package/runtime/desktop/DetachedShellRoot.d.ts.map +1 -0
  859. package/runtime/desktop/DetachedShellRoot.js +111 -0
  860. package/runtime/desktop/index.d.ts +6 -0
  861. package/runtime/desktop/index.d.ts.map +1 -0
  862. package/runtime/desktop/index.js +5 -0
  863. package/runtime/desktop/tray-menu.d.ts +20 -0
  864. package/runtime/desktop/tray-menu.d.ts.map +1 -0
  865. package/runtime/desktop/tray-menu.js +143 -0
  866. package/runtime/dev-server.d.ts +1 -1
  867. package/runtime/dev-server.d.ts.map +1 -1
  868. package/runtime/dev-server.js +93 -17
  869. package/runtime/eliza.d.ts +75 -1
  870. package/runtime/eliza.d.ts.map +1 -1
  871. package/runtime/eliza.js +596 -122
  872. package/runtime/ensure-text-to-speech-handler.d.ts.map +1 -1
  873. package/runtime/ensure-text-to-speech-handler.js +10 -3
  874. package/runtime/mobile-safe-runtime.d.ts +181 -2
  875. package/runtime/mobile-safe-runtime.d.ts.map +1 -1
  876. package/runtime/mobile-safe-runtime.js +1019 -12
  877. package/runtime/mode/remote-forwarder.d.ts.map +1 -1
  878. package/runtime/mode/remote-forwarder.js +2 -2
  879. package/runtime/mode/route-mode-guard.d.ts +1 -2
  880. package/runtime/mode/route-mode-guard.d.ts.map +1 -1
  881. package/runtime/mode/route-mode-guard.js +4 -5
  882. package/runtime/mode/route-mode-matrix.d.ts.map +1 -1
  883. package/runtime/mode/route-mode-matrix.js +14 -1
  884. package/runtime/mode/runtime-mode.d.ts +1 -1
  885. package/runtime/mode/runtime-mode.d.ts.map +1 -1
  886. package/runtime/mode/runtime-mode.js +15 -4
  887. package/runtime/runtime-bootstrap-policy.d.ts.map +1 -1
  888. package/runtime/runtime-bootstrap-policy.js +14 -2
  889. package/runtime/telegram-standalone-handler.d.ts.map +1 -1
  890. package/runtime/telegram-standalone-handler.js +10 -9
  891. package/runtime/tts-cache-wiring.d.ts +29 -0
  892. package/runtime/tts-cache-wiring.d.ts.map +1 -0
  893. package/runtime/tts-cache-wiring.js +114 -0
  894. package/runtime/voice-warmup.d.ts +81 -0
  895. package/runtime/voice-warmup.d.ts.map +1 -0
  896. package/runtime/voice-warmup.js +111 -0
  897. package/scripts/android-sms-gateway-template.test.mjs +1014 -0
  898. package/scripts/aosp/README.md +19 -15
  899. package/scripts/aosp/compile-libllama.mjs +1344 -248
  900. package/scripts/aosp/compile-shim.mjs +47 -18
  901. package/scripts/aosp/deploy-pixel.mjs +405 -0
  902. package/scripts/aosp/lib/load-variant-config.mjs +3 -3
  903. package/scripts/aosp/llama-cpp-patches/README.md +8 -8
  904. package/scripts/aosp/llama-cpp-patches/apply-patches.mjs +23 -6
  905. package/scripts/aosp/llama-cpp-patches/polarquant/README.md +37 -0
  906. package/scripts/aosp/llama-cpp-patches/qjl/README.md +37 -0
  907. package/scripts/aosp/seccomp-shim/sigsys-handler-arm64.c +169 -0
  908. package/scripts/aosp/seccomp-shim/sigsys-handler-riscv64.c +217 -0
  909. package/scripts/aosp/smoke-cuttlefish.mjs +34 -4
  910. package/scripts/aosp/stage-default-models.mjs +18 -18
  911. package/scripts/aosp/variant-config-schema.ts +2 -2
  912. package/scripts/assert-required-bundled-packages.test.ts +534 -0
  913. package/scripts/audit-apple-store-sandbox.mjs +146 -0
  914. package/scripts/audit-live-test-surface.mjs +5 -2
  915. package/scripts/build-capacitor-app.mjs +21 -0
  916. package/scripts/build-flatpak.mjs +5 -5
  917. package/scripts/build-helpers/arm64-simd.mjs +72 -0
  918. package/scripts/build-helpers/omnivoice-merged.mjs +87 -0
  919. package/scripts/build-helpers/verify-fused-symbols.mjs +567 -0
  920. package/scripts/build-image.sh +1 -1
  921. package/scripts/build-llama-cpp-mtp.mjs +487 -0
  922. package/scripts/build-native-plugins.mjs +230 -18
  923. package/scripts/build-patched-electrobun-cli.mjs +68 -10
  924. package/scripts/build-win.mjs +1 -1
  925. package/scripts/bun-riscv64/Dockerfile +418 -0
  926. package/scripts/bun-riscv64/README.md +316 -0
  927. package/scripts/bun-riscv64/build.sh +469 -0
  928. package/scripts/bun-riscv64/bun-patches/0001-config-add-riscv64-arch.patch +74 -0
  929. package/scripts/bun-riscv64/bun-patches/0002-flags-add-riscv64-march-mabi.patch +16 -0
  930. package/scripts/bun-riscv64/bun-patches/0003-zig-add-riscv64-target-triple-and-cpu.patch +26 -0
  931. package/scripts/bun-riscv64/bun-patches/0004-webkit-force-local-mode-on-riscv64.patch +33 -0
  932. package/scripts/bun-riscv64/bun-patches/0005-tinycc-disable-on-riscv64.patch +16 -0
  933. package/scripts/bun-riscv64/bun-patches/0006-build-add-riscv64-cli-validation.patch +15 -0
  934. package/scripts/bun-riscv64/bun-patches/0007-deps-per-dep-riscv64-checks.patch +24 -0
  935. package/scripts/bun-riscv64/bun-patches/0008-source-stabilize-riscv64-musl-build.patch +226 -0
  936. package/scripts/bun-riscv64/bun-patches/0009-disable-wasm-streaming-hooks-for-c-loop.patch +162 -0
  937. package/scripts/bun-riscv64/bun-patches/0010-disable-inspector-profiler-for-riscv64-c-loop.patch +80 -0
  938. package/scripts/bun-riscv64/bun-patches/0011-process-arch-add-riscv64.patch +23 -0
  939. package/scripts/bun-riscv64/bun-patches/0012-cpu-features-add-riscv64-fallback.patch +13 -0
  940. package/scripts/bun-riscv64/bun-patches/0013-disable-console-inspector-hooks-for-riscv64-c-loop.patch +43 -0
  941. package/scripts/bun-riscv64/bun-patches/0014-disable-custom-inspector-dispatchers-on-riscv64.patch +127 -0
  942. package/scripts/bun-riscv64/bun-patches/0015-disable-jsc-profiler-builtins-on-riscv64.patch +75 -0
  943. package/scripts/bun-riscv64/bun-patches/0016-node-vm-disable-jit-cached-data-on-riscv64-c-loop.patch +96 -0
  944. package/scripts/bun-riscv64/bun-patches/0017-disable-performance-domjit-signature-on-riscv64-c-loop.patch +34 -0
  945. package/scripts/bun-riscv64/bun-patches/0018-fix-serialized-script-identifier-big-endian-path.patch +19 -0
  946. package/scripts/bun-riscv64/bun-patches/0019-add-wtf-timer-fire-bridge-for-c-loop.patch +24 -0
  947. package/scripts/bun-riscv64/bun-patches/0020-run-riscv64-smoke-test-under-qemu.patch +13 -0
  948. package/scripts/bun-riscv64/bun-patches/0021-fix-riscv64-linux-open-flags.patch +25 -0
  949. package/scripts/bun-riscv64/bun-patches/0022-zlib-riscv64-generic-kernels.patch +25 -0
  950. package/scripts/bun-riscv64/bun-patches/README.md +127 -0
  951. package/scripts/bun-riscv64/bun-version.json +202 -0
  952. package/scripts/bun-riscv64/run-build.sh +162 -0
  953. package/scripts/bun-riscv64/rust-core/0001-riscv64-rust-core-port.patch +868 -0
  954. package/scripts/bun-riscv64/rust-core/0002-second-wave-riscv64-source-gaps.patch +130 -0
  955. package/scripts/bun-riscv64/rust-core/0003-third-wave-riscv64-crash-handler-gaps.patch +78 -0
  956. package/scripts/bun-riscv64/rust-core/0004-rust-target-cpu-riscv64.patch +39 -0
  957. package/scripts/bun-riscv64/rust-core/0005-fifth-wave-riscv64-source-gaps.patch +96 -0
  958. package/scripts/bun-riscv64/rust-core/0006-cpp-wasm-and-inspector-guards-riscv64.patch +91 -0
  959. package/scripts/bun-riscv64/rust-core/0007-bun-alloc-max-align-t-riscv64.patch +36 -0
  960. package/scripts/bun-riscv64/rust-core/0008-workspace-lints-warn-not-deny-riscv64.patch +75 -0
  961. package/scripts/bun-riscv64/rust-core/0009-zigglobalobject-wasm-streaming-guards-riscv64.patch +109 -0
  962. package/scripts/bun-riscv64/rust-core/0010-tcc-externs-stub-on-riscv64.patch +62 -0
  963. package/scripts/bun-riscv64/rust-core/0011-clippy-ptr-cast-lints-warn-riscv64.patch +61 -0
  964. package/scripts/bun-riscv64/rust-core/README.md +80 -0
  965. package/scripts/bun-riscv64/rust-core/webkit-patches/0003-disable-dfg-ftl-on-riscv64.patch +60 -0
  966. package/scripts/bun-riscv64/rust-core/webkit-patches/0004-riscv64-do-not-force-wasm-in-c-loop.patch +31 -0
  967. package/scripts/bun-riscv64/rust-core/webkit-patches/0005-domjit-effect-allow-no-dfg-c-loop.patch +40 -0
  968. package/scripts/bun-riscv64/rust-core/webkit-patches/0006-disable-usewasm-when-webassembly-compiled-out.patch +33 -0
  969. package/scripts/bun-riscv64/rust-core/webkit-patches/0007-restore-dropped-includes-and-llint-fwd-decl.patch +31 -0
  970. package/scripts/bun-riscv64/validate.sh +264 -0
  971. package/scripts/bun-riscv64/webkit-patches/0001-cherry-pick-llint-riscv64.recipe +155 -0
  972. package/scripts/bun-riscv64/webkit-patches/0002-cherry-pick-baseline-jit-riscv64.recipe +40 -0
  973. package/scripts/bun-riscv64/webkit-patches/0003-disable-dfg-ftl-on-riscv64.patch +60 -0
  974. package/scripts/bun-riscv64/webkit-patches/0004-riscv64-do-not-force-wasm-in-c-loop.patch +31 -0
  975. package/scripts/bun-riscv64/webkit-patches/0005-domjit-effect-allow-no-dfg-c-loop.patch +40 -0
  976. package/scripts/bun-riscv64/webkit-patches/0006-disable-usewasm-when-webassembly-compiled-out.patch +33 -0
  977. package/scripts/bun-riscv64/webkit-patches/0007-restore-dropped-includes-and-llint-fwd-decl.patch +72 -0
  978. package/scripts/bun-riscv64/webkit-patches/README.md +146 -0
  979. package/scripts/check-homepage-public-readiness.mjs +353 -0
  980. package/scripts/check-homepage-release-data.mjs +110 -0
  981. package/scripts/check-i18n.mjs +2 -1
  982. package/scripts/check-real-local-chat.ts +147 -0
  983. package/scripts/check-real-local-provisioning.ts +104 -0
  984. package/scripts/check-real-local-reset.ts +249 -0
  985. package/scripts/check-sms-gateway-completion-audit.mjs +428 -0
  986. package/scripts/check-sms-gateway-readiness.mjs +266 -0
  987. package/scripts/clean-repo.mjs +5 -5
  988. package/scripts/codesign-mas.mjs +222 -16
  989. package/scripts/collect-docker-runtime-deps.mjs +229 -0
  990. package/scripts/continue-sms-gateway-work.mjs +121 -0
  991. package/scripts/copy-runtime-node-modules.ts +903 -195
  992. package/scripts/deploy-cloud-api-production-gateway.mjs +52 -0
  993. package/scripts/desktop-build.mjs +655 -101
  994. package/scripts/dev-platform.mjs +346 -102
  995. package/scripts/dev-startup-smoke.mjs +248 -0
  996. package/scripts/dev-ui.mjs +418 -176
  997. package/scripts/disable-local-eliza-workspace.mjs +35 -0
  998. package/scripts/docker-ci-smoke.sh +298 -96
  999. package/scripts/docker-entrypoint.sh +62 -1
  1000. package/scripts/docker-entrypoint.test.ts +283 -0
  1001. package/scripts/ensure-avatars.mjs +2 -2
  1002. package/scripts/ensure-electrobun-core.mjs +1 -1
  1003. package/scripts/ensure-generated-core-proto-js.mjs +1 -1
  1004. package/scripts/ensure-type-package-aliases.mjs +62 -5
  1005. package/scripts/ensure-vision-deps.mjs +20 -1
  1006. package/scripts/entry.ts +1 -1
  1007. package/scripts/ffi-stub/Makefile +64 -0
  1008. package/scripts/ffi-stub/README.md +391 -0
  1009. package/scripts/ffi-stub/asr-ffi-smoke.ts +139 -0
  1010. package/scripts/ffi-stub/ffi-stub.c +539 -0
  1011. package/scripts/ffi-stub/ffi.h +538 -0
  1012. package/scripts/ffi-stub/libelizainference_stub.so +0 -0
  1013. package/scripts/ffi-stub/tts-stream-ffi-smoke.ts +349 -0
  1014. package/scripts/generate-first-run-voicelines.mjs +194 -0
  1015. package/scripts/generate-plugin-index.js +4 -3
  1016. package/scripts/generate-static-asset-manifest.mjs +1 -1
  1017. package/scripts/i18n-dynamic-keys.json +5 -5
  1018. package/scripts/init-submodules.mjs +2 -2
  1019. package/scripts/install-android-sms-gateway.md +177 -0
  1020. package/scripts/install-android-sms-gateway.mjs +1088 -0
  1021. package/scripts/ios-xcframework/README.md +74 -72
  1022. package/scripts/ios-xcframework/build-xcframework.mjs +204 -43
  1023. package/scripts/ios-xcframework/run-physical-device-smoke.mjs +1943 -0
  1024. package/scripts/ios-xcframework/runtime-symbol-shim.c +450 -0
  1025. package/scripts/kernel-patches/cpu-polar-kernels.mjs +441 -0
  1026. package/scripts/kernel-patches/cpu-simd-kernels.mjs +253 -0
  1027. package/scripts/kernel-patches/cpu-thread-parallelism.mjs +368 -0
  1028. package/scripts/kernel-patches/cuda-kernels.mjs +117 -0
  1029. package/scripts/kernel-patches/metal-kernels.mjs +1698 -109
  1030. package/scripts/kernel-patches/server-omnivoice-route.mjs +718 -0
  1031. package/scripts/kernel-patches/server-structured-output.mjs +279 -0
  1032. package/scripts/kernel-patches/vulkan-dispatch-log.mjs +166 -0
  1033. package/scripts/kernel-patches/vulkan-dispatch-log.test.mjs +50 -0
  1034. package/scripts/kernel-patches/vulkan-dispatch-patches/01-vulkan-shaders-gen.patch +30 -16
  1035. package/scripts/kernel-patches/vulkan-dispatch-patches/02-ggml-vulkan-pipelines.patch +75 -30
  1036. package/scripts/kernel-patches/vulkan-kernels.mjs +800 -49
  1037. package/scripts/lib/agent-source-watcher.mjs +174 -0
  1038. package/scripts/lib/agent-source-watcher.test.mjs +184 -0
  1039. package/scripts/lib/api-supervisor.mjs +78 -9
  1040. package/scripts/lib/api-supervisor.test.mjs +121 -0
  1041. package/scripts/lib/app-dir.mjs +2 -16
  1042. package/scripts/lib/apple-entitlement-audit.mjs +655 -0
  1043. package/scripts/lib/apple-entitlement-audit.test.mjs +144 -0
  1044. package/scripts/lib/bun-version-guard.mjs +13 -13
  1045. package/scripts/lib/capacitor-plugin-build-needed.mjs +4 -3
  1046. package/scripts/lib/capacitor-plugin-names.mjs +30 -14
  1047. package/scripts/lib/desktop-preflight.mjs +9 -5
  1048. package/scripts/lib/desktop-startup-embedding-warmup-policy.mjs +51 -0
  1049. package/scripts/lib/desktop-startup-embedding-warmup-policy.test.mjs +55 -0
  1050. package/scripts/lib/duet-bridge.d.mts +63 -0
  1051. package/scripts/lib/duet-bridge.mjs +193 -0
  1052. package/scripts/lib/node-path-env.mjs +4 -2
  1053. package/scripts/lib/orchestrator-desktop-dev-banner.mjs +12 -3
  1054. package/scripts/lib/patch-bun-exports.mjs +90 -27
  1055. package/scripts/lib/patch-bun-exports.test.mjs +79 -0
  1056. package/scripts/lib/renderer-build-action.mjs +35 -0
  1057. package/scripts/lib/renderer-build-action.test.mjs +70 -0
  1058. package/scripts/lib/stage-android-agent.mjs +748 -99
  1059. package/scripts/lib/sync-eliza-env-aliases.mjs +3 -25
  1060. package/scripts/lib/ui-smoke-stub-decision.mjs +33 -0
  1061. package/scripts/lib/ui-smoke-stub-decision.test.mjs +46 -0
  1062. package/scripts/lib/vite-renderer-dist-stale.mjs +5 -0
  1063. package/scripts/lib/voice-latency-report.mjs +154 -0
  1064. package/scripts/lifeops-prompt-benchmark.ts +21 -12
  1065. package/scripts/link-docker-local-app-packages.mjs +89 -36
  1066. package/scripts/local-stt-bench.ts +192 -0
  1067. package/scripts/maintain-cloud-api-production-gateway.mjs +54 -0
  1068. package/scripts/mas-smoke.mjs +459 -0
  1069. package/scripts/mas-smoke.test.mjs +220 -0
  1070. package/scripts/mobile-auth-simulator-smoke.mjs +0 -1
  1071. package/scripts/normalize-eliza-capture.ts +97 -0
  1072. package/scripts/omnivoice-fuse/prepare.mjs +2543 -23
  1073. package/scripts/pack-upstreams.mjs +65 -5
  1074. package/scripts/package-electrobun-linux.mjs +303 -0
  1075. package/scripts/patch-deps.mjs +5 -3
  1076. package/scripts/patches/llama-mobile-kokoro-tts.patch +480 -0
  1077. package/scripts/playwright-ui-live-stack.ts +194 -49
  1078. package/scripts/playwright-ui-smoke-api-stub.mjs +3501 -109
  1079. package/scripts/pre-review-local.mjs +2 -2
  1080. package/scripts/prepare-ios-cocoapods.sh +41 -3
  1081. package/scripts/release-check.ts +180 -84
  1082. package/scripts/release-workflow-drift.test.ts +57 -0
  1083. package/scripts/relink-workspace-packages-to-dist.mjs +21 -4
  1084. package/scripts/rt.mjs +16 -1
  1085. package/scripts/run-biome-check.mjs +1 -1
  1086. package/scripts/run-coding-agent-e2e.mjs +3 -3
  1087. package/scripts/run-eliza-app-core-script.mjs +34 -0
  1088. package/scripts/run-local-plugin-live-smoke.mjs +71 -2
  1089. package/scripts/run-mobile-build-android-app-actions.test.mjs +426 -0
  1090. package/scripts/run-mobile-build.mjs +4757 -607
  1091. package/scripts/run-node-runtime.mjs +184 -7
  1092. package/scripts/run-node-runtime.test.mjs +167 -0
  1093. package/scripts/run-node-tsx.mjs +80 -33
  1094. package/scripts/run-node.mjs +41 -1
  1095. package/scripts/run-production-build.mjs +34 -27
  1096. package/scripts/run-release-check.mjs +19 -0
  1097. package/scripts/run-release-contract-suite.mjs +107 -14
  1098. package/scripts/run-ui-smoke-playwright-suite.mjs +0 -2
  1099. package/scripts/runtime-package-manifest.ts +21 -3
  1100. package/scripts/setup-upstreams.mjs +42 -1
  1101. package/scripts/sms-gateway-status.mjs +194 -0
  1102. package/scripts/stage-android-agent.test.mjs +97 -0
  1103. package/scripts/stage-elizavoice-lib.mjs +203 -0
  1104. package/scripts/startup-integration-script-drift.test.ts +82 -4
  1105. package/scripts/streaming-pipeline-bench.ts +543 -0
  1106. package/scripts/sync-homepage-porkbun-dns.mjs +262 -0
  1107. package/scripts/test-sms-gateway-software.mjs +100 -0
  1108. package/scripts/type-audit.mjs +1 -1
  1109. package/scripts/validate-bluebubbles-outbound.mjs +293 -0
  1110. package/scripts/validate-cdn-assets.mjs +15 -7
  1111. package/scripts/validate-regression-matrix.mjs +109 -8
  1112. package/scripts/verify-android-sms-gateway-e2e.mjs +362 -0
  1113. package/scripts/verify-bluebubbles-gateway-e2e.mjs +191 -0
  1114. package/scripts/verify-bluebubbles-inbound-readiness.mjs +88 -0
  1115. package/scripts/verify-cloud-api-production-deploy.mjs +87 -0
  1116. package/scripts/verify-cloud-sms-onboarding-flow.mjs +336 -0
  1117. package/scripts/voice/freeze-voice.mjs +521 -0
  1118. package/scripts/voice-attribution-smoke.ts +538 -0
  1119. package/scripts/voice-create-profile.mjs +379 -0
  1120. package/scripts/voice-duet.mjs +1355 -0
  1121. package/scripts/voice-e2e-hardware.ts +871 -0
  1122. package/scripts/voice-interactive.mjs +1750 -0
  1123. package/scripts/voice-latency-report.mjs +96 -0
  1124. package/scripts/voice-latency-report.test.ts +176 -0
  1125. package/scripts/voice-preset/build-default-voice-preset.mjs +249 -0
  1126. package/scripts/voice-preset/build-onboarding-voice.mjs +281 -0
  1127. package/scripts/watch-sms-gateway-readiness.mjs +303 -0
  1128. package/scripts/write-homepage-release-data.mjs +458 -26
  1129. package/security/agent-vault-id.d.ts +1 -1
  1130. package/security/agent-vault-id.js +1 -1
  1131. package/security/hydrate-wallet-keys-from-platform-store.d.ts.map +1 -1
  1132. package/security/hydrate-wallet-keys-from-platform-store.js +23 -14
  1133. package/security/platform-secure-store-node.d.ts +2 -2
  1134. package/security/platform-secure-store-node.js +3 -3
  1135. package/security/wallet-os-store-actions.d.ts +0 -9
  1136. package/security/wallet-os-store-actions.d.ts.map +1 -1
  1137. package/security/wallet-os-store-actions.js +3 -10
  1138. package/services/account-pool.d.ts +23 -14
  1139. package/services/account-pool.d.ts.map +1 -1
  1140. package/services/account-pool.js +86 -24
  1141. package/services/account-usage.d.ts.map +1 -1
  1142. package/services/account-usage.js +2 -5
  1143. package/services/ambient-audio/consent.d.ts +9 -0
  1144. package/services/ambient-audio/consent.d.ts.map +1 -0
  1145. package/services/ambient-audio/consent.js +28 -0
  1146. package/services/ambient-audio/index.d.ts +7 -0
  1147. package/services/ambient-audio/index.d.ts.map +1 -0
  1148. package/services/ambient-audio/index.js +4 -0
  1149. package/services/ambient-audio/replay-buffer.d.ts +14 -0
  1150. package/services/ambient-audio/replay-buffer.d.ts.map +1 -0
  1151. package/services/ambient-audio/replay-buffer.js +66 -0
  1152. package/services/ambient-audio/response-gate.d.ts +3 -0
  1153. package/services/ambient-audio/response-gate.d.ts.map +1 -0
  1154. package/services/ambient-audio/response-gate.js +33 -0
  1155. package/services/ambient-audio/service.d.ts +22 -0
  1156. package/services/ambient-audio/service.d.ts.map +1 -0
  1157. package/services/ambient-audio/service.js +47 -0
  1158. package/services/ambient-audio/types.d.ts +42 -0
  1159. package/services/ambient-audio/types.d.ts.map +1 -0
  1160. package/services/app-updates/update-policy.d.ts +64 -0
  1161. package/services/app-updates/update-policy.d.ts.map +1 -0
  1162. package/services/app-updates/update-policy.js +228 -0
  1163. package/services/auth-store.d.ts +37 -1
  1164. package/services/auth-store.d.ts.map +1 -1
  1165. package/services/auth-store.js +59 -26
  1166. package/services/cloud-jwks-store.d.ts +3 -3
  1167. package/services/cloud-jwks-store.d.ts.map +1 -1
  1168. package/services/cloud-jwks-store.js +5 -8
  1169. package/services/coding-account-bridge.d.ts +71 -0
  1170. package/services/coding-account-bridge.d.ts.map +1 -0
  1171. package/services/coding-account-bridge.js +267 -0
  1172. package/services/connector-target-catalog.d.ts +10 -3
  1173. package/services/connector-target-catalog.d.ts.map +1 -1
  1174. package/services/connector-target-catalog.js +7 -4
  1175. package/services/credential-tunnel-service.d.ts +66 -0
  1176. package/services/credential-tunnel-service.d.ts.map +1 -0
  1177. package/services/credential-tunnel-service.js +227 -0
  1178. package/services/github-credentials.d.ts +1 -1
  1179. package/services/github-credentials.js +1 -1
  1180. package/services/inference-abort.d.ts +47 -0
  1181. package/services/inference-abort.d.ts.map +1 -0
  1182. package/services/inference-abort.js +76 -0
  1183. package/services/persistence.d.ts +2 -3
  1184. package/services/persistence.d.ts.map +1 -1
  1185. package/services/persistence.js +2 -3
  1186. package/services/phrase-chunked-tts.d.ts +136 -0
  1187. package/services/phrase-chunked-tts.d.ts.map +1 -0
  1188. package/services/phrase-chunked-tts.js +208 -0
  1189. package/services/sandbox-registry.d.ts +78 -0
  1190. package/services/sandbox-registry.d.ts.map +1 -0
  1191. package/services/sandbox-registry.js +323 -0
  1192. package/services/secrets-manager-installer.d.ts +8 -1
  1193. package/services/secrets-manager-installer.d.ts.map +1 -1
  1194. package/services/secrets-manager-installer.js +27 -2
  1195. package/services/sensitive-requests/cloud-link-adapter.d.ts +15 -0
  1196. package/services/sensitive-requests/cloud-link-adapter.d.ts.map +1 -0
  1197. package/services/sensitive-requests/cloud-link-adapter.js +73 -0
  1198. package/services/sensitive-requests/index.d.ts +27 -0
  1199. package/services/sensitive-requests/index.d.ts.map +1 -0
  1200. package/services/sensitive-requests/index.js +51 -0
  1201. package/services/sensitive-requests/instruct-dm-only-adapter.d.ts +14 -0
  1202. package/services/sensitive-requests/instruct-dm-only-adapter.d.ts.map +1 -0
  1203. package/services/sensitive-requests/instruct-dm-only-adapter.js +22 -0
  1204. package/services/sensitive-requests/owner-app-inline-adapter.d.ts +3 -0
  1205. package/services/sensitive-requests/owner-app-inline-adapter.d.ts.map +1 -0
  1206. package/services/sensitive-requests/owner-app-inline-adapter.js +146 -0
  1207. package/services/sensitive-requests/owner-app-oauth-adapter.d.ts +3 -0
  1208. package/services/sensitive-requests/owner-app-oauth-adapter.d.ts.map +1 -0
  1209. package/services/sensitive-requests/owner-app-oauth-adapter.js +156 -0
  1210. package/services/sensitive-requests/public-link-adapter.d.ts +14 -0
  1211. package/services/sensitive-requests/public-link-adapter.d.ts.map +1 -0
  1212. package/services/sensitive-requests/public-link-adapter.js +86 -0
  1213. package/services/sensitive-requests/tunnel-link-adapter.d.ts +17 -0
  1214. package/services/sensitive-requests/tunnel-link-adapter.d.ts.map +1 -0
  1215. package/services/sensitive-requests/tunnel-link-adapter.js +38 -0
  1216. package/services/steward-credentials.d.ts +1 -1
  1217. package/services/steward-credentials.d.ts.map +1 -1
  1218. package/services/steward-credentials.js +10 -6
  1219. package/services/steward-sidecar/health-check.d.ts.map +1 -1
  1220. package/services/steward-sidecar/health-check.js +4 -3
  1221. package/services/steward-sidecar/process-management.d.ts +1 -1
  1222. package/services/steward-sidecar/process-management.d.ts.map +1 -1
  1223. package/services/steward-sidecar/process-management.js +9 -3
  1224. package/services/steward-sidecar/types.d.ts +1 -1
  1225. package/services/steward-sidecar/types.d.ts.map +1 -1
  1226. package/services/steward-sidecar/wallet-setup.d.ts.map +1 -1
  1227. package/services/steward-sidecar/wallet-setup.js +8 -7
  1228. package/services/steward-sidecar.d.ts +2 -2
  1229. package/services/steward-sidecar.d.ts.map +1 -1
  1230. package/services/steward-sidecar.js +27 -19
  1231. package/services/task-host-capabilities.d.ts +60 -0
  1232. package/services/task-host-capabilities.d.ts.map +1 -0
  1233. package/services/task-host-capabilities.js +122 -0
  1234. package/services/tool-call-cache/index.d.ts +2 -2
  1235. package/services/tool-call-cache/index.d.ts.map +1 -1
  1236. package/services/tool-call-cache/index.js +1 -1
  1237. package/services/trigger-event-bridge.js +1 -1
  1238. package/services/tunnel-to-mobile/index.d.ts +2 -0
  1239. package/services/tunnel-to-mobile/index.d.ts.map +1 -0
  1240. package/services/tunnel-to-mobile/index.js +1 -0
  1241. package/services/tunnel-to-mobile/tunnel-to-mobile-client.d.ts +105 -0
  1242. package/services/tunnel-to-mobile/tunnel-to-mobile-client.d.ts.map +1 -0
  1243. package/services/tunnel-to-mobile/tunnel-to-mobile-client.js +190 -0
  1244. package/services/vault-bootstrap.d.ts.map +1 -1
  1245. package/services/vault-bootstrap.js +48 -21
  1246. package/services/vault-mirror.d.ts +1 -1
  1247. package/services/vault-mirror.d.ts.map +1 -1
  1248. package/services/vault-mirror.js +29 -6
  1249. package/services/voice-profiles/diarization-pipeline.d.ts +6 -0
  1250. package/services/voice-profiles/diarization-pipeline.d.ts.map +1 -0
  1251. package/services/voice-profiles/diarization-pipeline.js +20 -0
  1252. package/services/voice-profiles/index.d.ts +12 -0
  1253. package/services/voice-profiles/index.d.ts.map +1 -0
  1254. package/services/voice-profiles/index.js +5 -0
  1255. package/services/voice-profiles/nickname-evaluator.d.ts +14 -0
  1256. package/services/voice-profiles/nickname-evaluator.d.ts.map +1 -0
  1257. package/services/voice-profiles/nickname-evaluator.js +46 -0
  1258. package/services/voice-profiles/owner-confidence.d.ts +10 -0
  1259. package/services/voice-profiles/owner-confidence.d.ts.map +1 -0
  1260. package/services/voice-profiles/owner-confidence.js +38 -0
  1261. package/services/voice-profiles/private-challenge.d.ts +20 -0
  1262. package/services/voice-profiles/private-challenge.d.ts.map +1 -0
  1263. package/services/voice-profiles/private-challenge.js +44 -0
  1264. package/services/voice-profiles/store.d.ts +21 -0
  1265. package/services/voice-profiles/store.d.ts.map +1 -0
  1266. package/services/voice-profiles/store.js +50 -0
  1267. package/services/voice-profiles/types.d.ts +38 -0
  1268. package/services/voice-profiles/types.d.ts.map +1 -0
  1269. package/services/voice-profiles/types.js +1 -0
  1270. package/styles/electrobun-mac-window-drag.css +4 -4
  1271. package/test/helpers/__tests__/live-agent-test.smoke.test.ts +43 -70
  1272. package/test/helpers/browser-mocks.ts +2 -2
  1273. package/test/helpers/conditional-tests.ts +2 -2
  1274. package/test/helpers/i18n.ts +1 -1
  1275. package/test/helpers/live-agent-test.ts +537 -551
  1276. package/test/helpers/live-provider.test.ts +4 -4
  1277. package/test/helpers/live-provider.ts +41 -7
  1278. package/test/helpers/live-runtime-server.ts +4 -4
  1279. package/test/helpers/pglite-runtime.ts +1 -1
  1280. package/test/helpers/real-runtime.ts +54 -15
  1281. package/test/helpers/trajectory-harness.ts +11 -7
  1282. package/test/scripts/start-eliza-live.ts +9 -0
  1283. package/test/scripts/test-parallel.mjs +1 -1
  1284. package/test/scripts/test-root-unit.mjs +6 -7
  1285. package/ui-compat.d.ts +13 -2
  1286. package/ui-compat.d.ts.map +1 -1
  1287. package/ui-compat.js +19 -3
  1288. package/api/auth-pairing-compat-routes.d.ts +0 -17
  1289. package/api/auth-pairing-compat-routes.d.ts.map +0 -1
  1290. package/api/auth-pairing-compat-routes.js +0 -301
  1291. package/api/local-inference-compat-routes.d.ts +0 -16
  1292. package/api/local-inference-compat-routes.d.ts.map +0 -1
  1293. package/api/local-inference-compat-routes.js +0 -617
  1294. package/api/onboarding-compat-routes.d.ts +0 -4
  1295. package/api/onboarding-compat-routes.d.ts.map +0 -1
  1296. package/api/onboarding-compat-routes.js +0 -207
  1297. package/api/plugins-compat-routes.d.ts +0 -103
  1298. package/api/plugins-compat-routes.d.ts.map +0 -1
  1299. package/api/plugins-compat-routes.js +0 -1181
  1300. package/api/server-onboarding-compat.d.ts +0 -31
  1301. package/api/server-onboarding-compat.d.ts.map +0 -1
  1302. package/api/server-onboarding-compat.js +0 -283
  1303. package/benchmark/cua-routes.d.ts +0 -10
  1304. package/benchmark/cua-routes.d.ts.map +0 -1
  1305. package/benchmark/cua-routes.js +0 -179
  1306. package/benchmark/mock-plugin-base.d.ts +0 -9
  1307. package/benchmark/mock-plugin-base.d.ts.map +0 -1
  1308. package/benchmark/mock-plugin-base.js +0 -325
  1309. package/cli/parse-duration.d.ts +0 -5
  1310. package/cli/parse-duration.d.ts.map +0 -1
  1311. package/cli/parse-duration.js +0 -27
  1312. package/patches/llama-cpp-capacitor@0.1.5.patch +0 -2387
  1313. package/platform/agent-browser-stub.d.ts +0 -27
  1314. package/platform/agent-browser-stub.d.ts.map +0 -1
  1315. package/platform/agent-browser-stub.js +0 -16
  1316. package/platforms/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +0 -26
  1317. package/platforms/android/app/src/main/res/drawable/ic_launcher_background.xml +0 -170
  1318. package/platforms/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -34
  1319. package/platforms/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +0 -18
  1320. package/platforms/electrobun/assets/appIcon.iconset/icon_512x512@2x.png +0 -0
  1321. package/platforms/electrobun/assets/appIcon.png +0 -0
  1322. package/platforms/electrobun/scripts/build-whisper-universal.sh +0 -137
  1323. package/platforms/electrobun/scripts/build-whisper.sh +0 -95
  1324. package/platforms/electrobun/src/libMacWindowEffects.dylib +0 -0
  1325. package/platforms/electrobun/src/native/whisper.ts +0 -280
  1326. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png +0 -0
  1327. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png +0 -0
  1328. package/platforms/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png +0 -0
  1329. package/registry/generate-apps.d.ts +0 -2
  1330. package/registry/generate-apps.d.ts.map +0 -1
  1331. package/registry/generate-apps.js +0 -338
  1332. package/registry/generate.d.ts +0 -2
  1333. package/registry/generate.d.ts.map +0 -1
  1334. package/registry/generate.js +0 -506
  1335. package/runtime/embedding-manager-support.d.ts +0 -77
  1336. package/runtime/embedding-manager-support.d.ts.map +0 -1
  1337. package/runtime/embedding-manager-support.js +0 -309
  1338. package/runtime/embedding-presets.d.ts +0 -5
  1339. package/runtime/embedding-presets.d.ts.map +0 -1
  1340. package/runtime/embedding-presets.js +0 -47
  1341. package/runtime/embedding-warmup-policy.d.ts +0 -13
  1342. package/runtime/embedding-warmup-policy.d.ts.map +0 -1
  1343. package/runtime/embedding-warmup-policy.js +0 -33
  1344. package/runtime/ensure-local-inference-handler.d.ts +0 -25
  1345. package/runtime/ensure-local-inference-handler.d.ts.map +0 -1
  1346. package/runtime/ensure-local-inference-handler.js +0 -389
  1347. package/runtime/mobile-local-inference-gate.d.ts +0 -21
  1348. package/runtime/mobile-local-inference-gate.d.ts.map +0 -1
  1349. package/runtime/mobile-local-inference-gate.js +0 -24
  1350. package/scripts/aosp/avd-test.mjs +0 -403
  1351. package/scripts/aosp/boot-validate.mjs +0 -536
  1352. package/scripts/aosp/build-aosp.mjs +0 -448
  1353. package/scripts/aosp/build-bootanimation.mjs +0 -178
  1354. package/scripts/aosp/capture-screens.mjs +0 -325
  1355. package/scripts/aosp/e2e-validate.mjs +0 -225
  1356. package/scripts/aosp/lint-init-rc.mjs +0 -258
  1357. package/scripts/aosp/llama-shim/eliza_llama_shim.c +0 -276
  1358. package/scripts/aosp/sim.mjs +0 -277
  1359. package/scripts/aosp/sync-to-aosp.mjs +0 -134
  1360. package/scripts/aosp/validate.mjs +0 -1273
  1361. package/scripts/build-llama-cpp-dflash.mjs +0 -1866
  1362. package/scripts/generate-onboarding-voicelines.mjs +0 -194
  1363. package/scripts/generated/static-asset-manifest.json +0 -4
  1364. package/scripts/normalize-parallax-capture.ts +0 -97
  1365. package/scripts/omnivoice-fuse/Makefile +0 -44
  1366. package/scripts/omnivoice-fuse/README.md +0 -266
  1367. package/scripts/omnivoice-fuse/cmake-graft.mjs +0 -180
  1368. package/scripts/omnivoice-fuse/ffi-stub.c +0 -222
  1369. package/scripts/omnivoice-fuse/ffi.h +0 -158
  1370. package/scripts/omnivoice-fuse/libelizainference_stub.dylib +0 -0
  1371. package/scripts/omnivoice-fuse/verify-symbols.mjs +0 -138
  1372. package/security/cloud-secret-store.d.ts +0 -34
  1373. package/security/cloud-secret-store.d.ts.map +0 -1
  1374. package/security/cloud-secret-store.js +0 -65
  1375. package/security/export-guard.d.ts +0 -34
  1376. package/security/export-guard.d.ts.map +0 -1
  1377. package/security/export-guard.js +0 -127
  1378. package/services/local-inference/__stress__/cache-stress-helpers.d.ts +0 -76
  1379. package/services/local-inference/__stress__/cache-stress-helpers.d.ts.map +0 -1
  1380. package/services/local-inference/__stress__/cache-stress-helpers.js +0 -238
  1381. package/services/local-inference/active-model.d.ts +0 -180
  1382. package/services/local-inference/active-model.d.ts.map +0 -1
  1383. package/services/local-inference/active-model.js +0 -362
  1384. package/services/local-inference/assignments.d.ts +0 -58
  1385. package/services/local-inference/assignments.d.ts.map +0 -1
  1386. package/services/local-inference/assignments.js +0 -179
  1387. package/services/local-inference/backend.d.ts +0 -200
  1388. package/services/local-inference/backend.d.ts.map +0 -1
  1389. package/services/local-inference/backend.js +0 -242
  1390. package/services/local-inference/bundled-models.d.ts +0 -34
  1391. package/services/local-inference/bundled-models.d.ts.map +0 -1
  1392. package/services/local-inference/bundled-models.js +0 -104
  1393. package/services/local-inference/cache-bridge.d.ts +0 -184
  1394. package/services/local-inference/cache-bridge.d.ts.map +0 -1
  1395. package/services/local-inference/cache-bridge.js +0 -333
  1396. package/services/local-inference/catalog.d.ts +0 -57
  1397. package/services/local-inference/catalog.d.ts.map +0 -1
  1398. package/services/local-inference/catalog.js +0 -262
  1399. package/services/local-inference/conversation-registry.d.ts +0 -122
  1400. package/services/local-inference/conversation-registry.d.ts.map +0 -1
  1401. package/services/local-inference/conversation-registry.js +0 -182
  1402. package/services/local-inference/device-bridge.d.ts +0 -139
  1403. package/services/local-inference/device-bridge.d.ts.map +0 -1
  1404. package/services/local-inference/device-bridge.js +0 -774
  1405. package/services/local-inference/dflash-doctor.d.ts +0 -27
  1406. package/services/local-inference/dflash-doctor.d.ts.map +0 -1
  1407. package/services/local-inference/dflash-doctor.js +0 -149
  1408. package/services/local-inference/dflash-server.d.ts +0 -248
  1409. package/services/local-inference/dflash-server.d.ts.map +0 -1
  1410. package/services/local-inference/dflash-server.js +0 -1076
  1411. package/services/local-inference/downloader.d.ts +0 -48
  1412. package/services/local-inference/downloader.d.ts.map +0 -1
  1413. package/services/local-inference/downloader.js +0 -688
  1414. package/services/local-inference/engine.d.ts +0 -282
  1415. package/services/local-inference/engine.d.ts.map +0 -1
  1416. package/services/local-inference/engine.js +0 -743
  1417. package/services/local-inference/external-scanner.d.ts +0 -17
  1418. package/services/local-inference/external-scanner.d.ts.map +0 -1
  1419. package/services/local-inference/external-scanner.js +0 -261
  1420. package/services/local-inference/handler-registry.d.ts +0 -72
  1421. package/services/local-inference/handler-registry.d.ts.map +0 -1
  1422. package/services/local-inference/handler-registry.js +0 -159
  1423. package/services/local-inference/hardware.d.ts +0 -26
  1424. package/services/local-inference/hardware.d.ts.map +0 -1
  1425. package/services/local-inference/hardware.js +0 -139
  1426. package/services/local-inference/hf-search.d.ts +0 -19
  1427. package/services/local-inference/hf-search.d.ts.map +0 -1
  1428. package/services/local-inference/hf-search.js +0 -169
  1429. package/services/local-inference/index.d.ts +0 -10
  1430. package/services/local-inference/index.d.ts.map +0 -1
  1431. package/services/local-inference/index.js +0 -7
  1432. package/services/local-inference/llama-server-metrics.d.ts +0 -108
  1433. package/services/local-inference/llama-server-metrics.d.ts.map +0 -1
  1434. package/services/local-inference/llama-server-metrics.js +0 -175
  1435. package/services/local-inference/manifest/index.d.ts +0 -4
  1436. package/services/local-inference/manifest/index.d.ts.map +0 -1
  1437. package/services/local-inference/manifest/index.js +0 -5
  1438. package/services/local-inference/manifest/schema.d.ts +0 -419
  1439. package/services/local-inference/manifest/schema.d.ts.map +0 -1
  1440. package/services/local-inference/manifest/schema.js +0 -227
  1441. package/services/local-inference/manifest/types.d.ts +0 -23
  1442. package/services/local-inference/manifest/types.d.ts.map +0 -1
  1443. package/services/local-inference/manifest/types.js +0 -5
  1444. package/services/local-inference/manifest/validator.d.ts +0 -43
  1445. package/services/local-inference/manifest/validator.d.ts.map +0 -1
  1446. package/services/local-inference/manifest/validator.js +0 -180
  1447. package/services/local-inference/paths.d.ts +0 -8
  1448. package/services/local-inference/paths.d.ts.map +0 -1
  1449. package/services/local-inference/paths.js +0 -7
  1450. package/services/local-inference/providers.d.ts +0 -61
  1451. package/services/local-inference/providers.d.ts.map +0 -1
  1452. package/services/local-inference/providers.js +0 -334
  1453. package/services/local-inference/ram-budget.d.ts +0 -57
  1454. package/services/local-inference/ram-budget.d.ts.map +0 -1
  1455. package/services/local-inference/ram-budget.js +0 -107
  1456. package/services/local-inference/readiness.d.ts +0 -9
  1457. package/services/local-inference/readiness.d.ts.map +0 -1
  1458. package/services/local-inference/readiness.js +0 -153
  1459. package/services/local-inference/recommendation.d.ts +0 -62
  1460. package/services/local-inference/recommendation.d.ts.map +0 -1
  1461. package/services/local-inference/recommendation.js +0 -309
  1462. package/services/local-inference/registry.d.ts +0 -35
  1463. package/services/local-inference/registry.d.ts.map +0 -1
  1464. package/services/local-inference/registry.js +0 -117
  1465. package/services/local-inference/router-handler.d.ts +0 -51
  1466. package/services/local-inference/router-handler.d.ts.map +0 -1
  1467. package/services/local-inference/router-handler.js +0 -165
  1468. package/services/local-inference/routing-policy.d.ts +0 -55
  1469. package/services/local-inference/routing-policy.d.ts.map +0 -1
  1470. package/services/local-inference/routing-policy.js +0 -195
  1471. package/services/local-inference/routing-preferences.d.ts +0 -8
  1472. package/services/local-inference/routing-preferences.d.ts.map +0 -1
  1473. package/services/local-inference/routing-preferences.js +0 -7
  1474. package/services/local-inference/service.d.ts +0 -88
  1475. package/services/local-inference/service.d.ts.map +0 -1
  1476. package/services/local-inference/service.js +0 -210
  1477. package/services/local-inference/session-pool.d.ts +0 -72
  1478. package/services/local-inference/session-pool.d.ts.map +0 -1
  1479. package/services/local-inference/session-pool.js +0 -125
  1480. package/services/local-inference/types.d.ts +0 -309
  1481. package/services/local-inference/types.d.ts.map +0 -1
  1482. package/services/local-inference/types.js +0 -23
  1483. package/services/local-inference/verify.d.ts +0 -8
  1484. package/services/local-inference/verify.d.ts.map +0 -1
  1485. package/services/local-inference/verify.js +0 -7
  1486. package/services/local-inference/voice/barge-in.d.ts +0 -15
  1487. package/services/local-inference/voice/barge-in.d.ts.map +0 -1
  1488. package/services/local-inference/voice/barge-in.js +0 -20
  1489. package/services/local-inference/voice/engine-bridge.d.ts +0 -256
  1490. package/services/local-inference/voice/engine-bridge.d.ts.map +0 -1
  1491. package/services/local-inference/voice/engine-bridge.js +0 -398
  1492. package/services/local-inference/voice/ffi-bindings.d.ts +0 -114
  1493. package/services/local-inference/voice/ffi-bindings.d.ts.map +0 -1
  1494. package/services/local-inference/voice/ffi-bindings.js +0 -281
  1495. package/services/local-inference/voice/index.d.ts +0 -51
  1496. package/services/local-inference/voice/index.d.ts.map +0 -1
  1497. package/services/local-inference/voice/index.js +0 -50
  1498. package/services/local-inference/voice/lifecycle.d.ts +0 -135
  1499. package/services/local-inference/voice/lifecycle.d.ts.map +0 -1
  1500. package/services/local-inference/voice/lifecycle.js +0 -189
  1501. package/services/local-inference/voice/phoneme-tokenizer.d.ts +0 -58
  1502. package/services/local-inference/voice/phoneme-tokenizer.d.ts.map +0 -1
  1503. package/services/local-inference/voice/phoneme-tokenizer.js +0 -53
  1504. package/services/local-inference/voice/phrase-cache.d.ts +0 -24
  1505. package/services/local-inference/voice/phrase-cache.d.ts.map +0 -1
  1506. package/services/local-inference/voice/phrase-cache.js +0 -32
  1507. package/services/local-inference/voice/phrase-chunker.d.ts +0 -20
  1508. package/services/local-inference/voice/phrase-chunker.d.ts.map +0 -1
  1509. package/services/local-inference/voice/phrase-chunker.js +0 -85
  1510. package/services/local-inference/voice/ring-buffer.d.ts +0 -40
  1511. package/services/local-inference/voice/ring-buffer.d.ts.map +0 -1
  1512. package/services/local-inference/voice/ring-buffer.js +0 -85
  1513. package/services/local-inference/voice/rollback-queue.d.ts +0 -24
  1514. package/services/local-inference/voice/rollback-queue.d.ts.map +0 -1
  1515. package/services/local-inference/voice/rollback-queue.js +0 -49
  1516. package/services/local-inference/voice/scheduler.d.ts +0 -47
  1517. package/services/local-inference/voice/scheduler.d.ts.map +0 -1
  1518. package/services/local-inference/voice/scheduler.js +0 -123
  1519. package/services/local-inference/voice/shared-resources.d.ts +0 -119
  1520. package/services/local-inference/voice/shared-resources.d.ts.map +0 -1
  1521. package/services/local-inference/voice/shared-resources.js +0 -83
  1522. package/services/local-inference/voice/speaker-preset-cache.d.ts +0 -28
  1523. package/services/local-inference/voice/speaker-preset-cache.d.ts.map +0 -1
  1524. package/services/local-inference/voice/speaker-preset-cache.js +0 -44
  1525. package/services/local-inference/voice/types.d.ts +0 -80
  1526. package/services/local-inference/voice/types.d.ts.map +0 -1
  1527. package/services/local-inference/voice/voice-preset-format.d.ts +0 -56
  1528. package/services/local-inference/voice/voice-preset-format.d.ts.map +0 -1
  1529. package/services/local-inference/voice/voice-preset-format.js +0 -184
  1530. package/services/plugin-installer.d.ts +0 -22
  1531. package/services/plugin-installer.d.ts.map +0 -1
  1532. package/services/plugin-installer.js +0 -41
  1533. package/test/scripts/task-agent-live-smoke.ts +0 -1335
  1534. /package/services/{local-inference/voice → ambient-audio}/types.js +0 -0
@@ -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
  }