9.2 KiB
qiming-agent_diff Windows CMD 窗口问题验证报告
测试环境
- 版本: qiming-agent_diff (tag: win-v0.1.2, v0.1.26)
- mcp-proxy 版本: 0.1.37 → 0.1.39
- 测试日志:
/Users/apple/Downloads/qiming-agent.log.2026-02-12
测试结果 ❌
问题 1: CMD 窗口仍然弹出
预期: 所有服务启动时不弹出 CMD 窗口
实际: CMD 窗口仍然弹出
状态: ❌ 未解决
问题 2: mcp-proxy 启动失败
预期: mcp-proxy 正常启动并通过健康检查
实际: 健康检查超时(15秒后仍未就绪)
状态: ❌ 未解决
问题 3: Node.js 重复安装
预期: 检测到已安装的 Node.js,直接使用
实际: 重复尝试安装 10 次后才使用自动安装
状态: ❌ 未解决
代码验证
qiming-agent_diff 中的实现 ✅ 代码正确
文件: crates/qiming-agent-core/src/service/mod.rs
// Windows 下正确使用了 CreationFlags + JobObject
#[cfg(target_os = "windows")]
let child = cmd
.wrap(process_wrap::tokio::KillOnDrop)
.wrap(CreationFlags(CREATE_NO_WINDOW | DETACHED_PROCESS)) // ✅ 正确
.wrap(JobObject) // ✅ 正确
.spawn()?;
应用位置:
- ✅
run_command_with_timeout()- 通用命令执行 - ✅
file_server_start()- 文件服务器启动 - ✅
lanproxy_start()- LAN 代理启动 - ✅
mcp_proxy_start()- MCP 代理启动
Cargo.toml 特性:
process-wrap = { version = "9", features = ["tokio1", "process-group", "job-object"] }
✅ 已正确添加 job-object 特性
为什么代码正确但仍然弹出窗口?
可能原因 1: .cmd 批处理文件本身的问题 ⭐ 最可能
分析:
Windows 上的 npm 全局包会生成 .cmd 包装文件:
C:\Users\MECHREVO\.local\bin\
├── mcp-proxy.cmd ← 批处理文件
├── qiming-file-server.cmd
└── ...
问题: 即使父进程设置了 CREATE_NO_WINDOW,.cmd 文件启动时仍然会:
- 启动
cmd.exe解释器 cmd.exe可能会忽略父进程的窗口标志.cmd内部再启动 Node.js 进程
层级关系:
qiming-agent.exe (设置 CREATE_NO_WINDOW)
└─ cmd.exe (解释 .cmd 文件) ← 可能弹窗!
└─ node.exe (实际的服务)
验证方法:
- 直接运行
.cmd文件看是否弹窗:
# 测试 1: 直接运行
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
# 测试 2: 通过 Rust 程序运行(设置 CREATE_NO_WINDOW)
# 如果仍然弹窗,说明问题在 .cmd 文件本身
- 查看
.cmd文件内容:
type C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
可能原因 2: process-wrap 的 JobObject 实现问题
分析:
process-wrap 的 JobObject 可能没有正确传递 CREATE_NO_WINDOW 标志给子进程。
验证代码:
// 当前实现
.wrap(CreationFlags(CREATE_NO_WINDOW | DETACHED_PROCESS))
.wrap(JobObject)
// 可能的问题:JobObject 重新创建了进程,覆盖了 CreationFlags?
解决方案: 检查 process-wrap 的源码,确认 JobObject 是否保留了 CreationFlags。
可能原因 3: Windows 版本或系统设置
某些 Windows 版本或系统设置可能忽略 CREATE_NO_WINDOW 标志。
验证:
# 检查 Windows 版本
systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
# 检查是否有组策略限制
gpedit.msc
解决方案建议
方案 A: 绕过 .cmd 文件,直接调用 Node.js ⭐ 推荐
原理: 不使用 .cmd 包装文件,直接调用 node.exe
实现:
// 修改前
let bin_path = "C:\\Users\\...\\mcp-proxy.cmd";
let mut cmd = CommandWrap::with_new(&bin_path, |cmd| {
// ...
});
// 修改后
let node_exe = "C:\\Users\\...\\node.exe";
let mcp_proxy_js = "C:\\Users\\...\\node_modules\\mcp-stdio-proxy\\dist\\index.js";
let mut cmd = CommandWrap::with_new(node_exe, |cmd| {
cmd.arg(mcp_proxy_js) // 直接调用 JS 文件
.env("PATH", ...)
// ...
});
#[cfg(windows)]
let child = cmd
.wrap(CreationFlags(CREATE_NO_WINDOW | DETACHED_PROCESS))
.wrap(JobObject)
.spawn()?;
优点:
- ✅ 完全绕过
.cmd文件 - ✅ 更可控的进程启动
- ✅ 更清晰的参数传递
缺点:
- ⚠️ 需要找到
.cmd对应的.js文件路径 - ⚠️ 需要处理 npm 包的路径解析
方案 B: 使用 conhost.exe 隐藏控制台
原理: 使用 Windows 的 conhost.exe 工具隐藏控制台窗口
实现:
#[cfg(windows)]
fn run_hidden(cmd_path: &str, args: &[&str]) -> Result<Child> {
use std::process::{Command, Stdio};
use std::os::windows::process::CommandExt;
// 方法 1: 使用 CREATE_NO_WINDOW + STARTUPINFOEXW
const CREATE_NO_WINDOW: u32 = 0x08000000;
const DETACHED_PROCESS: u32 = 0x00000008;
let mut cmd = Command::new(cmd_path);
cmd.args(args);
cmd.creation_flags(CREATE_NO_WINDOW | DETACHED_PROCESS);
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::null());
// 方法 2: 使用 wscript 启动(更隐蔽)
// let mut cmd = Command::new("wscript.exe");
// cmd.arg("//B") // Batch mode (no UI)
// .arg("//NoLogo")
// .arg(vbs_script_path); // 需要生成 VBS 脚本
cmd.spawn()
}
方案 C: 打包成 Windows 服务或使用隐藏启动器
原理: 将服务打包成 Windows 服务或使用专门的隐藏启动器
实现:
- Windows 服务:
// 使用 windows-service crate
// 将 qiming-agent 注册为 Windows 服务
- 隐藏启动器:
// 创建一个小的 C++ 启动器
// 使用 ShellExecuteEx + SW_HIDE
调试步骤
1. 验证 .cmd 文件是否是根本原因
# 查看 mcp-proxy.cmd 内容
type C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
# 尝试直接运行 node.exe(而不是 .cmd)
C:\Users\MECHREVO\.local\bin\node.exe C:\Users\MECHREVO\.local\bin\node_modules\mcp-stdio-proxy\dist\index.js --help
2. 使用 Process Explorer 查看进程树
下载 Process Explorer: https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer
启动 qiming-agent 后,查看进程树:
qiming-agent.exe
└─ cmd.exe ← 如果看到这个,说明 .cmd 文件启动了 cmd.exe
└─ conhost.exe ← 控制台宿主
└─ node.exe
3. 添加详细日志
修改 service/mod.rs,添加日志:
#[cfg(windows)]
{
tracing::info!("Windows: 设置 CREATE_NO_WINDOW | DETACHED_PROCESS");
tracing::info!("命令路径: {}", bin_path);
tracing::info!("是否是 .cmd 文件: {}", bin_path.ends_with(".cmd"));
}
测试 mcp-proxy v0.1.39 的改进
根据 mcp-proxy 的更新日志,v0.1.39 添加了:
- ✅ 环境变量日志配置支持
- ✅ 增强的启动日志
- ✅ stderr 输出关键信息
验证:
# 设置日志目录
$env:MCP_PROXY_LOG_DIR = "C:\Users\MECHREVO\AppData\Roaming\qiming-agent\logs\mcp-proxy"
$env:MCP_PROXY_LOG_LEVEL = "debug"
$env:MCP_PROXY_PORT = "18099"
# 手动启动 mcp-proxy
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
# 查看日志
type C:\Users\MECHREVO\AppData\Roaming\qiming-agent\logs\mcp-proxy\log.2026-02-12
Node.js 检测问题修复
日志显示的问题:
[resolve_node_bin] npm -> fallback to PATH # 10 次失败
[NodeInstall] 开始自动安装 Node.js... # 最终触发安装
根本原因: resolve_node_bin 没有真正验证 npm 命令是否可用
修复方案 (已在 mcp-proxy 仓库的分析文档中提供):
async fn is_nodejs_available() -> bool {
tokio::process::Command::new("node")
.arg("--version")
.status()
.await
.map(|s| s.success())
.unwrap_or(false)
}
async fn ensure_npm_package(package_name: &str) -> Result<()> {
// 1. 先检查 Node.js 是否可用
if !is_nodejs_available().await {
install_nodejs().await?;
}
// 2. 检查包是否已安装
if is_npm_package_installed(package_name).await? {
return Ok(());
}
// 3. 安装包(带重试限制)
// ...
}
总结
✅ qiming-agent_diff 代码是正确的
代码层面的修改是正确的:
- ✅ 使用了
CreationFlags(CREATE_NO_WINDOW | DETACHED_PROCESS) - ✅ 使用了
JobObject管理进程 - ✅ 添加了
process-group和job-object特性
❌ 但仍然弹出 CMD 窗口
最可能的原因: .cmd 批处理文件本身启动了 cmd.exe,导致窗口弹出
推荐的解决方案:
- ⭐ 方案 A: 绕过
.cmd文件,直接调用node.exe - 🔧 方案 B: 验证 process-wrap 的 JobObject 实现
- 📊 调试: 使用 Process Explorer 查看进程树
📋 下一步行动
- 立即: 查看
.cmd文件内容,确认是否是根本原因 - 验证: 尝试直接调用
node.exe而不是.cmd - 测试: 使用 Process Explorer 查看进程树
- 应用: 如果方案 A 有效,修改 qiming-agent 代码
📝 相关文档
- mcp-proxy LOG_CONFIGURATION.md - 日志配置指南
- QIMING_AGENT_WINDOWS_CMD_FIX.md - CMD 窗口修复方案
- MCP_PROXY_STARTUP_FAILURE_ANALYSIS.md - 启动失败分析