mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-26 06:22:22 +02:00
[eric] windows: lazy backend boot, render in parallel with python cold-start (exp.2)
This commit is contained in:
+34
-4
@@ -447,7 +447,7 @@ async function pickBackendPort() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startBackend() {
|
async function startBackend() {
|
||||||
backendPort = await pickBackendPort();
|
if (!backendPort) backendPort = await pickBackendPort();
|
||||||
|
|
||||||
const pythonPath = getPythonPath();
|
const pythonPath = getPythonPath();
|
||||||
const backendDir = getResourcePath('backend');
|
const backendDir = getResourcePath('backend');
|
||||||
@@ -581,6 +581,7 @@ async function startBackend() {
|
|||||||
// any browser on the machine could hit our localhost API and
|
// any browser on the machine could hit our localhost API and
|
||||||
// impersonate the user. See backend/auth.py.
|
// impersonate the user. See backend/auth.py.
|
||||||
await loadAuthToken();
|
await loadAuthToken();
|
||||||
|
markBackendReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-install auth token read from <data-root>/auth.token (backend
|
// Per-install auth token read from <data-root>/auth.token (backend
|
||||||
@@ -589,6 +590,16 @@ async function startBackend() {
|
|||||||
// retry a few times.
|
// retry a few times.
|
||||||
let authToken = '';
|
let authToken = '';
|
||||||
|
|
||||||
|
// Lazy-backend gate: renderer fetches block on this until backend is reachable AND auth token is loaded. Lets the main window open immediately while Python is still cold-starting on Windows.
|
||||||
|
let backendReady = false;
|
||||||
|
let _backendReadyResolve;
|
||||||
|
const backendReadyPromise = new Promise((resolve) => { _backendReadyResolve = resolve; });
|
||||||
|
function markBackendReady() {
|
||||||
|
if (backendReady) return;
|
||||||
|
backendReady = true;
|
||||||
|
_backendReadyResolve();
|
||||||
|
}
|
||||||
|
|
||||||
function getAuthTokenFilePath() {
|
function getAuthTokenFilePath() {
|
||||||
// Mirrors backend/config/paths.py. On macOS the file lives at
|
// Mirrors backend/config/paths.py. On macOS the file lives at
|
||||||
// ~/Library/Application Support/OpenSwarm/data/auth.token; on
|
// ~/Library/Application Support/OpenSwarm/data/auth.token; on
|
||||||
@@ -900,8 +911,14 @@ app.whenReady().then(async () => {
|
|||||||
backendPort = parseInt(process.env.OPENSWARM_PORT || '8324', 10);
|
backendPort = parseInt(process.env.OPENSWARM_PORT || '8324', 10);
|
||||||
console.log(`Dev mode: using existing backend on port ${backendPort}`);
|
console.log(`Dev mode: using existing backend on port ${backendPort}`);
|
||||||
emitSplashStatus('Connecting to dev backend…');
|
emitSplashStatus('Connecting to dev backend…');
|
||||||
|
markBackendReady();
|
||||||
} else {
|
} else {
|
||||||
await startBackend();
|
// Kick off backend without awaiting so the window can paint while Python is still cold-starting. Renderer fetches lazy-await markBackendReady() via the get-auth-token IPC; splash status updates still fire from inside startBackend.
|
||||||
|
backendPort = await pickBackendPort();
|
||||||
|
const _backendBoot = startBackend().catch((err) => {
|
||||||
|
console.error('[boot] backend startup failed:', err && err.message);
|
||||||
|
emitSplashStatus({ text: 'Backend failed to start', level: 'error', logs: recentBackendStderr.slice(-20).join('') });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
emitSplashStatus('Almost ready…');
|
emitSplashStatus('Almost ready…');
|
||||||
createWindow();
|
createWindow();
|
||||||
@@ -919,9 +936,14 @@ app.whenReady().then(async () => {
|
|||||||
// Swap splash → main only once React has actually painted. ready-to-show
|
// Swap splash → main only once React has actually painted. ready-to-show
|
||||||
// fires after the renderer's first frame, eliminating the white-flash
|
// fires after the renderer's first frame, eliminating the white-flash
|
||||||
// that would otherwise pop between splash close and React mount.
|
// that would otherwise pop between splash close and React mount.
|
||||||
|
// Also gated on backendReady: with lazy backend, ready-to-show can fire while React is still showing null (SignInGateLoader returns null until settings load), so we'd show a blank window if we swapped early.
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
const swapToMain = () => {
|
const swapToMain = () => {
|
||||||
if (mainWindowReady || mainWindow.isDestroyed()) return;
|
if (mainWindowReady || mainWindow.isDestroyed()) return;
|
||||||
|
if (!backendReady) {
|
||||||
|
backendReadyPromise.then(() => swapToMain()).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
mainWindowReady = true;
|
mainWindowReady = true;
|
||||||
try { mainWindow.show(); mainWindow.focus(); } catch (_) {}
|
try { mainWindow.show(); mainWindow.focus(); } catch (_) {}
|
||||||
// Tiny delay so the OS gets a chance to bring main to front
|
// Tiny delay so the OS gets a chance to bring main to front
|
||||||
@@ -941,7 +963,13 @@ app.whenReady().then(async () => {
|
|||||||
// sees the load error in the window itself.
|
// sees the load error in the window itself.
|
||||||
mainWindow.webContents.once('did-fail-load', (_e, errorCode, errorDescription, validatedURL) => {
|
mainWindow.webContents.once('did-fail-load', (_e, errorCode, errorDescription, validatedURL) => {
|
||||||
console.warn('[boot] mainWindow load failed:', errorCode, errorDescription, validatedURL);
|
console.warn('[boot] mainWindow load failed:', errorCode, errorDescription, validatedURL);
|
||||||
if (isDev) swapToMain();
|
if (isDev) {
|
||||||
|
// Force-skip the backend gate so dev sees the error.
|
||||||
|
mainWindowReady = true;
|
||||||
|
try { mainWindow.show(); mainWindow.focus(); } catch (_) {}
|
||||||
|
if (splashWindow && !splashWindow.isDestroyed()) splashWindow.destroy();
|
||||||
|
splashWindow = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1294,7 +1322,9 @@ ipcMain.on('splash:action', (_event, action) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('get-backend-port', () => backendPort);
|
ipcMain.handle('get-backend-port', () => backendPort);
|
||||||
ipcMain.handle('get-auth-token', () => {
|
ipcMain.handle('get-auth-token', async () => {
|
||||||
|
// Wait for backend if it's still cold-starting; this is the lazy-backend gate that lets the window open while Python is warming up.
|
||||||
|
if (!backendReady) await backendReadyPromise;
|
||||||
// Re-read the file every time. The backend rotates the token on each
|
// Re-read the file every time. The backend rotates the token on each
|
||||||
// start, and during dev hot-reload the cached value could go stale
|
// start, and during dev hot-reload the cached value could go stale
|
||||||
// while the renderer stays alive. Re-reading is cheap (small file,
|
// while the renderer stays alive. Re-reading is cheap (small file,
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "openswarm",
|
"name": "openswarm",
|
||||||
"version": "1.1.41-exp.1",
|
"version": "1.1.40-exp.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "openswarm",
|
"name": "openswarm",
|
||||||
"version": "1.1.41-exp.1",
|
"version": "1.1.40-exp.2",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-updater": "^6.3.0",
|
"electron-updater": "^6.3.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openswarm",
|
"name": "openswarm",
|
||||||
"version": "1.1.41-exp.1",
|
"version": "1.1.40-exp.2",
|
||||||
"description": "OpenSwarm — AI Agent Orchestrator",
|
"description": "OpenSwarm — AI Agent Orchestrator",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user