Commit Graph
36 Commits
Author SHA1 Message Date
jkooe 7efe717cf8 fix(gui): window.list 进程名错乱 + screen.find UIA 只支持精确匹配
## bug 1:所有窗口的进程名都报成 powershell
根因:C# 回调里用 $pid 接 GetWindowThreadProcessId 的输出 —— 但 $pid 是 PowerShell
**自动变量**(当前进程 ID),赋值被静默忽略,于是每个窗口都取到 powershell 的 PID。
改为 $procId。真机验证:cmd / hexin(同花顺) / SearchPictureTool / TextInputHost / explorer
均正确归属。

## bug 2:UIA 路径实际是精确匹配,与文档「子串匹配」不符
根因:用 PropertyCondition(NameProperty, text, IgnoreCase) —— IgnoreCase 只影响大小写,
仍是精确相等。实测「管理员: C:\WINDOWS\system32\cmd.exe」用 '管理员' 永远匹配不到。
改为:FindAll(Descendants, [控制类型条件]) 后按 IndexOf(OrdinalIgnoreCase) 子串过滤,
并加 20000 元素扫描上限防止超大 UI 树卡住。
真机验证:
  '管理员' -> 命中 '管理员: C:\WINDOWS\system32\cmd.exe' @ (548,312)
  '同花顺' -> 命中窗口标题 + 内部文本元素

## 顺带明确一个语义(写入文档)
is_foreground = 「持有键盘焦点」,不等于「视觉上在最上层」。
实测 cmd 持有焦点(is_foreground=true)但画面被全屏的「同花顺」覆盖 ——
此时按 UIA 返回的坐标点击会打到覆盖它的窗口上。
因此:**几何信息可信,视觉遮挡需另行确认**(点击前截屏复核)。

单测 61/61、e2e 21/21 全绿
2026-09-29 13:30:23 +08:00
jkooe 2dccec9a5c feat(D): 生产化 —— 无人值守模式 / 一键升级 / 文档口径更新
## D1 无人值守(install.ps1)
- 新增 -Unattended(S4U 登录类型):**无需保存密码,注销/重启后仍自动运行**
- 新增 -AtStartup:追加开机触发器;任务设置补 StartWhenAvailable(错过触发后补跑)
- **诚实标注能力边界**:S4U 运行在非交互会话,GUI 能力(截屏/UIA/键鼠)不可用;
  -Unattended 与 -AllowInput 冲突时自动关闭输入控制并回写配置
- 说明文档给出两种模式的对照表(交互模式支持 GUI,但注销即停)

## D2 一键升级(nodeagent deploy)
- system.info 新增自描述字段:pid / agent_script / agent_home / node_path
- deploy 流程:读取被控端入口路径 → 备份旧文件 → 分块上传新 agent.mjs
  → 调 system.agent.restart 受控重启 → 等待恢复并复验能力数
- 真机验证:
  * 首次(旧版 agent 无自描述):--path 指定 → 上传 853KB → PID 16492 / 31 项能力
  * 二次:自动识别路径 + 自动备份 → PID 34120 / 31 项能力(免 --path)

## D3 CI(待推送)
- windows-e2e.yml 增加单元测试步骤(3 行);因 OAuth token 缺 workflow scope,
  该改动暂留本地未推送,需用户授权后推送

## D4 文档口径更新(README)
- 能力总览补齐 31 项(新增:自持/异步任务/窗口/录屏/剪贴板/拖拽/审计校验)
- 统计数字更正:19 项能力 -> 31,15 个 MCP 工具 -> 26,补 61 项单元测试
- 路线图补 v7~v11+;安全章节补密钥库/Audit 链/ACL 细化
- 命令速查新增:restart / deploy / daemon / fanout / bg / tasks / clip / record /
  window.* / screen.find / mouse drag
- MCP 工具表按新工具重排;安装章节新增两种运行模式对照表

单测 61/61、e2e 21/21 全绿
v1.1.0
2026-09-29 11:47:15 +08:00
jkooe ad88e0bb87 feat(C): 操作面补完 —— 拖拽 / 录屏 / 剪贴板图片 / 多设备并发(能力 29 -> 31)
## C1 鼠标拖拽(input.mouse.drag)
- C# SendInput 新增 Drag():按下 → 分步插值移动 → 抬起,整段在一次进程内完成
  (避免每步一个 PS 进程的抖动),步数按距离自适应(8~60),步间延时默认 12ms
- CLI: nodeagent mouse drag <x1> <y1> <x2> <y2>;MCP: na_mouse action=drag
- 同时给 key.press 增加 interval_ms(按键序列间隔可调)
- 真机端到端验证:记事本输入 DRAGTEST_1234 -> 拖选 -> Ctrl+C
  -> 剪贴板由 'CLIPBOARD_EMPTY_MARKER' 变为 'DRAGTEST_1234'(证明拖拽真实生效)

## C2 录屏(screen.record)
- 帧循环在一次 PS 调用内完成;有 ffmpeg 则自动封装 mp4
- 产物留在被控端目录,由调用方 fs.read / pull 取回(不把大视频塞进单条 RPC 响应)
- CLI: nodeagent record --duration/--fps/--scale/--region;MCP: na_record
- 真机验证:3s@2fps 区域录制 -> 6 帧(3087ms),pull 回帧并目检内容正确

