Files
qiming/qimingclaw/docs/WINDOWS_CMD_WINDOW_VERIFICATION_REPORT.md

366 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`
```rust
// 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 特性**:
```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` 文件看是否弹窗:
```powershell
# 测试 1: 直接运行
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
# 测试 2: 通过 Rust 程序运行(设置 CREATE_NO_WINDOW
# 如果仍然弹窗,说明问题在 .cmd 文件本身
```
2. 查看 `.cmd` 文件内容:
```powershell
type C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
```
---
### 可能原因 2: process-wrap 的 JobObject 实现问题
**分析**:
`process-wrap``JobObject` 可能没有正确传递 `CREATE_NO_WINDOW` 标志给子进程。
**验证代码**:
```rust
// 当前实现
.wrap(CreationFlags(CREATE_NO_WINDOW | DETACHED_PROCESS))
.wrap(JobObject)
// 可能的问题JobObject 重新创建了进程,覆盖了 CreationFlags
```
**解决方案**: 检查 process-wrap 的源码,确认 JobObject 是否保留了 CreationFlags。
---
### 可能原因 3: Windows 版本或系统设置
某些 Windows 版本或系统设置可能忽略 `CREATE_NO_WINDOW` 标志。
**验证**:
```powershell
# 检查 Windows 版本
systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
# 检查是否有组策略限制
gpedit.msc
```
---
## 解决方案建议
### 方案 A: 绕过 `.cmd` 文件,直接调用 Node.js ⭐ 推荐
**原理**: 不使用 `.cmd` 包装文件,直接调用 node.exe
**实现**:
```rust
// 修改前
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` 工具隐藏控制台窗口
**实现**:
```rust
#[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 服务**:
```rust
// 使用 windows-service crate
// 将 qiming-agent 注册为 Windows 服务
```
2. **隐藏启动器**:
```rust
// 创建一个小的 C++ 启动器
// 使用 ShellExecuteEx + SW_HIDE
```
---
## 调试步骤
### 1. 验证 `.cmd` 文件是否是根本原因
```powershell
# 查看 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`,添加日志:
```rust
#[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 输出关键信息
**验证**:
```powershell
# 设置日志目录
$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 仓库的分析文档中提供):
```rust
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`,导致窗口弹出
**推荐的解决方案**:
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 - 启动失败分析