# nodeagent 实战复盘:短板、瓶颈与改进优先级 > ⚠️ **本文写于 2026-09-28(当时 19 项能力),多数结论已过时。** > 2026-10-07 逐项核查:P0 全部解决、P1 仅「定时采样」未做、P2 已补大半。 > **当前状态与下一步路线以 [`ROADMAP.md`](./ROADMAP.md) 为准**,本文保留作为「问题从哪来」的历史记录。 > 基于 2026-09-27 ~ 09-28 的真实使用(Windows 真机部署 + 代理客户端故障排查 + 重装前信息采集) > 所有问题均来自**实际踩坑**,非理论推演。 --- ## 一、最痛的问题:GUI 语义能力缺失(P0) ### 场景还原 排查代理客户端节点问题时,AI 需要**切换活动节点**: | 尝试 | 结果 | |---|---| | 算坐标点击第 2 行 | ❌ 点到第 1 行 | | 重新算坐标再点 | ❌ 又点到第 3 行 | | 改用键盘 `↑`/`↓`/`Enter` | ✅ 成功 | **问题本质**:AI 有 `screen.capture`(能**看**)和 `input.mouse.*`(能**点**),但**没有把「看到的界面元素」映射为「可点击坐标」的能力**。 每次都要:截图 → 人眼估算像素 → 换算缩放的 region 偏移 → 点击 → 再截图验证。**误差极大,反复试错**。 ### 改进方向(按性价比排序) 1. **UI 元素定位能力**(最关键) - `screen.find(text)` / `screen.find(image)`:返回元素坐标 - Windows 侧可用 **UI Automation (UIA)** —— 原生支持,能直接枚举窗口控件树 - 实现成本中等,收益极高 2. **截图附带坐标网格** - `screen.capture` 可选叠加坐标标尺(每 100px 标线) - AI 看图即可精确推算坐标,无需盲试 3. **鼠标相对定位** - `input.mouse.move` 支持 `--relative dx dy`(当前只有绝对坐标) 4. **窗口管理** - `window.list` / `window.focus(title)` / `window.rect(title)` - 先聚焦目标窗口并拿到精确矩形,再在窗口内相对定位 —— **比全屏算坐标可靠得多** --- ## 二、编码与跨平台:反复踩的坑(P0) ### 三个真实事故 | # | 事故 | 根因 | 后果 | |---|---|---|---| | 1 | agent 装了却起不来 | PowerShell `Set-Content -Encoding UTF8` **写入了 BOM** | `JSON.parse` 崩,排查半天 | | 2 | 软件清单中文全变 `�����` | PowerShell → shell **管道损坏 UTF-8** | 清单报废,重采一次 | | 3 | 终端输出中文乱码 | Windows 控制台默认 GBK | 多次误判 | ### 改进方向 1. **能力层统一 UTF-8**(在 Windows 侧强制) - `system.shell.exec` 应**自动注入**: ```powershell [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new() $OutputEncoding = [Console]::OutputEncoding ``` - 从根上解决 2、3 2. **新增 `fs.write_text` / `fs.read_text`**(明确 UTF-8 无 BOM 语义) - 或者给 `fs.write` 增加 `encoding: 'utf8' | 'utf8-bom' | 'gbk'` 参数 - 让「写配置文件」这类高频操作**默认安全** 3. **结构化返回**(不靠文本传递) - 现在 `exec` 的输出是**纯文本 + 尾部附加 `[退出码 0 · 410ms]`**,导致 AI 解析 JSON 时被污染 - 应改为**结构化字段**:`{ stdout, stderr, exit_code, duration_ms }` —— 后端本来就有,只是 CLI 拼接成文本了 --- ## 三、进程与生命周期:自断生路(P0) ### 场景还原 AI 用 `system.shell.exec` 执行「杀旧 agent + 启新 agent」来让配置生效: ``` Start-Process powershell -ArgumentList '... Start-Sleep 3; Stop-Process ...; Start-Process node.exe ...' ``` **结果**:**分离的 PowerShell 被 agent 的进程树清理逻辑一起杀掉了** → 新 agent 从未启动 → 端口僵死 → **AI 彻底失联**,只能请用户手动恢复。 ### 改进方向 1. **受控重启能力**(最高优先) ``` system.agent.restart { delay_ms: 2000 } ``` - 由 agent **自己**实现:fork 一个「延时重启器」(脱离进程组),或注册一次性计划任务 - 重启后**主动回连/上报**,让控制端知道「我回来了」 - 这样 90% 的配置变更新场景都不再需要用户介入 2. **健康检查与自愈** - agent 内置看门狗:若 N 秒内未响应,自我重启 - 启动失败时**自动回滚**到上一份可用配置(配合配置快照) 3. **分离进程的明确语义** - 提供 `spawn` 能力(明确「不随命令结束而终止」),而不是让用户用 `Start-Process` 碰运气 - 或者:`exec` 增加 `detach: true` 参数 4. **改端口/改配置前先"预演"** - 支持用一个临时配置在**旁路端口**试启动,成功再切换 —— 这正是这次出事后 AI 总结的正确做法 --- ## 四、可观测性与长时任务(P1) ### 场景还原 - 代理不通问题**反复发作**,但 AI 只能看**当下快照**(`netstat`、`Get-Process`),**无法看到 5 分钟前的状态** - 想跑「持续 5 分钟、每 2 秒记录一次连接状态」的采样 → **`exec` 默认 30 秒超时**,长命令直接被砍 - 排查第三方杀软拦截时,需要「退出杀软前后对比」→ **AI 无法安排这个时序** ### 改进方向 1. **长任务异步化** ``` system.shell.exec { command, timeout_ms, async: true } → 返回 task_id system.task.get { task_id } / system.task.log { task_id } / system.task.kill ``` - 现在超时只能靠调用方抬 `timeout_ms`,且**命令一旦超时整条被杀**,无法续接 2. **定时采样能力** ``` monitor.start { interval_ms, command | metric, duration_ms } → 落盘 monitor.report { id } ``` - 专治「间歇性问题」—— 这次代理不通就是典型的间歇性/条件性问题 3. **事件订阅(push 模型)** - 现在全是**拉取式**(AI 问一次,答一次) - 应该支持:`subscribe { events: ['process.crash','net.change','file.change'] }` - 让被控端**主动推**异常,而不是等 AI 去查 4. **日志聚合查询** - 这次查代理客户端日志,AI 只能 `Get-Content -Tail N` 手工翻 - 应提供 `log.query { path, since, level, pattern, limit }`,由被控端侧做高效过滤 --- ## 五、能力缺口清单(P1~P2) | 缺口 | 场景影响 | 优先级 | |---|---|---| | **剪贴板读写**(`clip.get`/`clip.set`) | 传文本要绕大圈(写文件→读文件) | **P1** | | **窗口管理**(列举/聚焦/移动) | GUI 操作的基础设施 | **P1** | | **文件监控**(`fs.watch`) | 等某个文件生成、监控配置变更 | P2 | | **截屏区域智能裁剪** | 现在要手工算 region | P2 | | **录屏 / 连续帧** | 动态过程(如安装向导)无法观察 | P2 | | **键盘组合键序列**(宏) | 现在 `key press` 一次只能一种组合 | P2 | | **鼠标拖拽**(drag & drop) | 只支持 move/click/scroll | P2 | | **`app.list` 的 winget 补充** | 与 PRD 描述不符(只有注册表+商店) | P2 | | **`app.install` 的静默安装降级** | winget 之外无兜底(PRD T2 未闭环) | P2 | | **并发 RPC** | WS 单连接串行,批量操作慢 | P2 | | **多设备并行执行** | 现在只能 `--node` 串行切换 | P2 | --- ## 六、性能观察(实测数据) | 操作 | 实测 | 评价 | |---|---|---| | 31MB 文件传输 | **1.66 秒**(~19MB/s) | ✅ 够用 | | 截屏 1720×720 | ~1-2 秒 | ⚠️ base64 传输有开销 | | 列出 178 款软件 | ~9 秒(含 winget 调用) | ⚠️ 偏慢 | | 单次 `exec` 往返 | 300~600ms | ⚠️ 每次重建连接的代价 | | 连接建立(wss 握手) | ~1-2 秒 | ⚠️ CLI 每次调用都重连 | **瓶颈判断**: - **最慢的是"每次调用重建连接"** —— CLI 模式无连接复用 - 建议:**CLI 增加常驻 daemon 模式**(`nodeagent serve` 本地跑一个,命令走本地 socket 转发) --- ## 七、体验问题 | 问题 | 具体表现 | |---|---| | **CLI 参数记忆成本高** | `--args` 的 JSON 转义极易出错(这次反复失败) | | **错误信息不够可操作** | `E_CAPABILITY_DISABLED: 输入控制已禁用` —— 没说「怎么开」 | | **无交互式会话** | 每次命令独立,无法「进入某个上下文后连续操作」 | | **日志与结果混在一起** | agent 的日志和命令输出同流,污染数据 | | **`key press` 多键限制** | `key press up up up` 报 `E_PARAM_INVALID`,只能循环调用 | | **审计日志无查询便利** | 能做 `audit` 但无过滤/聚合 | --- ## 八、安全方面(已有基础,待加固) | 项 | 现状 | 建议 | |---|---|---| | 密钥存储 | 明文落盘(600 权限) | **DPAPI**(Windows)/ Keychain(macOS) | | 审计完整性 | JSONL 追加 | 增加**链式哈希**防篡改 | | 调用取消 | ❌ 无 | 长任务应支持 cancel | | 能力授权 | ✅ 能力级 ACL(做得不错) | 可细化到「路径级」「命令白名单级」 | | 速率限制 | ✅ 有 | 可增加「异常行为告警」 | --- ## 九、优先级建议(如果要排期) ### P0 —— 直接决定"能不能用起来" | # | 改进 | 理由 | |---|---|---| | 1 | **UI 元素定位(UIA)** | GUI 操作从"反复试错"变"一次命中",这次最痛 | | 2 | **受控重启 + 自愈** | 从根上消灭「自断生路」,配置变更不再需要人工 | | 3 | **编码统一(UTF-8 全链路)** | 三个事故都由此而来,且每次排查都耗时 | | 4 | **exec 结构化返回** | 消除输出污染,是所有自动化的基础 | ### P1 —— 决定"用得顺不顺" | # | 改进 | |---|---| | 5 | 长任务异步化(`async: true` + task_id) | | 6 | 定时采样 / 监控(治间歇性问题) | | 7 | 剪贴板读写 | | 8 | 窗口管理 | | 9 | CLI 常驻 daemon(连接复用,提速) | ### P2 —— 决定"用得爽不爽" 10. 事件订阅(push 模型) 11. 文件监控 / 录屏 12. 日志聚合查询 13. 鼠标拖拽 / 键盘宏 14. 多设备并行 15. DPAPI 密钥保护 --- ## 十、一句话总结 **nodeagent 在「能看、能算、能跑命令」这条线上已经相当扎实** —— 19 项能力、真实文件传输、审计闭环都经得起实战。 **它现在的真正短板集中在三个字:`语义` 与 `状态`。** - **语义**:看得到画面,但读不懂界面(缺 UI 元素理解) - **状态**:只看得到当下,看不到变化(缺长时观测) - **自持**:改得动别人,改不动自己(缺自愈与受控重启) 把这三块补上,它就从「远程命令行工具」进化为「**真正的远程操作代理**」。 --- ## 更新:2026-10-10 —— 本文档列出的短板已全部解决 > 保留上文原判断(复盘写于 2026-09-28,19 项能力时期),此处只补「后来怎么解决的」, > 不改写历史结论 —— 复盘的价值在于能看到当时的判断与后来的差距。 | 复盘时的短板 | 解决于 | 现在长什么样 | |---|---|---| | **自持**:无法受控重启、更新要人上手 | v7 / v19 / v2.0.0 | `system.agent.restart`(借计划任务,**非管理员身份自动降级**)+ `system.agent.update`(拉取式,校验 sha256) | | **语义**:只能「找到」,读不懂状态、操作后无法断言 | v1.6.0 / v2.0.0 | `gui.await` 等待条件(四类条件 × 出现/消失 + `any_of` 组合);`screen.find` 返回 **UIA 属性**(`enabled/value/selected/toggle`)并支持 `where` 谓词 —— 从「盲试」到「可断言」 | | **状态**:只有当下快照,间歇性问题无法回看 | v2.0.0 | `log.query` **在被控端侧过滤**日志;`monitor.*` 定时采样落盘 + 摘要回看(断了几次 / 最长断多久 / CPU·内存值域) | | **安全残余**(SECURITY §5) | v2.0.0 | 握手失败封禁(指数退避)· 连接上限与空闲断开 · 安装期密钥走 stdin · **零信任优先安装**(有公钥即 ed25519)· **审计链外部锚定**(防整链重写) | **当前规模**:51 项能力 · 269 单测 + 30 端到端 · Windows CI 13 步全绿。 > 一条经验(本轮反复验证):**真机验证不可替代**。10-09 ~ 10-10 的真机验证一次性抓出 > 8 个只有真机能暴露的 bug,其中最严重的两个(`install.cmd` 首装即炸、`where` 的 PS 注入 > 语法错)在 macOS 上 100% 测不出来。详见 `ROADMAP.md` §九 的 bug 表。