## C3 剪贴板图片(clip.get/set 1.1)
- format=auto/text/image;图片经临时文件双向传递(规避 Windows 命令行 ~32KB 上限与 stdout 截断)
- CLI: clip --image-file <路径> / clip --out <路径>;MCP: na_clip format/image_base64
- 真机验证:写入 14592 字节 -> 读回 PNG 400x200,目检图像正确

## C5 多设备并发(fanout)
- nodeagent fanout <能力> [--nodes a,b] [--args JSON]:allSettled 并发,任一失败不影响其他
- 真机验证:并发下发 system.info / system.status 正常

单测 59 -> 61、e2e 21/21 全绿
注:C4(macOS window.*/screen.find 对齐)暂缓,需 macOS 辅助功能/录屏权限,留待 Mac 作被控端时实现
2026-09-29 11:41:48 +08:00
jkooe eb54c60c7c feat(B): 安全加固 —— 密钥库保护 + 审计防篡改 + ACL 细化(能力 28 -> 29)
## B1 私钥不再明文落盘
- 新增 packages/client/src/secret.ts:macOS Keychain / Windows DPAPI(CurrentUser)
- keys.ts:落盘文件只留 secret 描述符(backend/label/protected),不含 privateKey;
  老格式明文密钥**首次加载自动迁移**;后端不可用时回退明文 + 告警(绝不因保护而不可用)
- 真机验证(隔离目录):
  * keygen -> '✓ 私钥已存入系统密钥库(keychain)',文件中无 privateKey,钥匙串有条目
  * loadKeys 回读私钥成功(64 字符)
  * 老格式明文文件 -> 自动迁移、文件清理、二次回读正常
  * Windows DPAPI 往返一致(ROUNDTRIP_OK=True,密文不含明文)

## B2 审计链式哈希(防篡改)
- 每条审计记录加 prev/hash(prev+规范化正文 sha256 前 32 位);轮转跨文件连续
- agent 重启从日志尾部恢复锚点,链不断
- 新能力 system.audit.verify + CLI 'nodeagent audit verify'
- 真机:'✓ 审计链完整(校验 21 条,跳过历史条目 857 条)'
- 单测覆盖:篡改检出、删除中间条目检出(链断裂)、历史条目 legacy 跳过、重启续链

## B3 ACL 细化
- 协议层新增纯函数:normalizeIp / ipMatchesCidr / ipAllowed / inTimeWindow / rateLimitFor
- AclClient 扩展:allow_cidr / deny_cidr / allow_window(days+from/to,支持跨零点) / rate_limits
- server.ts:IP 白黑名单与生效时段校验(命中即 audit acl.denied);限速改为按能力
  (rate_limits 精确优先、其次 glob、回退 max_calls_per_min),窗口按限速档位分桶

### 真机踩坑(已修复并回归)
Node 的 socket.remoteAddress 在双栈监听下是 **IPv4-mapped IPv6**(::ffff:192.168.1.100),
原用 split(':')[0] 取到空串 -> CIDR 永不匹配。新增 normalizeIp() 归一,单测覆盖该场景。
真机复测:'✗ E_ACL_DENIED: 来源 IP 不被允许';移除黑名单后 fs.list 连调 3 次 ->
第 3 次 '✗ E_RATE_LIMITED: 调用频率超限(fs.list 上限 2/分钟)'

单测 37 -> 59、e2e 21/21 全绿
2026-09-29 11:27:52 +08:00
jkooe 9bf330d228 fix(A): 阶段 A 稳定性清账 —— 单测 26 -> 37、按键序列、日志上限
A1 后台任务环境统一
- 抽出 childEnv() 并由 execCommand / task.ts 共用(PYTHONIOENCODING/LANG/LC_ALL)
  实测:后台任务中文正常(PowerShell 的 UTF-8 前缀本就生效,本次补齐子进程 env)

A2 按键语义扩展(input.key.press 1.1)
- keys + repeat=N:同一和弦连按 N 次(解决「连按上键 3 次」)
- sequence=[[...],[...]]:任意和弦序列
- 真机验证:repeat=3 -> times 3;sequence 2 段 -> times 2;和弦向后兼容
- manifest 补 minimum/maximum 约束

A3 重启日志保留上限
- 每次重启仅保留最近 5 个 nodeagent-restart-*.log(PowerShell 排序后 Skip 5 删除)
- 实测:日志数由累积多份收敛为 2 份,重启链路仍正常

A4 新能力单测(tests/unit/capabilities.test.mjs,+11 用例)
- 清单守护:能力名与 manifest 一一对应、无重复、字段合法、schema 封闭、共 28 项
- 参数校验:task.get/clip.set/screen.find(method 枚举)/key.press(repeat+sequence)/restart/exec(async)
- 默认值:screen.find.method=auto、task.get.offset=0、exec.async=false
- 顺带发现并修复:restart.delay_ms 缺 minimum 约束(0 被接受)
2026-09-29 11:13:53 +08:00
jkooe fac51046cf feat(v10.1): CLI 常驻 daemon(nodeagentd)—— 连接池复用,批量调用提速
## 机制
- nodeagentd 常驻进程监听 UDS($TMPDIR/nodeagentd.sock),持有与被控端的
  长连接池(autoReconnect,每设备一条,LRU 上限 8,空闲 5min 释放)
