()
| 1149 | }; |
| 1150 | |
| 1151 | const boot = async () => { |
| 1152 | installDockIcon(); |
| 1153 | installApplicationMenu(); |
| 1154 | await showStartupWindow(); |
| 1155 | setupAutoUpdater(); |
| 1156 | applyFakeUpdateFromEnv(); |
| 1157 | registerIpcHandlers(); |
| 1158 | // A sidecar that dies under a live window would leave the web UI failing |
| 1159 | // every request with no explanation. Swap in the crash screen — its |
| 1160 | // buttons drive the regular preload bridge (restart / export diagnostics). |
| 1161 | onUnexpectedSidecarExit(() => { |
| 1162 | showCrashScreen(); |
| 1163 | // A crashing sidecar may be a broken release — quietly stage any |
| 1164 | // available update so the install prompt appears on its own (same |
| 1165 | // self-heal as the fatal startup path). |
| 1166 | void runUpdateCheck({ alertOnFail: false, trigger: "boot" }); |
| 1167 | }); |
| 1168 | // Prefer an OS-supervised daemon: attach to one that's running, kick one |
| 1169 | // that's installed, or offer to install on first run. Quitting the app then |
| 1170 | // leaves MCP serving. This is also the clean handoff that replaces the old |
| 1171 | // "another server owns the data dir → fatal error" path. Packaged builds only; |
| 1172 | // dev and unsupported platforms keep managed-spawn. |
| 1173 | if (app.isPackaged) { |
| 1174 | const supervised = await ensureSupervisedConnection(); |
| 1175 | if (supervised) { |
| 1176 | connection = supervised; |
| 1177 | // createWindow installs the bearer-auth header itself. |
| 1178 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: supervised attach can race with daemon shutdown; fall back to managed spawn |
| 1179 | try { |
| 1180 | await createWindow(supervised); |
| 1181 | armSupervisedMonitor(); |
| 1182 | void runUpdateCheck({ alertOnFail: false, trigger: "boot" }); |
| 1183 | return; |
| 1184 | } catch (error) { |
| 1185 | log.warn("Failed to load supervised daemon; falling back to managed sidecar", error); |
| 1186 | stopSupervisedMonitor(); |
| 1187 | connection = null; |
| 1188 | } |
| 1189 | } |
| 1190 | } |
| 1191 | connection = await startWithCurrentSettings(); |
| 1192 | if (!connection && lastSidecarStartError != null) { |
| 1193 | // Port conflicts already showed their dialog inside |
| 1194 | // startWithCurrentSettings; every other failure surfaces here so the app |
| 1195 | // never silently bounces-and-vanishes. The dialog offers a data reset |
| 1196 | // (move-aside, for damaged state) — when taken, retry the boot once |
| 1197 | // against the fresh dir. |
| 1198 | const retryAfterReset = await handleFatalSidecarFailure(lastSidecarStartError); |
| 1199 | if (retryAfterReset) { |
| 1200 | lastSidecarStartError = null; |
| 1201 | connection = await startWithCurrentSettings(); |
| 1202 | if (!connection && lastSidecarStartError != null) { |
| 1203 | await handleFatalSidecarFailure(lastSidecarStartError); |
| 1204 | } |
| 1205 | } |
| 1206 | } |
| 1207 | if (!connection) { |
| 1208 | app.quit(); |
no test coverage detected