Files
nodeagent/docs/console-plan.md
T
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

20 KiB
Raw Blame History

nodeagent 大前端开发方案(桌面操作台)

版本 v1.0 | 日期 2026-10-07 | 状态:待皇上评审 定位:为「Mac 控 Windows」补上人肉可视化操作台——CLI 是脚本底座、MCP 是 AI 落点、本方案是皇上自己点按钮的桌面界面。


1. 一句话结论

用 Tauri 2 + Vue 3 + TypeScript + Vite 造一套跨端桌面应用,同一份代码编译出 **Mac 版(控制端操作台)**与 Windows 版(被控端本地面板);Rust 后端复刻 @nodeagent/client 的握手/RPC/事件逻辑,经 Tauri command 暴露给 Vue 前端。首期覆盖 核心管控 + 文件管理 + 审计。


2. 技术栈选型(含取舍)

层 选型 版本 选型理由
桌面壳 Tauri 2 2.x 跨端(Mac/Win)+ 性能好 + 体积小;Rust 后端契合皇上「Rust 加速」技术栈
前端框架 Vue 3 3.5+ 皇上既有 Vue2/Vue3 经验(reader、FinDataHub),迁移成本最低
语言 TypeScript 5.x 与 monorepo 既有 TS 生态对齐,复用协议类型定义
构建 Vite 6.x Tauri 官方模板默认,HMR 快
状态管理 Pinia 2.x Vue3 官方推荐,模块化 store 天然契合多面板
路由 Vue Router 4.x 多页面(总览/终端/文件/审计…)需要
组件库 Naive UI 2.x 暗色主题友好、TS 类型完整、可自定义涨红跌绿
图表 ECharts 5.x 资源趋势/磁盘/指标可视化,涨红跌绿可配
Rust 客户端 tokio + tokio-tungstenite + rustls — 复刻 client.ts 的 WebSocket 握手与 RPC
Rust 密码学 hmac + sha2 + ed25519-dalek + x509-parser — HMAC 挑战应答、ed25519 签名、证书指纹钉住

为什么 Tauri 而不是 Electron / 纯 Web

维度 Tauri 2 Electron 纯 Web(浏览器)
跨端 Mac+Win ✅ ✅ ✅(但无桌面壳)
打包体积 ~10MB 级 ~100MB+ 无
内存占用 低(复用系统 WebView) 高(自建 Chromium) 视浏览器
性能 好(Rust 后端) 中 受浏览器沙箱限制
直接复用 Node SDK ❌(需 Rust 版 client) ✅(主进程可 import) ❌(需浏览器版)

取舍:Tauri 需要新写一份 Rust 版 client(协议本身是 JSON-RPC over WebSocket,语言无关,逻辑对齐已跑通的 client.ts 即可),换取体积、内存、性能上的长期优势——与皇上「性能好 + 跨端」的要求一致。


3. 整体架构

┌─────────────────────────── apps/console(桌面应用,同一份代码两端编译) ───────────────────────────┐
│                                                                                                    │
│  Vue 3 前端(src/)                     Tauri Rust 壳(src-tauri/)                                  │
│  ┌─────────────────────┐   IPC invoke   ┌──────────────────────────────┐                            │
│  │ 视图 Views           │ ────────────► │ commands/*.rs(薄命令层)      │                            │
│  │ 组件 Components      │ ◄──────────── │   │                          │                            │
│  │ Pinia stores         │   event 推送   │   ▼                          │                            │
│  │ api 调用层           │ ◄──────────── │ state.rs(连接管理器)         │                            │
│  └─────────────────────┘               │   │                          │                            │
│                                         │   ▼                          │                            │
│                                         │ crates/nodeagent-client      │ ── WebSocket ──► 被控端      │
│                                         │ (Rust 版 client:握手/RPC/    │   (wss://IP:8765)  Windows    │
│                                         │  事件/重连/证书钉住/ed25519)   │                            │
│                                         └──────────────────────────────┘                            │
└────────────────────────────────────────────────────────────────────────────────────────────────────┘

关键设计:

  1. 薄壳厚前端:Rust 壳只做「连接管理 + 协议桥接」,业务展示逻辑全在 Vue 前端。
  2. 事件推送走 Tauri event:被控端主动事件(v12 event.*)、连接状态变化,由 Rust 后端转发为 Tauri event,前端 listen() 订阅,避免轮询。
  3. 能力调用统一 invoke:所有 42 项能力都走一个泛型 invoke(capability, args) 通道,前端按能力清单动态渲染参数表单,不逐能力手写 command。

4. 功能模块与页面清单(首期 = 核心管控 + 文件 + 审计)

# 页面/模块 路由 支撑能力 功能点
1 设备管理 /nodes discover、nodes 配置、connect 局域网发现、设备列表、连接/断开、增删改节点、设默认设备、证书指纹展示与重钉
2 状态总览 Dashboard /overview system.info / system.status / system.metrics / system.audio.get 连接状态、主机名/OS/CPU/内存/开机时长、磁盘用量环图、网络适配器、PRD 成功指标(闭环率/P95/拦截率)
3 命令执行(终端) /terminal system.shell.exec、system.task.* 交互式 PowerShell 终端、退出码/耗时、超时可配、长命令后台化 + 任务列表/续读/终止
4 进程与服务 /processes system.process.list / system.service.list 进程/服务列表,按 CPU/内存/PID/名称排序,按名筛选,按状态筛选
5 软件管理 /software app.list / app.install 已装软件列表与版本、winget 静默安装(输入包名/ID,返回结果+版本)
6 文件管理 /files fs.list / fs.stat / fs.read / fs.write 目录树浏览、文件预览(分块读)、上传/下载(分块 + 进度)、新建/覆盖/追加写入、元信息
7 审计日志 /audit system.audit.list / system.audit.verify 审计列表分页、按类型/调用方/时间筛选、完整性校验(verify)、导出 JSONL

Windows 版侧重(待确认):同一套代码,Windows 版默认聚焦被控端本地面板——本机连接状态、授权(ACL/allow_input 输入开关)、本机资源、日志、自更新入口。因协议点对点对称,Windows 版同样可作控制端。


5. 数据接口与状态管理

5.1 Rust 版 client(crates/nodeagent-client)对外 API

方法 说明 对齐 client.ts
Client::connect(profile) -> NodeInfo 握手:hello → challenge → auth → auth_ok connect()
Client::invoke(capability, args, timeout) -> InvokeResult 泛型能力调用 invoke()
Client::list_capabilities() / list_authorized() 能力/授权清单 listCapabilities()
Client::set_event_handler(fn) 订阅被控端事件(无 id 通知) onEvent
Client::on_state_change(fn) 连接/重连/关闭状态回调 onStateChange
Client::close() 主动断开(停自动重连) close()
Client::peer_cert_fingerprint() 对端证书指纹(TOFU 钉住) getPeerCertFingerprint()

复刻要点(已跑通,勿重蹈):指数退避重连 1s→30s ±15% 抖动;证书指纹 sha256 比对(E_CERT_MISMATCH);ed25519 模式需私钥;Hub 配对透传。

5.2 Tauri command(Rust 壳 → 前端 IPC)

Command 入参 返回 说明
discover wait_ms Node[] UDP 8766 发现
connect_node node_id NodeInfo 连接并握手
disconnect — () 断开
invoke capability, args, timeout_ms? InvokeResult 泛型能力调用
list_nodes / add_node / remove_node / set_current profile 结构 配置 设备 CRUD(写 ~/.nodeagent/config.json)
get_conn_state — ConnState 当前连接状态
get_capabilities — Capability[] 能力/授权清单

5.3 事件推送(Tauri event,Rust → 前端)

事件名 载荷 触发
node-event 被控端事件(kind/action/target/detail) 被控端 event.* 主动推送
conn-state connected/reconnecting/closed 连接状态变化
task-progress task_id, offset, output 长任务增量输出

5.4 Pinia store 设计

Store 职责 关键 state
useConnStore 连接状态、当前节点、能力/授权清单、证书指纹 state, nodeId, capabilities, authorized, certSha256
useNodeStore 设备配置列表、发现结果、分组 nodes, current, groups, discovered
useSysStore 系统信息/状态/指标缓存(可手动刷新) info, status, metrics
useFileStore 当前目录、文件列表、选中项、上传下载进度 cwd, entries, selection, transfers
useAuditStore 审计分页与筛选 items, page, filter
useEventStore 事件流(订阅 + 游标续拉) watches, events, cursor
useTaskStore 后台长命令任务 tasks

数据流:组件 → Pinia store → api/(封装 invoke)→ Tauri command → Rust client → 被控端;反向经 Tauri event → store 更新 → 响应式视图。高频指标(status/metrics)由 store 内定时器轮询,可配置周期;事件类数据走推送,不轮询。


6. 代码结构(monorepo 新增)

nodeagent/
├── apps/
│   └── console/                    # 桌面应用(新增)
│       ├── package.json
│       ├── vite.config.ts
│       ├── index.html
│       ├── src/                    # Vue 3 前端
│       │   ├── main.ts / App.vue
│       │   ├── router/index.ts     # 7 条路由
│       │   ├── views/              # Nodes / Overview / Terminal / Processes / Software / Files / Audit
│       │   ├── components/         # 可复用组件(设备卡片、参数表单、状态徽章、进度条…)
│       │   ├── stores/             # Pinia(5.4 节 7 个 store)
│       │   ├── api/                # invoke 封装 + 类型(对齐 Rust command)
│       │   ├── types/              # 能力清单、InvokeResult、NodeProfile 等类型镜像
│       │   └── utils/              # 格式化、涨红跌绿、分块编排
│       └── src-tauri/              # Rust 壳
│           ├── Cargo.toml
│           ├── tauri.conf.json
│           └── src/
│               ├── main.rs / lib.rs
│               ├── state.rs        # 连接管理器(持有 client)
│               └── commands/       # 5.2 节 command 实现
├── crates/
│   ├── nodeagent-client/           # Rust 版 client(新增,5.1 节)
│   │   ├── Cargo.toml
│   │   └── src/{lib.rs, client.rs, handshake.rs, tls.rs, events.rs}
│   └── nodeagent-protocol/         # (可选)协议类型/能力清单的 Rust 镜像
└── packages/… apps/{agent,cli,hub,mcp}   # 现有,不动

约束:Rust client 与 Node client(packages/client)行为一致,两端共享 ~/.nodeagent/config.json 与 keys/,不新造存储位置;能力清单以 packages/protocol/src/capabilities/manifest 为唯一事实源,Rust 侧做类型镜像并对齐。


7. 交付范围

In scope(首期 v1)

# 交付物 说明
1 crates/nodeagent-client Rust 版 client,对齐 client.ts 全部行为,含单元测试
2 apps/console 桌面应用 Tauri + Vue3,7 个页面,Mac/Win 双端可编译
3 核心管控 设备管理、总览、终端、进程/服务、软件安装
4 文件管理 浏览/预览/上传/下载(分块)/写入
5 审计 列表分页筛选、完整性校验、导出
6 打包脚本 Mac .app + Windows .msi/.exe(复用现有 pack.mjs 风格)

Out of scope(后续版本)

# 内容 归属
1 图形接管 UI(实时截图/键鼠/窗口定位/宏回放) v2
2 网络两阶段变更、音频控制、自更新面板 v2
3 事件流订阅的可视化(watch/list/poll 面板) v2
4 应用签名/公证/自动更新 独立发布项
5 多设备批量下发(fanout/分组)面板 v2

8. 验收标准

8.1 功能验收(Given/When/Then)

# 验收项 标准
A1 设备连接 Given 已配置被控端,When 点击连接,Then 3s 内返回能力清单并进入总览;密钥错误返回 E_AUTH_FAILED 且界面明确提示
A2 状态总览 Given 已连接,When 打开总览,Then 正确展示 CPU/内存/磁盘/网络与 PRD 指标,且数据带时间戳
A3 命令执行 Given 已连接,When 输入合法 PowerShell,Then 返回 exit_code/stdout/stderr/duration_ms;超时命令返回 E_TIMEOUT 且无残留进程
A4 软件安装 Given winget 可用,When 输入包名,Then 返回安装结果+版本;不存在时给出明确失败原因
A5 文件上传/下载 Given 已连接,When 上传大文件(>8MB),Then 分块写入成功且校验 sha256 一致;下载支持断点续读
A6 审计 Given 已连接,When 打开审计页,Then 分页展示并可筛选;verify 返回完整性结论
A7 断线重连 Given 已连接,When 拔网再恢复,Then 指数退避自动重连并回到 connected,界面状态同步

8.2 指标验收

指标 口径 目标
命令闭环成功率 成功返回 ÷ 发起 ≥ 95%
内网调用 P95 时延 前端点击到结果渲染 < 3s
应用内存占用 Mac 版空闲常驻 RSS < 150MB(远低于 Electron)
打包体积 单端安装包 Mac .app < 30MB / Win 安装包 < 40MB
双端编译 pnpm build 一次产出两端 100%

9. 里程碑拆分

阶段 内容 退出标准
M1 Rust client crate + 握手/RPC/事件/重连 + 单测 与现有 test:e2e 协议用例对齐通过
M2 Tauri 壳 + command 层 + 设备管理/总览/终端三页 真机连接成功、能跑命令、总览数据正确
M3 文件管理 + 审计 + 软件/进程服务页 A5/A6/A4 验收通过
M4 Mac/Win 双端打包脚本 + 文档 A 全部通过、指标达标

9.1 M3 实测:契约探针与漂移修正(2026-10-07)

M3 七页写完后,用 crates/nodeagent-client/examples/probe.rs 对真实被控端逐项调用, 输出字段契约(顶层键 + 数组首元素键)而非数据预览,再与前端类型逐项比对。

cargo run --example probe -- ws://127.0.0.1:8765 <key> /tmp/nodeagent-e2e
═══ 通过 10 · 失败 0 ═══

便捷入口:pnpm probe -- ws://127.0.0.1:8765 <key> /tmp/nodeagent-e2e (等价于 cargo run --manifest-path crates/nodeagent-client/Cargo.toml --example probe -- <url> <key> [base]; --full 可打印完整 JSON) 起本地被控端:NODEAGENT_HOME=/tmp/nodeagent-e2e node apps/agent/dist/index.js

探针结果(被控端 = macOS,故 Windows 专属能力预期失败):

能力 结果 实测字段
fs.list ✓ entries[{name,path,type,size,mtime}], total, truncated
fs.stat ✓ exists,mtime,path,size,type
fs.read ✓ bytes,data,encoding,eof,offset,sha256,total_bytes
fs.write ✓ path,total_bytes,written
system.audit.list ✓ entries[{args_digest,capability,client_id,duration_ms,hash,prev,remote,status,ts,type}], total, file
system.audit.verify ✓ checked,legacy,ok
app.list / app.install ✗ 预期 E_UNSUPPORTED_PLATFORM(非 Windows)
system.process.list ✓ processes[{cpu_pct,memory_bytes,name,pid,started_at}]
system.service.list ✗ 预期 E_UNSUPPORTED_PLATFORM(非 Windows)
system.info ✓ os 返回 macOS(平台门控的判断锚点)
system.status ✓ disks[{drive,free,total,used_pct}], net[{adapter,ip,up}]
system.shell.exec ✓ duration_ms,exit_code,killed,stderr,stdout,truncated

抓到的漂移(均为静默空列,非运行时报错)

页面 前端原读 契约真源 后果
进程表 CPU 列 r.cpu cpu_pct 恒为 -
进程表 内存列 r.memory memory_bytes 恒为 -
进程表 已运行 (无) started_at 缺列
服务表 状态列 r.status state 恒为 -
总览 磁盘盘名 d.mount || d.name drive 恒空白
总览 磁盘已用 d.used 无该字段,须算 total - free 恒为 -
总览 网卡 {name,rx_bytes,tx_bytes} {adapter,ip,up} 整块错位
软件表 ID 列 a.id publisher(结果里没有 id) 整列恒空
审计断点 broken_at 当数字渲染 {file,line,reason} 对象 渲染成 [object Object]

修正与固化

  1. 类型镜像 apps/console/src/types/index.ts 全部按契约改写; ProcessEntry/ServiceEntry/DiskInfo/NetInfo/AppEntry/AuditEntry/BuildInfo 等。
  2. 平台门控:conn.ts 导出 WINDOWS_ONLY_CAPS,连接后自动探测 system.info.os → isWindows/platformReady; 软件页与服务 Tab 事前禁用并说明原因,而不是发出注定失败的请求再解释错误。
  3. 结构化错误:api.CapabilityFailure 携带 error.name(协议真源 InvokeResult.error 只有 {name,message,data?},没有 code),提供 isUnsupportedPlatform / isAclDenied / isRateLimited。
  4. 守卫测试 tests/unit/console-contract.test.mjs(20 用例):
    • 正向:console 声明的字段 ⊆ 该能力 returns_schema 的字段 → 抓「声明了不存在的字段」
    • 反向:returns_schema 字段 ⊆ console 声明 → 抓「契约加了字段、前端没跟上」
    • 平台门控清单与 agent 侧实际抛 UNSUPPORTED_PLATFORM 的能力集合一致
    • 审计 AuditType 覆盖 apps/agent/src/audit.ts 的全部取值
    • 已注入漂移实测:把 cpu_pct 改回 cpu → 正向+反向各挂 1 项,确认非空转守卫。

10. 风险与对策

# 风险 概率 影响 对策
R1 Rust 版 client 与 Node 版行为漂移 中 高 以 packages/protocol 能力清单为唯一事实源;协议用例双端共跑
R2 Tauri 下自签证书/TLS 处理差异 中 中 复刻 client.ts 的 insecure + 指纹钉住逻辑,单测覆盖 E_CERT_MISMATCH
R3 Windows 端定位歧义 中 中 见第 4 节「待确认」,评审时定死再做
R4 大文件分块上传稳定性 中 中 复用已有分块能力;前端做进度+断点,M3 真机压测

11. 待确认项(请皇上评审时一并拍板)

# 事项 臣的建议
C1 Windows 端桌面应用的定位 被控端本地面板(默认侧重)+ 对称可作控制端
C2 组件库 Naive UI(暗色友好、TS 完整);若皇上惯用 Element Plus 可换
C3 图表库 ECharts(资源趋势/磁盘);确认后纳入 M2
C4 是否同步做图形接管 UI 建议 v2 再做,首期聚焦闭环

仅供参考,本方案为技术规划,不构成任何投资建议。