fix(linux): single-line description and correct desktop.entry for valid .desktop file (#325)

This commit is contained in:
paisley
2026-03-06 16:22:25 +08:00
committed by GitHub
Unverified
parent e7d4cf73d5
commit 3ce4b5d17a
5 changed files with 62 additions and 9 deletions

View File

@@ -436,6 +436,14 @@ export class GatewayManager extends EventEmitter {
logger.debug('No existing Gateway found, starting new process...');
// On Windows, TCP TIME_WAIT can hold the port for up to 2 minutes
// after the previous Gateway process exits, preventing the new one
// from binding. Wait for the port to be free before proceeding.
if (process.platform === 'win32') {
await this.waitForPortFree(this.status.port);
this.assertLifecycleEpoch(startEpoch, 'start/wait-port');
}
// Start new Gateway process
await this.startProcess();
this.assertLifecycleEpoch(startEpoch, 'start/start-process');
@@ -1015,6 +1023,45 @@ export class GatewayManager extends EventEmitter {
* Start Gateway process
* Uses OpenClaw npm package from node_modules (dev) or resources (production)
*/
/**
* Wait until the gateway port is no longer held by the OS.
* On Windows, TCP TIME_WAIT can keep a port occupied for up to 2 minutes
* after the owning process exits, causing the new Gateway to hang on bind.
*/
private async waitForPortFree(port: number, timeoutMs = 30000): Promise<void> {
const net = await import('net');
const start = Date.now();
const pollInterval = 500;
let logged = false;
while (Date.now() - start < timeoutMs) {
const available = await new Promise<boolean>((resolve) => {
const server = net.createServer();
server.once('error', () => resolve(false));
server.once('listening', () => {
server.close(() => resolve(true));
});
server.listen(port, '127.0.0.1');
});
if (available) {
const elapsed = Date.now() - start;
if (elapsed > pollInterval) {
logger.info(`Port ${port} became available after ${elapsed}ms`);
}
return;
}
if (!logged) {
logger.info(`Waiting for port ${port} to become available (Windows TCP TIME_WAIT)...`);
logged = true;
}
await new Promise(r => setTimeout(r, pollInterval));
}
logger.warn(`Port ${port} still occupied after ${timeoutMs}ms, proceeding anyway`);
}
private async startProcess(): Promise<void> {
// Ensure no system-managed gateway service will compete with our process.
await this.unloadLaunchctlService();

View File

@@ -46,11 +46,16 @@ export type DeferredRestartAction = 'none' | 'wait' | 'drop' | 'execute';
/**
* Decide what to do with a pending deferred restart once lifecycle changes.
*
* A deferred restart is an explicit restart() call that was postponed because
* the manager was mid-startup/reconnect. When the in-flight operation settles
* we must honour the request — even if the gateway is now running — because
* the caller may have changed config (e.g. provider switch) that the current
* process hasn't picked up.
*/
export function getDeferredRestartAction(context: DeferredRestartActionContext): DeferredRestartAction {
if (!context.hasPendingRestart) return 'none';
if (shouldDeferRestart(context)) return 'wait';
if (!context.shouldReconnect) return 'drop';
if (context.state === 'running') return 'drop';
return 'execute';
}

View File

@@ -995,10 +995,14 @@ function registerDeviceOAuthHandlers(mainWindow: BrowserWindow): void {
* Provider-related IPC handlers
*/
function registerProviderHandlers(gatewayManager: GatewayManager): void {
// Listen for OAuth success to automatically restart the Gateway with new tokens/configs
// Listen for OAuth success to automatically restart the Gateway with new tokens/configs.
// Use a longer debounce (8s) so that provider:setDefault — which writes the full config
// and then calls debouncedRestart(2s) — has time to fire and coalesce into a single
// restart. Without this, the OAuth restart fires first with stale config, and the
// subsequent provider:setDefault restart is deferred and dropped.
deviceOAuthManager.on('oauth:success', (providerType) => {
logger.info(`[IPC] Scheduling Gateway restart after ${providerType} OAuth success...`);
gatewayManager.debouncedRestart();
gatewayManager.debouncedRestart(8000);
});
// Get all providers with key info