- CLI withClient 检测 socket 存活 → 经 daemon JSON-lines 转发 invoke(虚拟 client);
  daemon 自身错误静默回退直连,业务错误原样透传 —— 零行为变化,纯加速层
- CLI 新命令: nodeagent daemon start|stop|status;打包新增 nodeagentd 产物

## 实测(真机)
- 轻调用(info): 752ms -> 527ms(省 TLS+hello/challenge/auth,约 30%)
- 轻调用 5 连发(clip.get): 2.58s -> 2.41s
- 重调用(ps 远端 CPU 采样 1.4s)提速不显著 —— 瓶颈在远端执行而非握手,符合预期
- unit 26/26、e2e 21/21 全绿
2026-09-29 10:55:38 +08:00
jkooe e50e805383 feat(v10): 长任务异步化(system.task.*)+ 剪贴板(clip.*),能力 23 -> 28
## 长任务异步化(P1)
- system.shell.exec 增加 async:true -> 立即返回 task_id(wait_forever 可不设超时)
- system.task.list / get / kill:任务表(Map 内存态,完成态保留 30min / 上限 50 条),
  输出 Buffer 累积(上限 10MB),get 支持 offset 增量续读
- kill 走 killTree(整棵进程树);超时自动标记 killed/exit=124(与 timeout(1) 惯例一致)
- 命令只记 sha256 前 8 位摘要(与审计脱敏策略一致)
- CLI:bg / tasks / task <id> [--offset N] [--kill]

## 剪贴板(P1)
- clip.get / clip.set(Windows: Get/Set-Clipboard;macOS: pbpaste/pbcopy)
- set 走 base64 传输规避命令行参数编码问题(中文/emoji 安全)

## MCP 同步
- na_bg / na_task(list|get|kill 多动作)/ na_clip(get|set 多动作)

真机验证(全生命周期):
- bg 20 秒长任务 -> 立即返回 task_id;tasks 列表 running;
  task 增量读 tick 10~12 -> 14~15;--kill 后 killed/exit=124
- clip --set 中文+emoji -> 18 字符;读回逐字一致
- 28 项能力在线;unit 26/26、e2e 21/21 全绿;Mac 端 CLI/MCP 已重装
2026-09-29 01:19:10 +08:00
jkooe e4cc1c20b6 feat(v9): screen.find OCR 兜底 + MCP 同步 4 项新工具
## screen.find OCR 引擎(自绘 UI 兜底)
- method 参数:auto(UIA 优先,空结果自动 OCR)/ uia / ocr;返回 engine 字段
- OCR 实现:CopyFromScreen 截区域(可限定窗口矩形)→ WinRT BitmapDecoder →
  Windows.Media.Ocr(zh-Hans-CN)→ 行/词矩形 → 屏幕坐标
- 两个真机坑(均已实证修复):
  1) Windows OCR 会在中文字符间插空格(「我的加速」识别为「我 的 加 速」),
     子串匹配永远失败 → 匹配一律在去空格文本上进行,
     并用「最小词窗口并集」精确定位目标词段的矩形
  2) 重启任务的 cmd 用 >> nodeagent-restart.log 启动新 agent,新 agent 长期持有
     该文件句柄 → 下次重启任务所有带该重定向的命令被整体跳过(包括 taskkill!),
     表现为 restart 返回 ok 但什么都没发生 → 日志文件改为时间戳唯一 + 清理历史
- 真机验证(加速器自绘界面):「网络监控」@(2174,568)、「STEAM」@(1590,582)、
  「我的加速」@(1365,434),截图目检坐标全部精确吻合

## MCP 同步(能力 19 -> 23 对 AI 可用)
- 新增 na_restart / na_window_list / na_window_focus / na_screen_find
- unit 26/26、e2e 21/21 全绿
2026-09-29 00:59:15 +08:00
jkooe 313420a482 feat(v8): GUI 语义能力 —— window.list / window.focus / screen.find(能力 20 -> 23)
P0-1 UI 元素定位(基于 Windows UI Automation + Win32):
- window.list:枚举可见顶层窗口(hwnd/标题/进程/精确矩形/前台标识/最小化)
  —— 真机验证:正确返回 PowerShell / cmd / 各应用窗口矩形
- window.focus:按标题正则或句柄置前窗口,返回精确矩形(SW_RESTORE + SetForegroundWindow)
- screen.find:UIA 查找元素并返回**中心点屏幕坐标**(支持窗口范围/控件类型过滤),
  直接喂给 input.mouse.click —— 把「看得到画面读不懂界面」变成「按名取坐标」
  —— 真机验证:查找 'Windows PowerShell' 返回 (716,648) 与完整矩形

实现要点:
- PowerShell 脚本一律走 EncodedCommand(UTF-16LE base64),规避引号/中文/换行转义
- extractJson():Add-Type 程序集加载会向 stdout 夹杂输出污染 JSON,
  按首 [/{ 到末 ]/} 截取后再 parse(真机踩过:'Unexpected non-whitespace character')

