- Added telemetry utility to capture application events and metrics. - Integrated PostHog for event tracking with distinct user identification. - Implemented telemetry initialization, event capturing, and shutdown procedures. feat: add UV environment setup for Python management - Created utilities to manage Python installation and configuration. - Implemented network optimization checks for Python installation mirrors. - Added functions to set up managed Python environments with error handling. feat: enhance host API communication with token management - Introduced host API token retrieval and management for secure requests. - Updated host API fetch functions to include token in headers. - Added support for creating event sources with authentication. test: add comprehensive tests for gateway protocol and startup helpers - Implemented unit tests for gateway protocol helpers, event dispatching, and state management. - Added tests for startup recovery strategies and process policies. - Ensured coverage for connection monitoring and restart governance logic.
111 lines
3.8 KiB
TypeScript
111 lines
3.8 KiB
TypeScript
import logManager from '@electron/service/logger';
|
|
import { LifecycleSupersededError } from './lifecycle-controller';
|
|
import { getGatewayStartupRecoveryAction } from './startup-recovery';
|
|
|
|
export interface ExistingGatewayInfo {
|
|
port: number;
|
|
externalToken?: string;
|
|
}
|
|
|
|
type StartupHooks = {
|
|
port: number;
|
|
shouldWaitForPortFree: boolean;
|
|
maxStartAttempts?: number;
|
|
hasOwnedProcess?: () => boolean;
|
|
assertLifecycle?: (phase: string) => void;
|
|
resetStartupStderrLines: () => void;
|
|
getStartupStderrLines: () => string[];
|
|
findExistingGateway: (port: 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 += 1;
|
|
hooks.assertLifecycle?.('start');
|
|
hooks.resetStartupStderrLines();
|
|
|
|
try {
|
|
const existing = await hooks.findExistingGateway(hooks.port);
|
|
hooks.assertLifecycle?.('start/find-existing');
|
|
if (existing) {
|
|
logManager.debug(`Found existing Gateway on port ${existing.port}`);
|
|
await hooks.connect(existing.port, existing.externalToken);
|
|
hooks.assertLifecycle?.('start/connect-existing');
|
|
hooks.onConnectedToExistingGateway();
|
|
return;
|
|
}
|
|
|
|
if (hooks.hasOwnedProcess?.()) {
|
|
logManager.info('Owned Gateway process still alive; waiting for it to become ready');
|
|
await hooks.waitForReady(hooks.port);
|
|
hooks.assertLifecycle?.('start/wait-ready-owned');
|
|
await hooks.connect(hooks.port);
|
|
hooks.assertLifecycle?.('start/connect-owned');
|
|
hooks.onConnectedToExistingGateway();
|
|
return;
|
|
}
|
|
|
|
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' && hooks.runDoctorRepair) {
|
|
configRepairAttempted = true;
|
|
logManager.warn(
|
|
'Detected invalid OpenClaw config during Gateway startup; running doctor repair before retry',
|
|
);
|
|
const repaired = await hooks.runDoctorRepair();
|
|
if (repaired) {
|
|
logManager.info('OpenClaw doctor repair completed; retrying Gateway startup');
|
|
hooks.onDoctorRepairSuccess?.();
|
|
continue;
|
|
}
|
|
logManager.error('OpenClaw doctor repair failed; not retrying Gateway startup');
|
|
}
|
|
|
|
if (recoveryAction === 'retry') {
|
|
logManager.warn(`Transient start error: ${String(error)}. Retrying... (${startAttempts}/${maxStartAttempts})`);
|
|
await hooks.delay(1000);
|
|
continue;
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
}
|