Files
nodeagent/docs/RETROSPECTIVE.md
jkooe b9236dfaf1 chore(privacy): 公开仓库隐私脱敏 —— 清除可定位到个人与设备的字面量
按 security-reports/ 的隐私审查台账逐项收口。纯脱敏与表述泛化,
不含任何业务逻辑变更;实测结论与排查步骤一字未动。

P1(可定位到具体设备/个人)
- scripts/verify-b2.mjs:NA_URL 去掉硬编码缺省值(原指向一台真实机器),改为必填。
  这是全仓库唯一「拿着就能连到某台真机」的常量。
- 测试账户名 pdverify → na-test(docs/VM-VERIFICATION.md 7 处、docs/console-plan.md 1 处)
- 虚拟机与宿主的真实网段 → RFC 5737 文档保留段 192.0.2.x(含 CLOSE_WAIT 记录)
- 跨项目名与私人称呼 → 中性表述(docs/PRD.md、docs/DEVELOPMENT.md、docs/console-plan.md 等)

P2(低风险设备指纹)
- 虚拟化产品及其 CLI / 访客工具名 → 中性表述(12 个文件 + 1 个工作流注释)
- 测试目录 C:\nodeagent-test → C:\na-test
- NAS 设备型号与系统名 → NAS 设备 / NAS 系统(docs/DEPLOY-LINUX.md、scripts/install-linux.sh、README.md)
- 真机 CPU 型号、真机物理分辨率、代理软件品牌与监听端口、杀软品牌 → 泛化
- 单元测试示例段去掉真机 IP 尾八位:acl-v11 / net-scripts 的 fixture 与断言同步更新
- .gitignore 补 config.json / agent.json(含真实密钥与节点地址,绝不入库)
- 顺带移除混入技术文档的「不构成投资建议」字样(与文档主题无关)

验证:pnpm test:unit 340/340 通过;独立扫描器对 235 个跟踪文件做 17 类模式
全量扫描,命中 0。
2026-10-13 22:35:40 +08:00

268 lines
12 KiB
Markdown
Raw Permalink 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.
# 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 表。