committed by
GitHub
Unverified
parent
3d804a9f5e
commit
2c5c82bb74
106
electron/gateway/startup-orchestrator.ts
Normal file
106
electron/gateway/startup-orchestrator.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { logger } from '../utils/logger';
|
||||
import { LifecycleSupersededError } from './lifecycle-controller';
|
||||
import { getGatewayStartupRecoveryAction } from './startup-recovery';
|
||||
|
||||
export interface ExistingGatewayInfo {
|
||||
port: number;
|
||||
externalToken?: string;
|
||||
}
|
||||
|
||||
type StartupHooks = {
|
||||
port: number;
|
||||
ownedPid?: number;
|
||||
shouldWaitForPortFree: boolean;
|
||||
maxStartAttempts?: number;
|
||||
resetStartupStderrLines: () => void;
|
||||
getStartupStderrLines: () => string[];
|
||||
assertLifecycle: (phase: string) => void;
|
||||
findExistingGateway: (port: number, ownedPid?: number) => Promise<ExistingGatewayInfo | null>;
|
||||
connect: (port: number, externalToken?: string) => Promise<void>;
|
||||
onConnectedToExistingGateway: () => void;
|
||||
waitForPortFree: (port: number) => Promise<void>;
|
||||
startProcess: () => Promise<void>;
|
||||
waitForReady: (port: number) => Promise<void>;
|
||||
onConnectedToManagedGateway: () => void;
|
||||
runDoctorRepair: () => Promise<boolean>;
|
||||
onDoctorRepairSuccess: () => void;
|
||||
delay: (ms: number) => Promise<void>;
|
||||
};
|
||||
|
||||
export async function runGatewayStartupSequence(hooks: StartupHooks): Promise<void> {
|
||||
let configRepairAttempted = false;
|
||||
let startAttempts = 0;
|
||||
const maxStartAttempts = hooks.maxStartAttempts ?? 3;
|
||||
|
||||
while (true) {
|
||||
startAttempts++;
|
||||
hooks.assertLifecycle('start');
|
||||
hooks.resetStartupStderrLines();
|
||||
|
||||
try {
|
||||
logger.debug('Checking for existing Gateway...');
|
||||
const existing = await hooks.findExistingGateway(hooks.port, hooks.ownedPid);
|
||||
hooks.assertLifecycle('start/find-existing');
|
||||
if (existing) {
|
||||
logger.debug(`Found existing Gateway on port ${existing.port}`);
|
||||
await hooks.connect(existing.port, existing.externalToken);
|
||||
hooks.assertLifecycle('start/connect-existing');
|
||||
hooks.onConnectedToExistingGateway();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug('No existing Gateway found, starting new process...');
|
||||
|
||||
if (hooks.shouldWaitForPortFree) {
|
||||
await hooks.waitForPortFree(hooks.port);
|
||||
hooks.assertLifecycle('start/wait-port');
|
||||
}
|
||||
|
||||
await hooks.startProcess();
|
||||
hooks.assertLifecycle('start/start-process');
|
||||
|
||||
await hooks.waitForReady(hooks.port);
|
||||
hooks.assertLifecycle('start/wait-ready');
|
||||
|
||||
await hooks.connect(hooks.port);
|
||||
hooks.assertLifecycle('start/connect');
|
||||
|
||||
hooks.onConnectedToManagedGateway();
|
||||
return;
|
||||
} catch (error) {
|
||||
if (error instanceof LifecycleSupersededError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const recoveryAction = getGatewayStartupRecoveryAction({
|
||||
startupError: error,
|
||||
startupStderrLines: hooks.getStartupStderrLines(),
|
||||
configRepairAttempted,
|
||||
attempt: startAttempts,
|
||||
maxAttempts: maxStartAttempts,
|
||||
});
|
||||
|
||||
if (recoveryAction === 'repair') {
|
||||
configRepairAttempted = true;
|
||||
logger.warn(
|
||||
'Detected invalid OpenClaw config during Gateway startup; running doctor repair before retry',
|
||||
);
|
||||
const repaired = await hooks.runDoctorRepair();
|
||||
if (repaired) {
|
||||
logger.info('OpenClaw doctor repair completed; retrying Gateway startup');
|
||||
hooks.onDoctorRepairSuccess();
|
||||
continue;
|
||||
}
|
||||
logger.error('OpenClaw doctor repair failed; not retrying Gateway startup');
|
||||
}
|
||||
|
||||
if (recoveryAction === 'retry') {
|
||||
logger.warn(`Transient start error: ${String(error)}. Retrying... (${startAttempts}/${maxStartAttempts})`);
|
||||
await hooks.delay(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user