Files
qiming/qimingclaw/docs/WINDOWS_CMD_WINDOW_VERIFICATION_REPORT.md

9.2 KiB
Raw Permalink Blame History

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()?;

应用位置:

  1. run_command_with_timeout() - 通用命令执行
  2. file_server_start() - 文件服务器启动
  3. lanproxy_start() - LAN 代理启动
  4. 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 文件启动时仍然会:

  1. 启动 cmd.exe 解释器
  2. cmd.exe 可能会忽略父进程的窗口标志
  3. .cmd 内部再启动 Node.js 进程

层级关系:

qiming-agent.exe (设置 CREATE_NO_WINDOW)
  └─ cmd.exe (解释 .cmd 文件) ← 可能弹窗!
      └─ node.exe (实际的服务)

验证方法:

  1. 直接运行 .cmd 文件看是否弹窗:
# 测试 1: 直接运行
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd

# 测试 2: 通过 Rust 程序运行(设置 CREATE_NO_WINDOW
# 如果仍然弹窗,说明问题在 .cmd 文件本身
  1. 查看 .cmd 文件内容:
type C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd

可能原因 2: process-wrap 的 JobObject 实现问题

分析:

process-wrapJobObject 可能没有正确传递 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 服务或使用专门的隐藏启动器

实现:

  1. Windows 服务:
// 使用 windows-service crate
// 将 qiming-agent 注册为 Windows 服务
  1. 隐藏启动器:
// 创建一个小的 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 添加了:

  1. 环境变量日志配置支持
  2. 增强的启动日志
  3. 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-groupjob-object 特性

但仍然弹出 CMD 窗口

最可能的原因: .cmd 批处理文件本身启动了 cmd.exe,导致窗口弹出

推荐的解决方案:

  1. 方案 A: 绕过 .cmd 文件,直接调用 node.exe
  2. 🔧 方案 B: 验证 process-wrap 的 JobObject 实现
  3. 📊 调试: 使用 Process Explorer 查看进程树

📋 下一步行动

  1. 立即: 查看 .cmd 文件内容,确认是否是根本原因
  2. 验证: 尝试直接调用 node.exe 而不是 .cmd
  3. 测试: 使用 Process Explorer 查看进程树
  4. 应用: 如果方案 A 有效,修改 qiming-agent 代码

📝 相关文档

  • mcp-proxy LOG_CONFIGURATION.md - 日志配置指南
  • QIMING_AGENT_WINDOWS_CMD_FIX.md - CMD 窗口修复方案
  • MCP_PROXY_STARTUP_FAILURE_ANALYSIS.md - 启动失败分析