fix(gateway): prevent reconnect race and globally hide windows subprocess console
This commit is contained in:
@@ -106,6 +106,7 @@ export class ClawHubService {
|
||||
...env,
|
||||
CLAWHUB_WORKDIR: this.workDir,
|
||||
},
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
getReconnectSkipReason,
|
||||
isLifecycleSuperseded,
|
||||
nextLifecycleEpoch,
|
||||
shouldHideConsoleWindow,
|
||||
} from './process-policy';
|
||||
|
||||
/**
|
||||
@@ -709,7 +708,7 @@ export class GatewayManager extends EventEmitter {
|
||||
|
||||
const { stdout } = await new Promise<{ stdout: string }>((resolve, reject) => {
|
||||
import('child_process').then(cp => {
|
||||
cp.exec(cmd, { timeout: 5000, windowsHide: shouldHideConsoleWindow() }, (err, stdout) => {
|
||||
cp.exec(cmd, { timeout: 5000, windowsHide: true }, (err, stdout) => {
|
||||
if (err) resolve({ stdout: '' });
|
||||
else resolve({ stdout });
|
||||
});
|
||||
@@ -739,7 +738,7 @@ export class GatewayManager extends EventEmitter {
|
||||
import('child_process').then(cp => {
|
||||
cp.exec(
|
||||
`taskkill /PID ${pid} /T /F`,
|
||||
{ timeout: 5000, windowsHide: shouldHideConsoleWindow() },
|
||||
{ timeout: 5000, windowsHide: true },
|
||||
() => { }
|
||||
);
|
||||
}).catch(() => { });
|
||||
@@ -844,7 +843,7 @@ export class GatewayManager extends EventEmitter {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
shell: false,
|
||||
windowsHide: shouldHideConsoleWindow(),
|
||||
windowsHide: true,
|
||||
env: spawnEnv,
|
||||
});
|
||||
|
||||
@@ -1098,7 +1097,7 @@ export class GatewayManager extends EventEmitter {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
shell: useShell,
|
||||
windowsHide: shouldHideConsoleWindow(),
|
||||
windowsHide: true,
|
||||
env: spawnEnv,
|
||||
});
|
||||
const child = this.process;
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
export function shouldHideConsoleWindow(platform: NodeJS.Platform = process.platform): boolean {
|
||||
return platform === 'win32';
|
||||
}
|
||||
|
||||
export function nextLifecycleEpoch(currentEpoch: number): number {
|
||||
return currentEpoch + 1;
|
||||
}
|
||||
|
||||
@@ -526,6 +526,7 @@ export async function validateChannelConfig(channelType: string): Promise<Valida
|
||||
cwd: openclawPath,
|
||||
encoding: 'utf-8',
|
||||
timeout: 30000,
|
||||
windowsHide: true,
|
||||
},
|
||||
(err, stdout) => {
|
||||
if (err) reject(err);
|
||||
|
||||
@@ -269,6 +269,7 @@ export function generateCompletionCache(): void {
|
||||
},
|
||||
stdio: 'ignore',
|
||||
detached: false,
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
@@ -305,7 +306,8 @@ export function installCompletionToProfile(): void {
|
||||
},
|
||||
stdio: 'ignore',
|
||||
detached: false,
|
||||
},
|
||||
windowsHide: true,
|
||||
}
|
||||
);
|
||||
|
||||
child.on('close', (code) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ function getBundledUvPath(): string {
|
||||
const arch = process.arch;
|
||||
const target = `${platform}-${arch}`;
|
||||
const binName = platform === 'win32' ? 'uv.exe' : 'uv';
|
||||
|
||||
|
||||
if (app.isPackaged) {
|
||||
return join(process.resourcesPath, 'bin', binName);
|
||||
} else {
|
||||
@@ -53,7 +53,7 @@ function resolveUvBin(): { bin: string; source: 'bundled' | 'path' | 'bundled-fa
|
||||
function findUvInPathSync(): boolean {
|
||||
try {
|
||||
const cmd = process.platform === 'win32' ? 'where.exe uv' : 'which uv';
|
||||
execSync(cmd, { stdio: 'ignore', timeout: 5000 });
|
||||
execSync(cmd, { stdio: 'ignore', timeout: 5000, windowsHide: true });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -95,6 +95,7 @@ export async function isPythonReady(): Promise<boolean> {
|
||||
try {
|
||||
const child = spawn(useShell ? quoteForCmd(uvBin) : uvBin, ['python', 'find', '3.12'], {
|
||||
shell: useShell,
|
||||
windowsHide: true,
|
||||
});
|
||||
child.on('close', (code) => resolve(code === 0));
|
||||
child.on('error', () => resolve(false));
|
||||
@@ -121,6 +122,7 @@ async function runPythonInstall(
|
||||
const child = spawn(useShell ? quoteForCmd(uvBin) : uvBin, ['python', 'install', '3.12'], {
|
||||
shell: useShell,
|
||||
env,
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
child.stdout?.on('data', (data) => {
|
||||
@@ -210,12 +212,13 @@ export async function setupManagedPython(): Promise<void> {
|
||||
const child = spawn(verifyShell ? quoteForCmd(uvBin) : uvBin, ['python', 'find', '3.12'], {
|
||||
shell: verifyShell,
|
||||
env: { ...process.env, ...uvEnv },
|
||||
windowsHide: true,
|
||||
});
|
||||
let output = '';
|
||||
child.stdout?.on('data', (data) => { output += data; });
|
||||
child.on('close', () => resolve(output.trim()));
|
||||
});
|
||||
|
||||
|
||||
if (findPath) {
|
||||
logger.info(`Managed Python 3.12 installed at: ${findPath}`);
|
||||
}
|
||||
|
||||
@@ -3,21 +3,9 @@ import {
|
||||
getReconnectSkipReason,
|
||||
isLifecycleSuperseded,
|
||||
nextLifecycleEpoch,
|
||||
shouldHideConsoleWindow,
|
||||
} from '@electron/gateway/process-policy';
|
||||
|
||||
describe('gateway process policy helpers', () => {
|
||||
describe('shouldHideConsoleWindow', () => {
|
||||
it('returns true on Windows', () => {
|
||||
expect(shouldHideConsoleWindow('win32')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false on non-Windows platforms', () => {
|
||||
expect(shouldHideConsoleWindow('darwin')).toBe(false);
|
||||
expect(shouldHideConsoleWindow('linux')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lifecycle epoch helpers', () => {
|
||||
it('increments lifecycle epoch by one', () => {
|
||||
expect(nextLifecycleEpoch(0)).toBe(1);
|
||||
|
||||
Reference in New Issue
Block a user