()
| 1033 | }; |
| 1034 | |
| 1035 | const boot = async () => { |
| 1036 | installDockIcon(); |
| 1037 | installApplicationMenu(); |
| 1038 | setupAutoUpdater(); |
| 1039 | applyFakeUpdateFromEnv(); |
| 1040 | registerIpcHandlers(); |
| 1041 | // A sidecar that dies under a live window would leave the web UI failing |
| 1042 | // every request with no explanation. Swap in the crash screen — its |
| 1043 | // buttons drive the regular preload bridge (restart / export diagnostics). |
| 1044 | onUnexpectedSidecarExit(() => { |
| 1045 | const window = liveMainWindow(); |
| 1046 | if (!window) return; |
| 1047 | const html = sidecarCrashHtml({ reported: errorReportingEnabled }); |
| 1048 | void window.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`); |
| 1049 | // A crashing sidecar may be a broken release — quietly stage any |
| 1050 | // available update so the install prompt appears on its own (same |
| 1051 | // self-heal as the fatal startup path). |
| 1052 | void runUpdateCheck({ alertOnFail: false }); |
| 1053 | }); |
| 1054 | // Prefer an OS-supervised daemon: attach to one that's running, kick one |
| 1055 | // that's installed, or offer to install on first run. Quitting the app then |
| 1056 | // leaves MCP serving. This is also the clean handoff that replaces the old |
| 1057 | // "another server owns the data dir → fatal error" path. Packaged builds only; |
| 1058 | // dev and unsupported platforms keep managed-spawn. |
| 1059 | if (app.isPackaged) { |
| 1060 | const supervised = await ensureSupervisedConnection(); |
| 1061 | if (supervised) { |
| 1062 | connection = supervised; |
| 1063 | // createWindow installs the bearer-auth header itself. |
| 1064 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: supervised attach can race with daemon shutdown; fall back to managed spawn |
| 1065 | try { |
| 1066 | await createWindow(supervised); |
| 1067 | armSupervisedMonitor(); |
| 1068 | void runUpdateCheck({ alertOnFail: false }); |
| 1069 | return; |
| 1070 | } catch (error) { |
| 1071 | log.warn("Failed to load supervised daemon; falling back to managed sidecar", error); |
| 1072 | stopSupervisedMonitor(); |
| 1073 | connection = null; |
| 1074 | } |
| 1075 | } |
| 1076 | } |
| 1077 | connection = await startWithCurrentSettings(); |
| 1078 | if (!connection && lastSidecarStartError != null) { |
| 1079 | // Port conflicts already showed their dialog inside |
| 1080 | // startWithCurrentSettings; every other failure surfaces here so the app |
| 1081 | // never silently bounces-and-vanishes. The dialog offers a data reset |
| 1082 | // (move-aside, for damaged state) — when taken, retry the boot once |
| 1083 | // against the fresh dir. |
| 1084 | const retryAfterReset = await handleFatalSidecarFailure(lastSidecarStartError); |
| 1085 | if (retryAfterReset) { |
| 1086 | lastSidecarStartError = null; |
| 1087 | connection = await startWithCurrentSettings(); |
| 1088 | if (!connection && lastSidecarStartError != null) { |
| 1089 | await handleFatalSidecarFailure(lastSidecarStartError); |
| 1090 | } |
| 1091 | } |
| 1092 | } |
no test coverage detected