Files
nodeagent/docs/RETROSPECTIVE.md
T
jkooe e23788d717 refactor(versioning): 统一版本号 —— semver 为唯一版本号,开发批次代号并入其归属版本
## 问题(皇上指出)
项目里两套编号并行,无法判断哪个是"当前版本":
- **发布版本**(semver):v1.1.0 ~ v1.6.0、v2.0.0
- **开发批次代号**:v1 ~ v26(README 路线表 / 代码注释 / 文档)
时间线证据:v1.1.0 首发(09-29)时批次已推进到 v12.4 —— **二者从来不是一对一**(26 批次只发过 10 版)。

## 规则(新增 docs/VERSIONING.md §0 版本号唯一性规范)
- **唯一版本号 = semver `vX.Y.Z`**,两个来源必须永远一致:git tag 与 package.json(构建期注入 agent)
- **批次代号不是版本号**,不再单独出现;需要区分同版内多批改动时用**功能名**
- 附「批次 → 归属发布版本」映射表(按 tag 时间线与功能内容推定,并注明以 CHANGELOG 为准)

## 统一动作
1. **批次 v23 ~ v26 → v2.0.0**(22 文件 / 53 处)—— 它们确实属于 v2.0.0
2. **臣的批次号 v1.7 / v1.8 → v2.0.0**(27 文件)—— 未单独发版
3. **简写补全三段式**:v1.5 → v1.5.0、v1.6 → v1.6.0
4. **README 路线表**:`**v1**` → `**批次 1**`;表头加「批次 ≠ 版本号」说明并链到 §0;
   补齐批次 16~29 与 **v2.0.0 发布行**;删掉过时的「待办」行(提到的分支早已删除)
5. `docs/VERSIONING.md` 里**故意保留** 3 处批次号 —— 那是规范说明本身

## 保留项(真实发布版本,不动)
v1.1.0 / v1.2.1 / v1.3.0 / v1.4.0~v1.4.3 / v1.5.0 / v1.6.0 / v2.0.0(10 个 tag)

## 验证
- 版本号一致性:package.json = tag = 8 个 workspace = tauri.conf.json = **2.0.0** ✓
- v1.7/v1.8/v23~v26 全仓残留 **3 处**(均在 VERSIONING.md 规范说明中,属预期)
- 7 包构建 · 单测 273 · e2e 30 · ps1-encoding 24 全绿
2026-10-10 14:27:12 +08:00

12 KiB
Raw Blame History

nodeagent 实战复盘:短板、瓶颈与改进优先级

⚠️ 本文写于 2026-09-28(当时 19 项能力),多数结论已过时。 2026-10-07 逐项核查:P0 全部解决、P1 仅「定时采样」未做、P2 已补大半。 当前状态与下一步路线以 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 应自动注入:
      [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 —— 决定"用得爽不爽"

  1. 事件订阅(push 模型)
  2. 文件监控 / 录屏
  3. 日志聚合查询
  4. 鼠标拖拽 / 键盘宏
  5. 多设备并行
  6. 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 表。