/** * ProcessLauncher — spawn 本地子进程 * * start() 返回 { process, stdin, stdout, stderr } * 供 PipeTransport 绑定 stdin/stdout 通信 */ const { spawn } = require('child_process'); const { Launcher } = require('./Launcher'); class ProcessLauncher extends Launcher { constructor() { super(); this.child = null; } /** * @param {object} options * @param {string} options.command - 可执行命令 * @param {string[]} options.args - 参数列表 * @param {object} [options.env] - 环境变量 * @param {string} [options.cwd] - 工作目录 * @returns {Promise<{ process, stdin, stdout, stderr }>} */ async start(options = {}) { const { command, args = [], env, cwd } = options; const processEnv = { ...process.env, ...(env || {}) }; delete processEnv.NODE_OPTIONS; delete processEnv.ELECTRON_RUN_AS_NODE; delete processEnv.CLAUDECODE; console.log(`[ProcessLauncher] 启动: ${command} ${args.join(' ')}`); this.child = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'], env: processEnv, cwd, }); // 等待进程启动(简单延时,进程 error 会 reject) await new Promise((resolve, reject) => { this.child.on('error', reject); setTimeout(resolve, 300); }); return { process: this.child, stdin: this.child.stdin, stdout: this.child.stdout, stderr: this.child.stderr, }; } /** * 终止子进程并等待其完全退出 * * 关闭策略(确保 Node.js event loop 不被悬挂的子进程阻塞): * 1. 关闭 stdin(发送 EOF,给子进程优雅退出的机会) * 2. 发送 SIGTERM * 3. 超时后强制 SIGKILL * 4. 等待 exit 事件确保进程完全退出 * 5. 销毁 stdio 流,释放 event loop 引用 * * @param {number} [timeoutMs=5000] - SIGTERM 超时时间,超时后发送 SIGKILL */ async stop(timeoutMs = 5000) { if (!this.child) return; const child = this.child; this.child = null; // 已经退出,直接清理流 if (child.exitCode !== null || child.signalCode !== null) { this._destroyStreams(child); return; } return new Promise((resolve) => { // 超时后 SIGKILL 强杀 const forceKillTimer = setTimeout(() => { console.log('[ProcessLauncher] SIGTERM 超时,发送 SIGKILL'); try { child.kill('SIGKILL'); } catch { } }, timeoutMs); // 等待进程退出 child.once('exit', (code, signal) => { clearTimeout(forceKillTimer); this._destroyStreams(child); console.log(`[ProcessLauncher] 子进程已退出 (code=${code}, signal=${signal})`); resolve(); }); // 关闭 stdin,通知子进程 EOF try { child.stdin.end(); } catch { } // 发送 SIGTERM try { child.kill('SIGTERM'); } catch { } }); } /** * 销毁子进程的 stdio 流,释放 event loop 引用 * @private */ _destroyStreams(child) { for (const stream of [child.stdin, child.stdout, child.stderr]) { if (stream && !stream.destroyed) { try { stream.removeAllListeners(); stream.destroy(); } catch { } } } } } module.exports = { ProcessLauncher };