顺带修复(本轮测试发现的 bug):
1. services 状态显示数字:Get-Service 的 Status/StartType 是 .NET 枚举,
   ConvertTo-Json 退化为数字(4=Running),显式 ToString() 修复
2. restart 任务名冲突:重启任务的 cmd 持有新 agent 进程 → 任务长期 Running
   → 下次 Unregister/Register 静默失败 → 'restart 返回成功但没重启'。
   改为唯一任务名(时间戳)+ 注册后显式校验 + 清理历史非运行任务
   —— 真机验证连续 restart:PID 2612 -> 18556,任务名带时间戳
3. cmd shell 中文:定性为 Windows 固有局限(命令行参数在进程创建时已按 ANSI
   解释,chcp 救不了参数本身),代码注释说明并引导用 PowerShell

真机:23 项能力全量在线;unit 26/26、e2e 21/21 全绿
2026-09-29 00:38:54 +08:00
jkooe 111a7b5918 feat(P0): UTF-8 全链路统一 + exec 输出结构化 + 受控重启(v7)
## P0-2 统一 UTF-8 编码链路
- util/exec.ts:Windows 侧注入 [Console]::OutputEncoding/ 为 UTF-8,
  cmd 分支加 chcp 65001;并为子进程设置 PYTHONIOENCODING/LANG/LC_ALL。
- 根治三类事故:PowerShell 管道损坏中文(U+FFFD)、控制台 GBK 乱码。
- 真机验证:桌面文件名 [115浏览器.lnk / 360安全卫士.lnk] 由乱码变为正确中文。

## P0-4 exec 输出结构化
- CLI cmdExec:退出码/耗时等诊断信息改走 stderr,stdout 只保留命令自身输出。
- 使  不再被尾部标注污染(实测 stdout 仅 'ABC')。

## P0-3 受控重启(新增 system.agent.restart,能力数 19 -> 20)
- 先经历 4 种失败方案(均有真机证据):
  1) 计划任务 + start  node.exe  -> Session 0 无桌面,start 拉不起进程
  2) detached spawn + start /B     -> cmd 退出即带走子进程
  3) detached spawn + 直接跑 node  -> detached cmd 随 agent 一起消失
  4) 计划任务 action 直接跑 node   -> 成功!但暴露环境变量传参 bug
- 最终方案:计划任务(ExecutionTimeLimit=0),action = cmd /c
  「延时 -> taskkill 旧 PID -> 前台执行 node.exe」;Task Scheduler 持有 cmd,
  cmd 持有 node,整条链与旧 agent 无关,且不依赖桌面会话。
- 关键坑:cmd 中 `set X=Y & cmd` 的 `&` 前空格会被计入变量值('Y '),
  导致 agent 读不到配置回退默认端口 → EADDRINUSE。必须写成 `set X=Y& cmd`。
- 真机验证:节点 PID 31864 -> 22656,端口/能力/调用全部正常。
- 附带:Windows 用 ping 替代 timeout(后者在非交互环境报 Input redirection 不支持)。

CLI 新增 `nodeagent restart [--delay ms]`;unit 26/26、e2e 21/21 全绿。
2026-09-28 23:26:59 +08:00
jkooe 3f91e1ddc0 docs: 新增《实战复盘:短板与改进优先级》
基于真机部署 + v2rayN 故障排查 + 重装前采集的真实使用,梳理短板:

P0(决定能不能用)
1. GUI 语义能力缺失 —— 有截屏有鼠标,但没有「界面元素 -> 坐标」的映射,
   切个节点反复点错三次,最后靠键盘导航才成功。建议引入 UIA。
2. 编码不统一 —— 三个真实事故均源于此:Set-Content 写 BOM 致 agent 起不来、
   PowerShell 管道损坏中文致清单报废、控制台 GBK 误判。
3. 自断生路 —— 重启 agent 时分离进程被自身进程树清理一起杀掉,导致失联。
   建议提供受控重启(system.agent.restart)+ 看门狗自愈 + 配置回滚。
4. exec 输出未结构化 —— 尾部附加 [退出码/耗时] 文本,污染 JSON 解析。

P1:长任务异步化、定时采样、剪贴板、窗口管理、CLI 常驻 daemon
P2:事件订阅、文件监控、录屏、拖拽/宏、多设备并行、DPAPI

一句话:能看能算能跑命令已扎实,短板在「语义」(读不懂界面)、
「状态」(只看当下)与「自持」(改不动自己)。
2026-09-28 22:01:15 +08:00
jkooe eb331502a6 docs: 新增《跨机接管的边界与局限》并记录真实故障诊断案例
皇上报障「Windows 上 WorkBuddy 反复提示 Response timed out」,要求记录
「受到什么局限导致 Mac 端无法修复 Windows 上的问题」。

新增 docs/REMOTE-LIMITS.md:
- 能力边界总览(读状态/执行命令/改文件可;改网络高风险;GUI 与人工判断不可)
- 五大核心局限:自断生路悖论 / 无 GUI 交互 / 无法替代人的判断 / 观测窗口时点性 / 破坏性操作不可逆
- 协作模式建议(能看能算的远程,断后路与需判断的交人工)
- 真实案例(2026-09-27):完整诊断链路与判读

本次远程诊断结论(未触碰 Windows 即完成):
- 链路正常:Ethernet 1Gbps/百度 200/腾讯 IPv4 200
- 矛盾状态:System Proxy ProxyEnable=0 但 ProxyServer 残留 127.0.0.1:10808
- 走 10808 访问外网超时 -> xray 上游节点不通
- resolveProxy RPC 耗时 1737ms(代理解析卡顿)
- MarketplaceInitGate 15s 安全超时后强放行
判定高度可能为「代理配置残留 + 上游节点实际不通」导致部分请求超时;
未直接修复,因所需动作为 v2rayN GUI 换节点(属 GUI + 人工判断)。
2026-09-27 20:23:21 +08:00
jkooe 3316f08c92 fix: 修复 Windows 安装后无法启动 —— PowerShell 写入的 JSON 带 UTF-8 BOM
真机报错:启动失败: Unexpected token '', "{"a"... is not valid JSON
根因:Windows PowerShell 5.1 的 Set-Content -Encoding UTF8 会写入 BOM(U+FEFF),
      Node 侧 JSON.parse 拒绝解析带 BOM 的文本。

双管齐下修复:
1. 写端 install.ps1:改用 [System.IO.File]::WriteAllText + UTF8Encoding($false)
   写出无 BOM 的 UTF-8
2. 读端容错(兼容已被写坏的配置):
   - apps/agent/src/config.ts:读配置时剥离开头 BOM
   - packages/client/src/config.ts:同上

验证:构造带 BOM 的 agent.json(ef bb bf 开头),agent 可正常启动;
单元 26/26、E2E 21/21 全绿
2026-09-27 19:32:53 +08:00
jkooe b350a41803 fix(install.ps1): 安装后验证端口监听,并在连接命令中填入真实 IP
真机安装反馈暴露两处缺陷:
1. 只打印计划任务状态(Ready)就宣告完成,未确认端口真的在监听 ——
   用户按提示连接时会超时,却得不到任何线索。
   现改为轮询等待端口进入 Listen(最多 15s),失败时打印任务最后运行结果
   与三步排查命令(手动启动任务 / 查进程 / 前台运行看报错)。
2. 连接提示里的 <THIS-IP> 是字面占位符,用户照抄会失败。
   现改为填入实际探测到的本机 IPv4。
2026-09-27 19:29:56 +08:00
jkooe 665c0e85c4 feat: Mac 控制端一键安装(pack:mac + install-macos.sh)
Mac 是控制端,不需要 Windows 安装包,但此前必须 cd 到仓库敲
node apps/cli/dist/index.js,太啰嗦。

新增:
- pnpm pack:mac(scripts/pack-macos.mjs):把 CLI 与 MCP 打包为带 shebang 的
  可执行单文件(195KB / 772KB),免 node_modules
- scripts/install-macos.sh:安装到 ~/.local/bin(已在 PATH)+ 打印 MCP 配置片段
- Mac 端不打 Node 运行时(控制端本机已有 Node,体积从约 50MB 降到约 1MB)

修复:execFileSync 不能接收带参数的命令字符串(改用 execSync)

验证:./nodeagent --help 与 MCP tools/list(18) 可直接执行;
install-macos.sh 安装后可在任意目录调用 nodeagent
2026-09-27 19:24:57 +08:00
jkooe 4c9b44288c fix(pack): esbuild 输入改用绝对路径,支持从任意目录调用
此前 pack.mjs 用相对路径 apps/agent/dist/index.js 作为 esbuild 输入,
从项目目录外调用(node <abs>/scripts/pack.mjs)时报
Could not resolve apps/agent/dist/index.js。

改为基于 __dirname 的绝对路径;README 补充两种调用方式并注明需在项目目录执行。

验证:cd /tmp && node <abs>/scripts/pack.mjs 可正常产出 32MB 安装包
2026-09-27 19:21:21 +08:00
jkooe 52b2c97bdf fix: 修复 CI 安装失败 —— pnpm 12 用 allowBuilds 而非 onlyBuiltDependencies
pnpm 12 在 pnpm-workspace.yaml 自动生成了 allowBuilds 占位(值为字面字符串),
而 package.json 的 pnpm.onlyBuiltDependencies 对该版本不生效,导致 CI 上
pnpm install --frozen-lockfile 报 ERR_PNPM_IGNORED_BUILDS。

修正 pnpm-workspace.yaml 为 allowBuilds: { esbuild: true },
本地已用 --frozen-lockfile 复现并验证修复。
2026-09-27 18:07:41 +08:00
jkooe 192e08b171 feat: Windows 便携安装包构建(免装 Node.js)
新增 pnpm pack:win(scripts/pack.mjs),一键产出 nodeagent-win-x64.zip:
- esbuild 打包被控端为单文件 bundle(含全部依赖,约 800KB)
- 自动下载 Node.js Windows x64 运行时(npmmirror,node.exe)
- 组装便携包并打 zip(node.exe + agent.mjs + install.ps1 + uninstall.ps1)

改造 install.ps1:
- 优先使用包内 node.exe + agent.mjs(便携包模式)
- 否则回退系统 Node.js + 仓库构建产物(开发模式)
- 无 Node.js 的 Windows 机器也能一键安装

配套:
- .gitignore 忽略 release/(产物体积大,不入库)
- README 安装说明改为「安装包(推荐)/ 开发模式」两方式

验证:打包产物 32MB;解压后 agent.mjs 可独立运行(19 项能力、wss 监听);
esbuild bundle 通过 banner 提供 createRequire 兼容 CJS 依赖(node-forge)
2026-09-27 18:03:04 +08:00
jkooe f4c605cc6d test+fix: 单元测试套件 + 两处细节修复
单元测试(新增,node:test 零依赖):
- tests/unit/: ulid / hmac / ed25519 / acl / validate 共 26 项用例
- 覆盖最关键的安全函数:签名验签、ACL 授权、参数校验
- 新增 pnpm test:unit;CI 增加单元测试步骤

细节修复:
1. screen.info 的 scale 硬编码为 1 → 改读注册表 HKCU AppliedDPI,
   真实反映 Windows DPI 缩放(100% -> 1.0, 150% -> 1.5)
2. verify.mjs 升级:支持多设备配置(resolveTarget)+ ed25519 模式 + Hub 中转
   (此前直接用旧的扁平配置 + 仅 psk,配了多设备会取不到 host)

验证:构建 + 类型检查全绿;单元 26/26;E2E 21/21;verify.mjs 13/0/3
2026-09-27 17:04:21 +08:00
jkooe daa39e67f4 fix: 修复项目分析指出的 5 项问题
P0 · 真 bug:
1. MCP 无法走 Hub:ensureClient 漏传 hub 字段 → 补齐(与 CLI 对齐)
2. Hub 启动提示有误:打印 --hub 但 CLI 认 --hub-token/--hub-node → 更正文案

P1 · 功能完整性与安全缺口:
3. app.list 补商店/UWP 应用来源(Get-AppxPackage),不再漏装商店软件
4. psk 模式支持能力级 ACL(此前仅 ed25519 生效,持钥即全通)
   —— 配置 ACL 的 psk 设备现在按调用方授权;未配置 ACL 的行为不变
5. MCP 设备选择:新增 na_nodes / na_use(会话级切换,不影响 CLI 默认)+ 补 na_fs_stat
6. 审计查询支持轮转文件(audit.log.1/2/...),历史记录不再丢失

验证:构建 + 类型检查全绿;E2E 21/21;
冒烟确认 psk-ACL 授权/拒绝正确、MCP 工具列表含全部 18 个工具
2026-09-27 16:38:37 +08:00
jkooe 0bf9a2f918 docs: 重写 README
原 README 已滞后与冗余,具体问题:
- 「当前状态」仍写 v1(实际已到 v6)
- 「关键决策」写「点对点直连(无中枢)」,与已实现的 Hub 中转自相矛盾
- Hub 说明重复出现两处;CLI 命令、验证、工程结构之间层级混乱

按读者动线重写(这是什么 → 能做什么 → 怎么用 → 怎么做的 → 深入):
1. 价值主张:突出「能力层」而非「远程控制」的四点差异(结构化/可授权/可审计/可穿透)
2. 能力总览:19 项能力分组表格
3. 快速开始:构建 → Windows 被控端 → Mac 控制端 → MCP 接入
4. 架构:三链路拓扑(直连 / 发现 / Hub)+ 分层职责表
5. 安全模型:认证 → 授权 → 审计 三层收窄 + 零信任启用步骤
6. 命令速查:按「连接设备 / 系统软件 / 图形文件 / 审计密钥」分组
7. MCP 工具表、Hub 中转、开发与三层验证、工程结构、路线图

同步修正:CLI 帮助补充 Hub 参数(--hub-token / --hub-node)

验证:命令与 CLI --help 逐条核对一致;构建 + E2E 21/21 全绿
2026-09-27 15:35:54 +08:00
jkooe 3d03945448 feat(v6): Hub 中转 —— 跨网段 / 公网接入
新增 apps/hub(中转节点):
- 被控端主动外连注册(/hub/agent),控制端接入配对(/hub/client)
- 配对后双向字节透传 —— Hub 不解析内容、不持有设备密钥
- Hub 令牌鉴权;list 可查在线设备
- 连接语义:控制端断开仅解绑(被控端保持在线);被控端掉线连带关闭控制端
- 同一被控端同一时刻仅服务一个控制端(占用返回 E_NODE_BUSY)

被控端:
- 新增 hub 配置;启用后不监听本地端口,改为主动外连 Hub(穿 NAT)+ 指数退避重连
- 重构抽出 createAgentCore(),监听模式与 Hub 模式共用同一套鉴权/ACL/能力分发

控制端:
- NodeProfile 新增 hub 字段;toWsUrl 自动附 /hub/client 路径
- client 支持 Hub 配对(控制面消息与 RPC 分离)

修复(Hub 场景暴露):
- cmdConnect 漏传 hub 字段,导致跳过配对直接发 RPC
- Hub unpair 过激进:控制端断开时把被控端也踢下线
- 安全:被控端 hello 时重置认证态与授权(Hub 下同一连接被多控制端复用)

验证:本地 E2E 21/21;构建 + 类型检查全绿;冒烟确认 info/exec 穿透 Hub 且多次接入保持在线
2026-09-27 15:04:05 +08:00
jkooe 4ff1b4fd90 feat(v5): 多设备管理 + 文件传输
方向 1 · 多设备管理:
- 配置模型升级为 nodes{} + current,自动迁移 v1/v2 单设备扁平配置(零破坏)
- 新增 resolveTarget():按名解析生效设备与控制端身份
- CLI: nodes(列表)/ use(切换)/ remove(移除)/ connect --name(命名)
- 全局 --node <设备名> 单次指定目标,前置(--node=x cmd)与后置(cmd --node x)均可

方向 2 · 文件传输(分块 RPC,协议零改动):
- fs.list(递归+glob)/ fs.stat / fs.read(offset+max_bytes 分块)/ fs.write(原子写+append)
- 小文件(<=8MB)首次读取附带 sha256;路径白名单 fs_roots,越界 -> E_ACL_DENIED
- CLI: ls / stat / cat / pull / push(自动分块;pull 与 push 均原子落盘)
- MCP: na_fs_list / na_fs_read / na_fs_write

能力总数 15 -> 19;MCP 工具 12 -> 15

验证:本地 E2E 20/20(新增文件传输全链路用例);构建 + 类型检查全绿;
冒烟确认 3MB 文件 pull 后 sha256 一致、白名单越界被拒
2026-09-27 13:25:38 +08:00
jkooe 3d0eb1c696 feat(v4): 无感体验 —— 局域网自动发现 + 断线自动重连
自动发现(免手抄 IP):
- 被控端 UDP 心跳广播(默认 5s 一次,目标 255.255.255.255:8766)
- 控制端 Discovery 监听 + TTL 设备表(默认 30s 过期)
- 报文不含任何凭据;可 discovery.enabled:false 关闭
- 控制端取「报文来源 IP」作为可达地址(比被控端自报地址可靠)
- CLI: nodeagent discover [--wait 5];MCP: na_discover

断线自动重连:
- client 新增 autoReconnect(指数退避 1s→30s + ±15% 抖动)
- 重连前清空在途请求(统一 E_NODE_OFFLINE),避免悬挂
- onStateChange 回调(connected/reconnecting/closed)
- close() 会停止重连,不会关不掉

修复:UDP socket 需先 bind 再 setBroadcast(未绑定时报 EBADF)

验证:本地 E2E 19/19(新增发现 + 重连两用例);构建与类型检查全绿
2026-09-27 13:11:13 +08:00
jkooe edc69b0a34 feat(v3+): 审计日志 + 调用限速
审计(安全闭环最后一环):
- 被控端将关键动作以 JSONL 写入 <数据目录>/audit.log:agent 启停 / 认证成败 /
  invoke 结果 / ACL 拒绝 / 限速拦截
- 大小轮转(默认 10MB × 5 份);参数默认只记 sha256 摘要,log_args 开启后
  自动脱敏(pass/token/secret/key/auth 键 → ***,字符串截断 200 字符)
- 审计写入失败被吞掉,绝不阻断业务主流程
- 新增能力 system.audit.list(支持 limit/since/client_id/type 筛选)
- CLI: nodeagent audit;MCP: na_audit

限速:
- 落地 ACL 中预留的 max_calls_per_min(按调用方滑动窗口计数)
- 超限返回 E_RATE_LIMITED(-32407)

验证:本地 E2E 全绿;零信任用例扩展为 6 项断言(授权/拒绝/审计留痕/脱敏/限速/未注册/错钥)
2026-09-27 11:56:37 +08:00
jkooe 5b7e967868 feat(v3): 零信任安全 —— Ed25519 身份认证 + 能力级 ACL
认证(两模式并存,默认 psk 向后兼容):
- 新增 auth_mode: psk | ed25519
- ed25519:每控制端独立密钥对,挑战-应答签名验证(替代共享密钥)
- 密钥用 Node 内置 crypto 生成(零新依赖),Base64(DER) 单行格式
- 私钥存 <数据目录>/keys/ed25519.json(600 权限,永不外传)

授权(能力级 ACL):
- acl.clients[] 按调用方登记 pubkey + allow/deny(glob 匹配)
- 判定顺序 deny → allow → default_effect(默认拒绝)
- 未注册身份 / 验签失败 → 握手拒绝;未授权能力 → E_ACL_DENIED
- auth_ok 下发 authorized 子集,CLI 以 授权/未授权 标识展示授权矩阵

接入:
- CLI: keygen(生成密钥 + 打印 ACL 配置片段)、connect --auth-mode ed25519
- MCP: 按 auth_mode 自动选择认证方式
- install.ps1: 显式写 auth_mode + 零信任指引

