按 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。
12 KiB
nodeagent 实战复盘:短板、瓶颈与改进优先级
⚠️ 本文写于 2026-09-28(当时 19 项能力),多数结论已过时。 2026-10-07 逐项核查:P0 全部解决、P1 仅「定时采样」未做、P2 已补大半。 当前状态与下一步路线以
ROADMAP.md为准,本文保留作为「问题从哪来」的历史记录。
基于 2026-09-27 ~ 09-28 的真实使用(Windows 真机部署 + 代理客户端故障排查 + 重装前信息采集) 所有问题均来自实际踩坑,非理论推演。
一、最痛的问题:GUI 语义能力缺失(P0)
场景还原
排查代理客户端节点问题时,AI 需要切换活动节点:
| 尝试 | 结果 |
|---|---|
| 算坐标点击第 2 行 | ❌ 点到第 1 行 |
| 重新算坐标再点 | ❌ 又点到第 3 行 |
改用键盘 ↑/↓/Enter |
✅ 成功 |
问题本质:AI 有 screen.capture(能看)和 input.mouse.*(能点),但没有把「看到的界面元素」映射为「可点击坐标」的能力。
每次都要:截图 → 人眼估算像素 → 换算缩放的 region 偏移 → 点击 → 再截图验证。误差极大,反复试错。
改进方向(按性价比排序)
-
UI 元素定位能力(最关键)
screen.find(text)/screen.find(image):返回元素坐标- Windows 侧可用 UI Automation (UIA) —— 原生支持,能直接枚举窗口控件树
- 实现成本中等,收益极高
-
截图附带坐标网格
screen.capture可选叠加坐标标尺(每 100px 标线)- AI 看图即可精确推算坐标,无需盲试
-
鼠标相对定位
input.mouse.move支持--relative dx dy(当前只有绝对坐标)
-
窗口管理
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 | 多次误判 |
改进方向
-
能力层统一 UTF-8(在 Windows 侧强制)
system.shell.exec应自动注入:[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new() $OutputEncoding = [Console]::OutputEncoding- 从根上解决 2、3
-
新增
fs.write_text/fs.read_text(明确 UTF-8 无 BOM 语义)- 或者给
fs.write增加encoding: 'utf8' | 'utf8-bom' | 'gbk'参数 - 让「写配置文件」这类高频操作默认安全
- 或者给
-
结构化返回(不靠文本传递)
- 现在
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 彻底失联,只能请用户手动恢复。
改进方向
-
受控重启能力(最高优先)
system.agent.restart { delay_ms: 2000 }- 由 agent 自己实现:fork 一个「延时重启器」(脱离进程组),或注册一次性计划任务
- 重启后主动回连/上报,让控制端知道「我回来了」
- 这样 90% 的配置变更新场景都不再需要用户介入
-
健康检查与自愈
- agent 内置看门狗:若 N 秒内未响应,自我重启
- 启动失败时自动回滚到上一份可用配置(配合配置快照)
-
分离进程的明确语义
- 提供
spawn能力(明确「不随命令结束而终止」),而不是让用户用Start-Process碰运气 - 或者:
exec增加detach: true参数
- 提供
-
改端口/改配置前先"预演"
- 支持用一个临时配置在旁路端口试启动,成功再切换 —— 这正是这次出事后 AI 总结的正确做法
四、可观测性与长时任务(P1)
场景还原
- 代理不通问题反复发作,但 AI 只能看当下快照(
netstat、Get-Process),无法看到 5 分钟前的状态 - 想跑「持续 5 分钟、每 2 秒记录一次连接状态」的采样 →
exec默认 30 秒超时,长命令直接被砍 - 排查第三方杀软拦截时,需要「退出杀软前后对比」→ AI 无法安排这个时序
改进方向
-
长任务异步化
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,且命令一旦超时整条被杀,无法续接
- 现在超时只能靠调用方抬
-
定时采样能力
monitor.start { interval_ms, command | metric, duration_ms } → 落盘 monitor.report { id }- 专治「间歇性问题」—— 这次代理不通就是典型的间歇性/条件性问题
-
事件订阅(push 模型)
- 现在全是拉取式(AI 问一次,答一次)
- 应该支持:
subscribe { events: ['process.crash','net.change','file.change'] } - 让被控端主动推异常,而不是等 AI 去查
-
日志聚合查询
- 这次查代理客户端日志,AI 只能
Get-Content -Tail N手工翻 - 应提供
log.query { path, since, level, pattern, limit },由被控端侧做高效过滤
- 这次查代理客户端日志,AI 只能
五、能力缺口清单(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 —— 决定"用得爽不爽"
- 事件订阅(push 模型)
- 文件监控 / 录屏
- 日志聚合查询
- 鼠标拖拽 / 键盘宏
- 多设备并行
- 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 表。