mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-20 06:05:36 +00:00
fix(kaos): hide console window for spawned commands on Windows (#957)
- add windowsHide:true to spawn options so commands do not flash a console on Windows - extract buildLocalSpawnOptions helper shared by exec and execWithEnv - add regression coverage for the Windows console-window behavior
This commit is contained in:
parent
42237392dd
commit
b57fc905fe
3 changed files with 65 additions and 17 deletions
5
.changeset/fix-windows-console-flash.md
Normal file
5
.changeset/fix-windows-console-flash.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Fix commands flashing an empty console window on Windows.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ChildProcess } from 'node:child_process';
|
||||
import type { ChildProcess, SpawnOptions } from 'node:child_process';
|
||||
import { spawn } from 'node:child_process';
|
||||
import {
|
||||
appendFile,
|
||||
|
|
@ -37,6 +37,20 @@ function cycleKey(s: { dev: number; ino: number }): string | null {
|
|||
return `${String(s.dev)}:${String(s.ino)}`;
|
||||
}
|
||||
|
||||
export function buildLocalSpawnOptions(
|
||||
isWindows: boolean,
|
||||
cwd: string,
|
||||
env: Record<string, string> | undefined,
|
||||
): SpawnOptions {
|
||||
return {
|
||||
cwd,
|
||||
env,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
detached: !isWindows,
|
||||
windowsHide: true,
|
||||
};
|
||||
}
|
||||
|
||||
class LocalProcess implements KaosProcess {
|
||||
readonly stdin: Writable;
|
||||
readonly stdout: Readable;
|
||||
|
|
@ -544,16 +558,11 @@ export class LocalKaos implements Kaos {
|
|||
throw new Error('LocalKaos.exec(): at least one argument (the command to run) is required.');
|
||||
}
|
||||
const restArgs = args.slice(1);
|
||||
const child = spawn(command, restArgs, {
|
||||
cwd: this._cwd,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
// POSIX `detached:true` makes the child a process-group leader so
|
||||
// `LocalProcess.kill()` can signal the entire tree. No-op on Windows
|
||||
// (`taskkill /T` handles the tree there). We do not call `child.unref()`
|
||||
// because the parent still waits on the child's exit through `wait()`.
|
||||
detached: !isWindows,
|
||||
env: this._buildExecEnv(),
|
||||
});
|
||||
const child = spawn(
|
||||
command,
|
||||
restArgs,
|
||||
buildLocalSpawnOptions(isWindows, this._cwd, this._buildExecEnv()),
|
||||
);
|
||||
await waitForSpawn(child);
|
||||
return new LocalProcess(child);
|
||||
}
|
||||
|
|
@ -566,12 +575,11 @@ export class LocalKaos implements Kaos {
|
|||
);
|
||||
}
|
||||
const restArgs = args.slice(1);
|
||||
const child = spawn(command, restArgs, {
|
||||
cwd: this._cwd,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
detached: !isWindows,
|
||||
env: this._buildExecEnv(env),
|
||||
});
|
||||
const child = spawn(
|
||||
command,
|
||||
restArgs,
|
||||
buildLocalSpawnOptions(isWindows, this._cwd, this._buildExecEnv(env)),
|
||||
);
|
||||
await waitForSpawn(child);
|
||||
return new LocalProcess(child);
|
||||
}
|
||||
|
|
|
|||
35
packages/kaos/test/spawn-options.test.ts
Normal file
35
packages/kaos/test/spawn-options.test.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildLocalSpawnOptions } from '#/local';
|
||||
|
||||
// Regression coverage for the "every command pops an empty console window on
|
||||
// Windows" bug. `child_process.spawn` defaults `windowsHide` to `false`; on
|
||||
// Windows that makes Node allocate a *visible* console for each child process
|
||||
// the agent spawns through `BashTool` → `LocalKaos.exec`/`execWithEnv`. The
|
||||
// fix is to pass `windowsHide: true`. The flag is only observable on Windows,
|
||||
// so we assert the spawn options builder directly.
|
||||
|
||||
describe('buildLocalSpawnOptions (Windows console-window regression)', () => {
|
||||
it('sets windowsHide:true on Windows so commands do not flash a console', () => {
|
||||
const options = buildLocalSpawnOptions(true, 'C:\\repo', undefined);
|
||||
expect(options.windowsHide).toBe(true);
|
||||
});
|
||||
|
||||
it('sets windowsHide:true on POSIX too (it is ignored there, kept unconditional)', () => {
|
||||
const options = buildLocalSpawnOptions(false, '/repo', undefined);
|
||||
expect(options.windowsHide).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps detached platform-conditional (POSIX tree-kill vs Windows taskkill /T)', () => {
|
||||
expect(buildLocalSpawnOptions(true, 'C:\\repo', undefined).detached).toBe(false);
|
||||
expect(buildLocalSpawnOptions(false, '/repo', undefined).detached).toBe(true);
|
||||
});
|
||||
|
||||
it('pipes stdin/stdout/stderr and forwards cwd + env', () => {
|
||||
const env = { FOO: 'bar' };
|
||||
const options = buildLocalSpawnOptions(false, '/repo', env);
|
||||
expect(options.stdio).toEqual(['pipe', 'pipe', 'pipe']);
|
||||
expect(options.cwd).toBe('/repo');
|
||||
expect(options.env).toBe(env);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue