Commit Graph
14 Commits
Author SHA1 Message Date
jkooe 590f62e968 docs(changelog): 回填「检查更新」修复的真机复验与守卫口径
CHANGELOG 与 docs/console-plan.md 的该节写于「15 用例 / 248KB」时点(随后被隐私脱敏
提交一并带走),已落后于代码,且当时「经代理成功」那一组受宿主线路拥塞未取到。
本次回填三处:

- 用例数 15 → **16**,补第 15 条守卫(错误映射:连接被中途重置不得报成「请求超时」)
  及其注入验证行。
- 补记**第三个缺陷**:ECONNRESET 被误归 abort 类 → 真机耗时 82.9s 却报「请求超时
  (120000ms 内未完成)」,把「对端掐断连接」这条最有价值的信息抹掉。
- 产物 248KB → **249KB**;真机复验表补 D/E 两组(Win11 ARM64 VM,用随包 node.exe 直跑
  dist/sidecar.mjs,两侧 sha256 一致):
  · D 对照(不配系统代理、不给任何环境变量)→ 30877ms 失败,但报真实原因 socket hang up,
    data 带 via=直连 / timeout_ms / 可操作提示;
  · E 实验(只设 WinINET 系统代理、仍不给任何环境变量)→ **成功 28883ms**,
    via_proxy=system、日志「(来源 system)」、退出码 0。
  实验组走通的正是本次新增的系统代理发现;其 28.9s 耗时同时反证旧 20s 内部超时必然失败。
  复验后 VM 的 ProxyEnable/ProxyServer 已原样还原。

无代码改动。
2026-10-13 22:47:36 +08:00
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
jkooe 02887e265b fix(agent): 图形能力在高 DPI(200% 缩放)下坐标错位
真机(Parallels 27 + Win11 ARM64,主机 200% 缩放、物理 3600×2020)暴露:
被控端 PowerShell 助手进程**默认 DPI 不感知**,同一进程里并存两套坐标系 ——

| 调用 | 不感知时 |
|---|---|
| Screen.Bounds / VirtualScreen | 逻辑 1800×1010 |
| CopyFromScreen | 按物理裁剪 → 缺省只截到屏幕左上 1/4 |
| SetCursorPos | 被系统 ×2 |
| UIA(screen.find) | 物理(与进程是否感知无关) |

净效果:screen.find 给出的物理矩形**不能**直接喂 input.mouse.*,必须人工除以 scale
(上一轮靠这个临时绕法才点中主题按钮 —— 能绕,但属用户侧的隐式约定,必须消灭)。

- 新增 apps/agent/src/util/dpi.ts:PS_DPI_AWARE(SetProcessDPIAware 前置段)。
- 注入全部坐标相关脚本:screen.ts / input.ts / window.ts(WIN_HELPER_PRELUDE,覆盖
  window.list 与 window.focus)/ record.ts;并把它从 win-ps/prelude-ocr.ts 的 try{} 里
  移出 —— 放在 try 内一旦抛错就被 catch 吞掉,助手会静默退回不感知。
- screen.ts 显式改用 $d.Bounds.Width(已是物理),scale 仅作参考。
- 新增守卫 tests/unit/dpi-awareness.test.mjs(8 项):断言**实际下发的脚本文本**,
  而非「源码里出现过常量」—— 首版按源码扫描,注入故障实测不会变红;改为断言
  buildInputPsScript / buildListDisplaysScript / buildCaptureScript / WIN_HELPER_PRELUDE
  的脚本串后,二次注入故障精准变红。

真机终验(零换算):
- screen.info 3600×2020,与裸 GetSystemMetrics 一致;
- input.mouse.move(x,y) 读回物理光标 == (x,y)(4 处抽样全中);
- screen.find 物理矩形中心**直接** click → 主题单选 UIA selected 正确翻转且可逆。

文档:VM-VERIFICATION.md 新增 §10(坐标口径缺陷本体 / 复验配方 / 输入注入失效的逐条
排除与判决证据 / 两条 prlctl 通道的语义差异);console-plan.md 补 ARM64 真机安装冒烟与
界面实点记录(含新发现 P2:控制台无单实例保护)。

⚠️ CHANGELOG 备注:本批**属被控端变更**,与上一节「本轮不升版本号」的结论不同 ——
发版必须升 PATCH(→ 2.0.3),否则 agent.mjs 会谎报一个并不包含本修复的版本。

测试:dpi 守卫 8/8;pnpm test:unit 290/290。
2026-10-12 03:11:34 +08:00
jkooe c20e71bddc docs: 补齐 Windows 端控制台文档(含三次 run 的失败-修复-实证全过程)
README / CHANGELOG / docs/console-plan.md 三处同步:

- README:补 Windows 双架构安装包实测体积、第三条断言(解开安装包查内容)、
  以及实测解出的 node.exe 体积与 PE Machine。
- CHANGELOG:新增「Windows 端控制台安装包」整节,含双架构理由、
  为什么用原生 ARM runner、cfg!(windows) 分支首次被编译器看到、
  以及三次 run 的完整过程(体积断言写错 → `&` 调 GUI 程序不等待 → 双绿)。
- docs/console-plan.md:断言表 2 条 → 3 条;新增三节实测记录;
  产物形态表补实测体积;8.2 指标验收的「Win 安装包 < 40MB(待实测)」补上实测值。

另修复:`## 界面观感约定(2026-10-11)` 标题被上一轮编辑误吞
(正文直接从「两轮纯界面调整」开始),已恢复。

留下两条可迁移教训:
1. 体积阈值属于「中间逻辑自洽」式断言 —— 判据要落在产物内容上;
   且压缩容器与未压缩目录的体积不可直接比。
2. `msiexec.exe` / NSIS 安装包是 GUI 子系统程序,PowerShell `&` 调用
   既不等待也不设 $LASTEXITCODE → 必须 Start-Process -Wait -PassThru。
2026-10-12 00:20:50 +08:00
jkooe 456e452b30 test(console): 补两条「静默失效」守卫 —— CSS 变量闭合 + 首帧底色一致
主题改动引入了一类新的静默失效:**拼错的 CSS 变量名不报错**。
`var(--na-sunkenTypo)` 类型检查过、构建过、运行也不抛 —— 颜色只是落回继承值,
界面「看着对但就是不对」,与本项目历来吃亏的失效模式同源。故补守卫:

7. 扫 src/** 全部 var(--na-*) 使用点,与 cssVars() 的键集合比对
8. index.html 的首帧兜底色 ⇄ 调色板 bg

第 8 条是因为首帧防闪脚本是全应用**唯一允许的色值重复处**(其执行时点早于任何模块加载,
重复无法消除);既然允许重复,就必须有东西盯着它,否则改调色板忘了同步
→ 深色用户每次启动闪一帧白。

注入故障实测两条都会红:
  var(--na-sunken) → var(--na-sunkenTypo)   第 7 条红
  var(--na-sunken) → var(--na-sunkenx)      第 7 条红
  index.html #f4f4f5 → #ffffff              第 8 条红
复原后 8/8 绿,文件逐字节还原。

第 7 条顺带暴露过一次**守卫自身漏报**:正则原写 [a-z0-9-]+,遇到 `sunkenTypo`
这种含大写的拼错会在 `T` 处停下导致整个模式不匹配 —— 写错的变量名反而逃过检查。
放宽到 [^)\s,;]+ 后才真正生效。
教训:**注入故障不只验证守卫有没有,还要验证它覆盖了目标形态**。

单测 280 → 282。
2026-10-11 14:34:41 +08:00
jkooe 4e6eb32929 style(console): 配色收敛为「简约白 / 高级灰」两套中性灰阶主题
原配色是深色 + 品牌青绿(#63e2b7)+ naive-ui 默认绿主色 —— 颜色种类多。
改为两套**只用中性灰阶**的主题,侧栏底部一行切换,选择存 localStorage:

  简约白  页底 #f4f4f5 / 侧栏 #fafafa / 卡片 #ffffff
  高级灰  页底 #1d1f22 / 侧栏 #181a1d / 卡片 #24262a

几处刻意的选择:

1. 白主题页底**不是纯白**。卡片本身是白的,两者同色就只能靠 1px 分线撑层次,
   整页发「糊」;压一档浅灰后层次为「页底 < 侧栏 < 卡片」,且仍是白为主。
2. 主色位(naive-ui 的 primary)改**中性灰** —— 主按钮、开关激活态、进度条、
   勾选框全吃这一个值。原来它们是绿色/青绿,正是「配色不简单」的主因。
3. 语义色(成功/警告/错误/信息)**降饱和而非去掉**。这是运维工具,
   「运行中 / 出错」必须一眼可辨,全部灰化会导致误读。故它们只出现在徽章与
   告警这类**小面积**上,底色观感仍是「白 + 灰」。
4. **色值只定义一份**:src/theme/index.ts 的 Palette 既生成 naive-ui 的
   themeOverrides,又由 App.vue 运行期注入成 --na-* CSS 变量。
   若「TS 调色板」与「CSS 写死色值」各存一份,换主题时会**一半新一半旧、
   不报错、类型也抓不到** —— 正是本轮要消灭的失效模式。
   为此清掉散在 8 个文件里的 17 处写死色值(日志三流着色、代码块底色、
   节点选中边框、历史悬停…),复扫后自定义样式里硬编码色为 0。
5. 主题**只存 localStorage**,不落 Rust 侧 settings:那套 Settings 是给
   「仅服务模式」(无窗口、常驻托盘)也生效的偏好用的;主题是纯图形偏好,
   没有窗口时毫无意义 —— 为一件纯前端的事去动 Rust 命令面与契约守卫不划算。
6. index.html 内联一小段脚本做**首帧防闪**(否则深色用户每次启动闪一帧白)。
   此处**刻意重复 2 个底色值**,已在 index.html 与 theme/index.ts 两处注明
   「改底色时同步」—— 因为在它执行之前没有任何模块可依赖;
   除此之外全应用再无第二份色值。

验证:vue-tsc 0 错 | 契约守卫 6/6 | 单测 280/280 | .app 重打通过(92.22 MiB)|
两套主题均以真实采集数据回填渲染核对 —— 截图脚本顺带读回 --na-bg 与 body 背景,
确认主题真的切过去了,而不是「看起来像」。
2026-10-11 14:29:50 +08:00
jkooe 2528f6b709 docs: 归档「控制台去命令行化」—— CHANGELOG / console-plan / README
CHANGELOG:新增 [未发布] 章节,并按「刻意不升版本号」写明理由 ——
本轮新增的全是控制台功能,被控端能力集合未变;而 bundle-agent.mjs 会把根
package.json 的 version 注入 agent.mjs(__AGENT_VERSION__),
一旦为控制台功能升号,被控端就会谎报一个它并未包含的版本。故维持 2.0.2。

console-plan:新增「本机服务托管」章节,含需求逐条落点对照表、
消灭命令行窗口的三条措施、四条实测踩出来的约束、播种防降级的规则、
「不靠单一信号判实例在跑」的交叉判定、以及仍需图形环境人工实点的清单。

README:控制台页面表 7 → 9(补「本机服务」与「服务日志」),
打包命令改为 pnpm --filter nodeagent-console bundle,
体积数字修正 —— 原文写的 .app ≈ 6.8 MiB 是 Node sidecar 方案之前的旧数,
现为 .app 90.8 MiB / .dmg 33.78 MiB,并补「无需预装 Node」「自启与图形能力
要求登录用户会话」两条注意事项。
2026-10-11 11:42:27 +08:00
jkooe a6be917637 docs: 记录控制台 Node 运行时分发与体积口径调整
- console-plan.md:
  · §8.2 打包体积指标口径由「Mac .app < 30MB」改为「**分发件** Mac .dmg < 40MB」
    —— 原数字是 Tauri + Rust client 时代定的(当时应用内不含 JS 运行时);
    改 sidecar 方案后应用必须自带 Node 运行时,.app 因此到 90.8MB,但用户下载的 dmg 仅 33.8MB。
    已明确标注「口径调整,待确认」。附本机 Electron 运行时本体 207MB 的实测对照。
  · 新增「Node 运行时分发」节:做法、三处实测约束(编译期契约 / strip 须重签 / 映射须对齐
    resolve_node)、验证方式(verify-bundle 真跑产物)、实测体积。
  · 「已知待办」由两条收敛为一条(剩 GUI 端到端人工验证,需图形环境)。
- CHANGELOG.md:新增 [未发布] 节,记录运行时分发功能与两处规避(SIGKILL 137、编译期契约)。
2026-10-11 08:55:45 +08:00
jkooe 9bba53d5c0 fix(ci): 修 Rust Shell 工作流 —— resources 只声明构建期必产出的文件
CI 失败根因(本地已复现确认):`tauri-build`(build.rs) 会在**编译期校验**
`tauri.conf.json` 的 `bundle.resources` 声明文件是否存在。
我之前声明了两项:`sidecar.mjs`(CI 未生成)+ `resources/node`(Node 运行时未下载)
→ build.rs 直接失败("failed to run custom build command")。

修复:
- `tauri.conf.json` 的 resources **只留 sidecar.mjs**,并写明为何不能声明未就绪的文件
- `rust.yml` 在 clippy/test **之前**加「构建 sidecar」步骤(`pnpm --filter nodeagent-console sidecar:build`)
- console-plan.md:⑤ 不再把 node 记为已完成(未验证的不写成做完),
  补「已知待办」(Node 运行时随包分发 + GUI 端到端人工验证)与「构建约束」说明

本地复现验证:删掉 `sidecar/dist/` → `cargo check` 复现同一错误;恢复 → 通过 ✓
2026-10-10 15:25:12 +08:00
jkooe 53bf5a0634 feat(console)!: Rust 壳改用 Node sidecar(选项 D 完成)—— 退役 crates/nodeagent-client
## 动机(消除「两套协议实现」)
原设计让 Rust 壳自己实现 client,与 TS 版(@nodeagent/client)并存 → 改一处漏一处时
构建过、单测过,只在真机暴露。现 Rust 壳只做**进程管理 + 消息转发**,协议实现只剩 TS 一份。

## ④ Rust 壳改造
- 新增 `src-tauri/src/sidecar.rs`(~290 行):Sidecar 管理器 —— spawn node、
  行分隔 JSON-RPC 的请求/响应匹配(oneshot + 超时兜底)、通知回调(state/log/agent_event)、
  `resolve_node`/`resolve_sidecar` 路径解析(env → Tauri resource → 开发期系统 node)
- 重写 `lib.rs` 为薄转发层:`ensure_sidecar` 懒启动;**5 个 command 签名与 3 个 event 名
  (na-state/na-log/na-event)一律不变 → 前端零改动**;`get_capabilities`/`get_state`
  读本地缓存以**保持同步签名**;`RunEvent::Exit` 时优雅 shutdown(kill_on_drop 兜底不留孤儿)
- `Cargo.toml`:移除 nodeagent-client 依赖、加 tokio;**版本 0.1.0 → 2.0.0**
  (原先与 tauri.conf.json 的 2.0.0 不一致,属版本号统一范围)

## ⑤ 打包配置
- `tauri.conf.json`:`bundle.resources` 声明 sidecar.mjs 与 node;
  `beforeBuildCommand` 串上 `sidecar:build`(否则 resource 缺失)

## ⑥ 退役
- 删除整个 `crates/`(12 文件,含 nodeagent-client 与其 probe 示例)
- `rust.yml` 由「Rust client 单测」改为「**Rust 壳单测(真跑 sidecar)**」:
  装 Tauri 系统依赖 + Node + pnpm,跑 clippy `-D warnings` 与 `cargo test`
- `package.json`:`test:rust` 改指 console 壳;移除 `probe`
  (其「对真机逐项核对字段」的用途已由 CLI `nodeagent invoke` 覆盖)
- 引用清理:README(数字与协议客户端说明)、console 前端两处注释、
  console-plan(开头加「原方案已被取代」提示 + 补改造详情)、ROADMAP 历史描述加退役批注

## 验证
- **`cargo test` 3/3 通过**(真跑 Node sidecar 子进程:spawn/请求-响应/错误不崩)——
  这是关键:sidecar 管理器**不依赖 Tauri GUI**,故核心逻辑可在 CI 真实验证,不必等人工开窗口
- `cargo check` 零警告;clippy 标准与 CI 一致
- 273 单测 / 7 包构建 全绿
- ⚠️ **仍需人工验证**:Tauri GUI 端到端(开窗口 → 连被控端 → 点界面),沙箱跑不了图形应用
2026-10-10 15:16:24 +08:00
jkooe 096193a8ec feat(console): 采纳 sidecar 方案(选项 D 第一步)—— Node sidecar 实现 + 8/8 真机 e2e
## 为什么
原设计让 Rust 壳自己实现一份 client(crates/nodeagent-client),代价是**两套协议实现静默漂移**
(改一处漏一处时构建/单测都不报错,只在真机暴露)。sidecar 让 Rust 壳退化为
「进程管理 + 消息转发」,协议实现只剩 TS 一份,从根上消除漂移。

## 本次交付
1. `apps/console/sidecar/main.mjs`(190 行):把 `@nodeagent/client` 暴露成
   **JSON-RPC 2.0 over stdio**(行分隔)。方法 connect/disconnect/invoke/state;
   onStateChange/onLog/onEvent 三个回调 → `event` 通知(kind=state/log/agent_event)
2. `apps/console/sidecar/build.mjs`:esbuild **JS API** 打成单文件(避开 pnpm `.bin` 的
   ENOENT 坑),并注入 `createRequire` shim —— 否则 `ws` 的动态 require 会报
   "Dynamic require of events is not supported"
3. `apps/console/package.json`:加 `@nodeagent/client`/`protocol` 依赖 + sidecar:dev/build 脚本
4. `docs/console-plan.md`:补「实施决策」节(决策理由、协议定义、进度表、打包方向)

## 两条协议纪律(写进注释与文档)
- **stdout 只写协议消息**,日志一律 stderr(混入非 JSON 行会让 Rust 侧解析失败)
- 任何异常都转成响应,**绝不因单个请求崩进程**

## 错误形态归一(实测中修正)
被控端对未知能力/ACL 拒绝/超时是回 JSON-RPC error → `client.invoke()` 抛异常。
若原样上抛前端要处理两种形态,故归一为 `{status:'failed',error:{name,message,data?}}`;
而「未连接/缺参数」仍走协议错误(语义不同)。

## 验证(真实链路,非 mock)
本地 agent → sidecar **8/8 全绿**:connect 回传 51 项能力与 agent_version ✓ ·
invoke system.info ✓ · state 反映已连接 ✓ · 事件通知已推送 ✓ ·
非法能力返回 failed 且不崩 ✓ · disconnect ✓ · **stdout 无日志污染** ✓
打包产物拷到仓库外**独立运行通过**(227KB 单文件自足)。

## 待办(下一批)
Rust 壳改造(command 签名不变 → 前端零改动)· tauri.conf.json 的 resource 配置 +
Node 运行时打包 · 退役 crates/nodeagent-client
2026-10-10 15:11:39 +08:00
jkooe e7da258d17 docs: 入库控制台方案与路线图
- docs/console-plan.md:大前端方案(选型取舍、7 页面/7 store、command 表、事件名、
  验收标准 A1-A7、指标、里程碑 M1-M4)。§9.1 记录了 M3 的契约探针对账结果 ——
  9 处静默空列的漂移明细、平台门控设计、以及三段契约闭环的构成。
- docs/ROADMAP.md:能力路线图(含在建项与经验条目)。

两份文档此前因「在建工程」策略暂不入库,随本轮正式入库一并转正。
2026-10-07 10:14:51 +08:00
jkooe 5571cf1aea chore: 取消跟踪误提交的在建工程(保持工作区文件不动)
上一个提交(v1.6)用了 git add -A,把工作区里**不属于本任务的用户在建文件**
一起带入并推送:crates/(Rust 客户端脚手架)、docs/console-plan.md、
docs/ROADMAP.md,以及 docs/RETROSPECTIVE.md 的本地改动。

本提交将它们移出 git 跟踪(磁盘文件全部保留、回到未跟踪状态),
使公开仓库只包含 v1.6 的预期变更。如需从历史中彻底抹去,
可另行 filter-repo 重写(公开仓库,需皇上决策)。
2026-10-07 01:22:58 +08:00
jkooe 2eb33587fe feat(v1.6): 新增 gui.await 等待条件能力(复盘短板之「语义」)
把 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 用法章节。
2026-10-07 01:21:41 +08:00