@elizaos/app-core 2.0.0-beta.3 → 2.0.3-beta.3

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