验证:本地 E2E 17/17(新增零信任四项断言);手动端到端复验通过
2026-09-27 11:01:58 +08:00
jkooe 792937c6b0 feat(v2): 图形接管 —— 屏幕感知 + 输入控制
新增 7 项能力(总计 14):
- screen.info / screen.capture:显示器信息、截屏(region/scale/format)
- input.mouse.move/click/scroll、input.key.type/press:鼠标键盘控制

安全设计:
- input.* 为高危能力,由被控端 allow_input 开关统一管控,默认禁用
- 未开启时返回 E_CAPABILITY_DISABLED(附开启指引)
- 文本输入经 Base64 传参、PowerShell 侧解码,杜绝内容注入
- 按键名走白名单映射(虚拟键码),非法键名直接拒绝

实现:纯 PowerShell(System.Drawing 截屏 + user32.dll SendInput),
无原生编译、无第三方依赖。

接入:
- CLI: screen / screenshot / mouse / key 命令
- MCP: na_screenshot(直接返回图像)/ na_mouse / na_key
- install.ps1: 新增 -AllowInput 开关
- verify.mjs: 新增 v2.1~v2.3 验收项

验证:本地 E2E 16/16;远程验收 12 通过 / 0 失败
2026-09-27 03:43:10 +08:00
jkooe 553d123033 fix(agent): 修复 TLS 未生效的严重 bug(自称 wss 实为明文)
根因:ws 库的 WebSocketServer 不识别 cert/key 选项,直接传入会被静默忽略,
导致被控端配置 tls=true 时仍以明文监听;控制端用 wss 连接即报
'tls_validate_record_header:wrong version number'。

修复:TLS 由 https.createServer({cert,key}) 承载,再挂 WebSocketServer({server})。

验证:新增 TLS 模式 E2E 用例(先本地复现失败,修复后通过),本地 14/14 全绿。
该 bug 由 CI 真机验证(Windows 上 CLI 连入步骤失败)暴露。
2026-09-27 02:45:58 +08:00
jkooe 1d992cf9dc fix(agent): 超时强杀的 exit_code 跨平台归一为 124
Windows 上 taskkill /F 会让子进程以 code=1 退出(Unix 为 null),
原逻辑 code ?? (killed ? 124 : -1) 在 Windows 上会返回 1。
改为 killed 优先:被杀统一返回 124,与 Unix timeout(1) 语义一致。

CI 实机验证发现:Windows 上 12/13 → 修复后应 13/13
2026-09-27 02:39:16 +08:00
jkooe 96b02df857 fix(e2e): 修复 Windows 上被控端无法就绪的根因
根因:测试脚本用 HOME 指定数据目录,但 Windows 的 os.homedir() 认 USERPROFILE,
导致被控端读不到测试配置、回落到默认端口 8765,与测试连接的 18765 不匹配。

- 引入 NODEAGENT_HOME(数据目录,跨平台一致),agent/client 均优先读取
- 两个 E2E 脚本改用 NODEAGENT_HOME,并同时设置 HOME/USERPROFILE 兜底
- 转发被控端 stdout/stderr,被控端提前退出时立即失败(便于定位)
- 就绪等待超时 15s → 30s

本地已回归:13/13 通过
2026-09-27 02:36:33 +08:00
jkooe 9e357cf7cc Create windows-e2e.yml 2026-09-27 02:23:42 +08:00
jkooe bda45e41e5 docs: README 补充自动化验证的成本说明(GitHub Actions 免费额度与支出限额) 2026-09-27 01:29:37 +08:00
jkooe f1506b3fd0 test: 新增自动化真机验证体系
- tests/e2e/windows.mjs: Windows 专属能力测试(service.list / app.list / winget 装软件 / cmd 外壳)
- tests/e2e/run.mjs: service.list 断言改为平台感知(Windows 期望成功)
- scripts/verify.mjs: 远程验收脚本,对齐 PRD FR-01~FR-08 逐条验收 + Markdown 报告
- package.json: 新增 test:windows / verify 脚本
- README: 补充自动化真机验证三层体系说明

注: .github/workflows/windows-e2e.yml 需 workflow scope,待授权后单独提交
2026-09-27 01:24:52 +08:00
jkooe 77c024703a feat: nodeagent v1 初期开发完成
- packages/protocol: 协议定义(信封/方法/错误码/能力schema/校验/HMAC/ULID)
- packages/client: 控制端客户端库(连接/握手/invoke/超时)
- apps/agent: Windows 被控端(监听/HMAC认证/能力分发/7项能力/进程执行)
- apps/cli: 控制端 CLI(connect/info/status/ps/services/exec/apps/install/list/invoke)
- apps/mcp: MCP server(na_* 工具,接入 WorkBuddy)
- scripts: install.ps1 / uninstall.ps1(开机自启 + 防火墙 + 密钥生成)
- tests/e2e: 端到端测试 13/13 通过(握手/能力/超时强杀/参数校验/鉴权失败/未认证)
2026-09-27 01:06:35 +08:00
jkooe 3e129e6411 docs: 新增 nodeagent 开发文档(DEVELOPMENT.md,参考 NodeBuddy 裁剪) 2026-09-27 00:50:35 +08:00
jkooe f0f4854110 docs: nodeagent PRD v1.0(已认可)+ README + .gitignore 2026-09-27 00:32:50 +08:00