把 GUI 操作从「按一下→睡几秒→截图碰运气」变成**可断言**:四类条件 window / control / process / file,各复用 window.list / screen.find / process.list / fs.stat(零新平台代码,平台行为与既有能力一致,只读低危)。 - present/absent 两种等待;超时正确返回 satisfied:false + note - 轮询期单次异常**不视为失败**(启动中程序常短暂报错),超时才带回 last_error - 参数非法分两层拒:schema 层(协议错误)与业务层(E_PARAM_INVALID) 本地真实验证(macOS,7 项全过):present 命中(1208ms/7 次轮询)、absent 命中、 超时返回、非法参数 2+1 拒绝、process 命中、底层报错不中断轮询(last_error 带回 辅助功能权限提示)、缺省参数可用。 能力数 42 → 43(数量守卫测试同步更新);README 补快捷键与 gui.await 用法章节。
248 lines
10 KiB
Markdown
248 lines
10 KiB
Markdown
# nodeagent 实战复盘:短板、瓶颈与改进优先级
|
||
|
||
> ⚠️ **本文写于 2026-09-28(当时 19 项能力),多数结论已过时。**
|
||
> 2026-10-07 逐项核查:P0 全部解决、P1 仅「定时采样」未做、P2 已补大半。
|
||
> **当前状态与下一步路线以 [`ROADMAP.md`](./ROADMAP.md) 为准**,本文保留作为「问题从哪来」的历史记录。
|
||
|
||
> 基于 2026-09-27 ~ 09-28 的真实使用(Windows 真机部署 + v2rayN 故障排查 + 重装前信息采集)
|
||
> 所有问题均来自**实际踩坑**,非理论推演。
|
||
|
||
---
|
||
|
||
## 一、最痛的问题:GUI 语义能力缺失(P0)
|
||
|
||
### 场景还原
|
||
排查 v2rayN 节点问题时,臣需要**切换活动节点**:
|
||
|
||
| 尝试 | 结果 |
|
||
|---|---|
|
||
| 算坐标点击第 2 行 | ❌ 点到第 1 行 |
|
||
| 重新算坐标再点 | ❌ 又点到第 3 行 |
|
||
| 改用键盘 `↑`/`↓`/`Enter` | ✅ 成功 |
|
||
|
||
**问题本质**:臣有 `screen.capture`(能**看**)和 `input.mouse.*`(能**点**),但**没有把「看到的界面元素」映射为「可点击坐标」的能力**。
|
||
|
||
每次都要:截图 → 人眼估算像素 → 换算缩放的 region 偏移 → 点击 → 再截图验证。**误差极大,反复试错**。
|
||
|
||
### 改进方向(按性价比排序)
|
||
|
||
1. **UI 元素定位能力**(最关键)
|
||
- `screen.find(text)` / `screen.find(image)`:返回元素坐标
|
||
- Windows 侧可用 **UI Automation (UIA)** —— 原生支持,能直接枚举窗口控件树
|
||
- 实现成本中等,收益极高
|
||
|
||
2. **截图附带坐标网格**
|
||
- `screen.capture` 可选叠加坐标标尺(每 100px 标线)
|
||
- 臣看图即可精确推算坐标,无需盲试
|
||
|
||
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]`**,导致臣解析 JSON 时被污染
|
||
- 应改为**结构化字段**:`{ stdout, stderr, exit_code, duration_ms }` —— 后端本来就有,只是 CLI 拼接成文本了
|
||
|
||
---
|
||
|
||
## 三、进程与生命周期:自断生路(P0)
|
||
|
||
### 场景还原
|
||
臣用 `system.shell.exec` 执行「杀旧 agent + 启新 agent」来让配置生效:
|
||
|
||
```
|
||
Start-Process powershell -ArgumentList '... Start-Sleep 3; Stop-Process ...; Start-Process node.exe ...'
|
||
```
|
||
|
||
**结果**:**分离的 PowerShell 被 agent 的进程树清理逻辑一起杀掉了** → 新 agent 从未启动 → 端口僵死 → **臣彻底失联**,只能请皇上手动恢复。
|
||
|
||
### 改进方向
|
||
|
||
1. **受控重启能力**(最高优先)
|
||
```
|
||
system.agent.restart { delay_ms: 2000 }
|
||
```
|
||
- 由 agent **自己**实现:fork 一个「延时重启器」(脱离进程组),或注册一次性计划任务
|
||
- 重启后**主动回连/上报**,让控制端知道「我回来了」
|
||
- 这样 90% 的配置变更新场景都不再需要皇上介入
|
||
|
||
2. **健康检查与自愈**
|
||
- agent 内置看门狗:若 N 秒内未响应,自我重启
|
||
- 启动失败时**自动回滚**到上一份可用配置(配合配置快照)
|
||
|
||
3. **分离进程的明确语义**
|
||
- 提供 `spawn` 能力(明确「不随命令结束而终止」),而不是让用户用 `Start-Process` 碰运气
|
||
- 或者:`exec` 增加 `detach: true` 参数
|
||
|
||
4. **改端口/改配置前先"预演"**
|
||
- 支持用一个临时配置在**旁路端口**试启动,成功再切换 —— 这正是这次出事后臣总结的正确做法
|
||
|
||
---
|
||
|
||
## 四、可观测性与长时任务(P1)
|
||
|
||
### 场景还原
|
||
- 代理不通问题**反复发作**,但臣只能看**当下快照**(`netstat`、`Get-Process`),**无法看到 5 分钟前的状态**
|
||
- 想跑「持续 5 分钟、每 2 秒记录一次连接状态」的采样 → **`exec` 默认 30 秒超时**,长命令直接被砍
|
||
- 排查 360 拦截时,需要「退出 360 前后对比」→ **臣无法安排这个时序**
|
||
|
||
### 改进方向
|
||
|
||
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 模型)**
|
||
- 现在全是**拉取式**(臣问一次,答一次)
|
||
- 应该支持:`subscribe { events: ['process.crash','net.change','file.change'] }`
|
||
- 让被控端**主动推**异常,而不是等臣去查
|
||
|
||
4. **日志聚合查询**
|
||
- 这次查 v2rayN 日志,臣只能 `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 元素理解)
|
||
- **状态**:只看得到当下,看不到变化(缺长时观测)
|
||
- **自持**:改得动别人,改不动自己(缺自愈与受控重启)
|
||
|
||
把这三块补上,它就从「远程命令行工具」进化为「**真正的远程操作代理**」。
|