feat(settings): auto-set default provider after successful OAuth (#254)

This commit is contained in:
paisley
2026-03-02 15:13:55 +08:00
committed by GitHub
Unverified
parent 19406757f1
commit 29ef9591cf
2 changed files with 160 additions and 146 deletions

View File

@@ -678,38 +678,38 @@ export class GatewayManager extends EventEmitter {
if (pids.length > 0) {
if (!this.process || !pids.includes(String(this.process.pid))) {
logger.info(`Found orphaned process listening on port ${port} (PIDs: ${pids.join(', ')}), attempting to kill...`);
logger.info(`Found orphaned process listening on port ${port} (PIDs: ${pids.join(', ')}), attempting to kill...`);
// Unload the launchctl service first so macOS doesn't auto-
// respawn the process we're about to kill.
if (process.platform === 'darwin') {
await this.unloadLaunchctlService();
}
// Unload the launchctl service first so macOS doesn't auto-
// respawn the process we're about to kill.
if (process.platform === 'darwin') {
await this.unloadLaunchctlService();
}
// Terminate orphaned processes
for (const pid of pids) {
try {
if (process.platform === 'win32') {
// On Windows, use taskkill for reliable process group termination
import('child_process').then(cp => {
cp.exec(`taskkill /PID ${pid} /T /F`, { timeout: 5000 }, () => {});
}).catch(() => {});
} else {
// SIGTERM first so the gateway can clean up its lock file.
process.kill(parseInt(pid), 'SIGTERM');
}
} catch { /* ignore */ }
}
await new Promise(r => setTimeout(r, process.platform === 'win32' ? 2000 : 3000));
// Terminate orphaned processes
for (const pid of pids) {
try {
if (process.platform === 'win32') {
// On Windows, use taskkill for reliable process group termination
import('child_process').then(cp => {
cp.exec(`taskkill /PID ${pid} /T /F`, { timeout: 5000 }, () => { });
}).catch(() => { });
} else {
// SIGTERM first so the gateway can clean up its lock file.
process.kill(parseInt(pid), 'SIGTERM');
}
} catch { /* ignore */ }
}
await new Promise(r => setTimeout(r, process.platform === 'win32' ? 2000 : 3000));
// SIGKILL any survivors (Unix only — Windows taskkill /F is already forceful)
if (process.platform !== 'win32') {
for (const pid of pids) {
try { process.kill(parseInt(pid), 0); process.kill(parseInt(pid), 'SIGKILL'); } catch { /* already exited */ }
}
await new Promise(r => setTimeout(r, 1000));
}
return null;
// SIGKILL any survivors (Unix only — Windows taskkill /F is already forceful)
if (process.platform !== 'win32') {
for (const pid of pids) {
try { process.kill(parseInt(pid), 0); process.kill(parseInt(pid), 'SIGKILL'); } catch { /* already exited */ }
}
await new Promise(r => setTimeout(r, 1000));
}
return null;
}
}
}
@@ -785,7 +785,7 @@ export class GatewayManager extends EventEmitter {
spawnEnv['OPENCLAW_NO_RESPAWN'] = '1';
const existingNodeOpts = spawnEnv['NODE_OPTIONS'] ?? '';
if (!existingNodeOpts.includes('--disable-warning=ExperimentalWarning') &&
!existingNodeOpts.includes('--no-warnings')) {
!existingNodeOpts.includes('--no-warnings')) {
spawnEnv['NODE_OPTIONS'] = `${existingNodeOpts} --disable-warning=ExperimentalWarning`.trim();
}
}
@@ -1014,7 +1014,7 @@ export class GatewayManager extends EventEmitter {
// Pre-set the NODE_OPTIONS that entry.ts would have added via respawn
const existingNodeOpts = spawnEnv['NODE_OPTIONS'] ?? '';
if (!existingNodeOpts.includes('--disable-warning=ExperimentalWarning') &&
!existingNodeOpts.includes('--no-warnings')) {
!existingNodeOpts.includes('--no-warnings')) {
spawnEnv['NODE_OPTIONS'] = `${existingNodeOpts} --disable-warning=ExperimentalWarning`.trim();
}
}
@@ -1347,6 +1347,8 @@ export class GatewayManager extends EventEmitter {
const reasonStr = reason?.toString() || 'unknown';
logger.warn(`Gateway WebSocket closed (code=${code}, reason=${reasonStr}, handshake=${handshakeComplete ? 'ok' : 'pending'})`);
if (!handshakeComplete) {
// If the socket closes before the handshake completes, it usually means the server is still starting or restarting.
// Rejecting this promise will cause the caller (startProcess/reconnect logic) to retry cleanly.
rejectOnce(new Error(`WebSocket closed before handshake: ${reasonStr}`));
return;
}
@@ -1358,7 +1360,12 @@ export class GatewayManager extends EventEmitter {
});
this.ws.on('error', (error) => {
logger.error('Gateway WebSocket error:', error);
// Suppress noisy ECONNREFUSED/WebSocket handshake errors that happen during expected Gateway restarts.
if (error.message?.includes('closed before handshake') || (error as NodeJS.ErrnoException).code === 'ECONNREFUSED') {
logger.debug(`Gateway WebSocket connection error (transient): ${error.message}`);
} else {
logger.error('Gateway WebSocket error:', error);
}
if (!handshakeComplete) {
rejectOnce(error);
}

View File

@@ -516,7 +516,14 @@ function AddProviderDialog({ existingTypes, onClose, onAdd, onValidateKey }: Add
// If we call add() here with undefined baseUrl, it will overwrite and erase it!
// So we just fetch the latest list from the backend to update the UI.
try {
await useProviderStore.getState().fetchProviders();
const store = useProviderStore.getState();
await store.fetchProviders();
// Auto-set as default if no default is currently configured
if (!store.defaultProviderId && latestRef.current.selectedType) {
// Provider type is expected to match provider ID for built-in OAuth providers
await store.setDefaultProvider(latestRef.current.selectedType);
}
} catch (err) {
console.error('Failed to refresh providers after OAuth:', err);
}