GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-05-04-netabrowser-cli-s1-design.md
2026-05-20 21:39:12 +08:00

613 lines
29 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# netabrowser-cli S1 基础设施层 设计文档
> Neta 子项目 netabrowser-cli 第一个迭代:浏览器后端 daemon service + CLI 包装 + skill 元数据。
> 不含任何业务 service业务 service 留给 S2/S3
| 元数据 | 值 |
|---|---|
| 子项目 | netabrowser-cli S1基础设施层 |
| 创建日期 | 2026-05-04 |
| 估期 | ~10-12 天 |
| 状态 | 设计完成,待 user 复核 → writing-plans |
| 关联 | geo S1 联调暂停Task 22netabrowser-cli 完成后 geo PlainChromiumProvider 改调 BrowserDaemonService |
---
## 1. 目标
为 Neta 平台提供**反风控 + 拟人化**的浏览器自动化基础设施,统一服务以下三类调用方:
1. **NetaClaw Agent**:通过 `netabrowser-cli` skill命令行调用AI token 友好)
2. **后端业务 service**(如未来的 `XiaohongshuLoginService`):通过 `@Inject BrowserDaemonService` 直接调
3. **未来的电商自动化模块**:同 #2
S1 完成后AI Agent 可以用 cli 命令探索任意网页(小红书/淘宝/抖音/拼多多),后续 S2/S3 把固化的 SOP 封装为业务 service。
## 2. 关键决策brainstorming 阶段已确认)
| 决策点 | 决策 |
|---|---|
| skill 范围 | **三个 skill 并存**playwright-cli + patchwright-cli + netabrowser-cli。前两个保留不动 |
| 拟人化粒度 | **完整拟人化**:贝塞尔轨迹 + 滚动 + 视觉停顿 + 字符间随机间隔 + 偶尔错字回退 |
| 拟人化触发 | **默认拟人化**:一个命令 = 人类行为;提供 `--no-humanize` 用于测试 |
| daemon 进程 | **嵌入 backend**Midway @Provide @Singleton),单进程;崩溃隔离的代价不值 |
| 实施风格 | **Service-First**BrowserDaemonService 为核心CLI 是 thin client 调 backend HTTP后端业务直接 @Inject service |
| chromium 打包 | **内嵌完整打包**Inno Setup [Files]),安装包 ~400MB |
| 拟人化实现 | **ghost-cursor 开源库 + 自加套**:用 ghost-cursor 的贝塞尔轨迹,自己加随机延迟/滚动/停顿/错字模拟 |
| 平台补丁 | S1 **只做通用 CLI**,小红书/抖音/淘宝特定补丁 → S2 |
| S1 范围 | **仅基础设施**daemon service + cli + skill无业务 service |
| 数据库 | local + prod 都用 `neta_test`(用户已确认) |
## 3. 已有架构契合度
Neta 在 2026-04-25 后已有完整 windows-runtime 体系:
| 已有机制 | netabrowser-cli S1 复用方式 |
|---|---|
| `pkg/yao-pkg` 打包 backend.exe | netabrowser-cli 单独 pkg 打包成 `netabrowser-cli.exe` |
| Inno Setup 安装器 | 增加 `[Files]` 段把 `chromium/win64/*` 拷到安装目录 |
| .NET 托盘 | 不动 |
| `comm/config-loader.ts` | 不改netabrowser-cli 通过 backend HTTP 间接读 config |
| `comm/data-dir.ts` | 不改daemon 内拼子路径 `<dataDir>/browser-profiles/``<dataDir>/states/` |
| `comm/runtime-info.ts` | 不改netabrowser-cli 读 runtime-info.json 拿端口 + secret |
| `/app/base/runtime/status` `/stop` | 不动;新增 `/admin/browser-daemon/*` 平行路径 |
| bundled skills 自动入安装包 | netabrowser-cli skill 沿用 |
**意思**S1 完全在现有 windows-runtime 框架内增量构建,不重写打包/托盘/安装器。
---
## 4. 架构与目录
### 4.1 三层职责
```
┌──────────────────────────────────────────────────────────────┐
│ 调用方层S1 范围外) │
│ NetaClaw Agent ←─── netabrowser-cli skill探索期 token 省)│
│ Geo / 电商业务 ←─── @Inject BrowserDaemonService固化期
└──────────────────────────────────────────────────────────────┘
↓ ↓
┌──────────────────────────────────────────────────────────────┐
│ S1 范围 │
│ │
│ packages/netabrowser-cli/ CLI 包装 + Chromium 二进制 │
│ packages/backend/...browser-daemon/ Daemon Service + HTTP │
│ packages/backend/skills/netabrowser-cli/ Skill 元数据 │
└──────────────────────────────────────────────────────────────┘
```
### 4.2 后端模块目录
```
packages/backend/src/modules/netaclaw/browser-daemon/
├── service/
│ ├── daemon.service.ts # ★ BrowserDaemonService核心单例
│ ├── humanizer.service.ts # 拟人化操作包装ghost-cursor + 自加套)
│ └── fingerprint.service.ts # neta-chromium 指纹参数管理
├── controller/admin/
│ ├── session.ts # /open /close /list 会话管理
│ ├── interaction.ts # /click /fill /type /scroll /hover 拟人化交互
│ ├── navigation.ts # /goto /back /forward /reload
│ ├── state.ts # /cookie-list /cookie-set /state-save /state-load
│ └── inspect.ts # /snapshot /screenshot /eval /run-code
├── runtime/
│ ├── chromium-launcher.ts # 启动 neta-chromium 子进程路径解析、proxy、fingerprint args
│ ├── session-registry.ts # Map<sessionName, BrowserContext> 内存表
│ └── cleanup.ts # backend 退出时优雅关闭所有 context
└── config.ts
```
### 4.3 netabrowser-cli 包目录
```
packages/netabrowser-cli/
├── package.json # bin: netabrowser-cli → dist/bin/main.js
├── src/
│ ├── bin/
│ │ └── main.ts # CLI 入口commander 解析)
│ ├── commands/
│ │ ├── open.ts # netabrowser-cli open <url> --session=xx --proxy=xx --fingerprint=xx
│ │ ├── close.ts
│ │ ├── click.ts / fill.ts / type.ts / scroll.ts / hover.ts / press.ts
│ │ ├── cookie.ts # cookie-list / cookie-set / cookie-clear
│ │ ├── state.ts # state-save / state-load
│ │ ├── navigate.ts # goto / back / forward / reload
│ │ ├── snapshot.ts / screenshot.ts / eval.ts / run-code.ts
│ │ └── list.ts # netabrowser-cli list列出所有 session
│ ├── client/
│ │ ├── http-client.ts # 调 backend HTTP API
│ │ └── runtime-info.ts # 解析 backend runtime info 拿端口和 secret
│ └── output/
│ └── formatter.ts # --raw / 默认格式化输出
├── vendor/ # patchright 补丁源码——仅作"未来改造参考",运行时不依赖
│ ├── patchright/ # Kaliiiiiiiiii-Vinyzu/patchrightdriver patches 源码,已 vendor
│ └── patchright-nodejs/ # Kaliiiiiiiiii-Vinyzu/patchright-nodejsclient patches 源码,已 vendor
│ # 运行时直接 npm install patchright编译产物
│ # 当未来要改 patch如加小红书反检测才需要本地跑 vendor 里的 patches 流程
├── chromium/
│ └── win64/ # neta-chromium 二进制 397MB已就位gitignore
├── tests/
└── README.md
```
### 4.4 Skill 目录
```
packages/backend/skills/netabrowser-cli/
├── SKILL.md # 给 NetaClaw Agent 看的命令清单
└── references/
├── humanization.md # 拟人化能力说明
├── fingerprint.md # 指纹参数说明
├── proxy.md # 代理配置说明
└── examples/ # 典型场景示例
```
---
## 5. 核心数据流
### 5.1 启动会话(探索期 AI 路径)
```
NetaClaw Agent 决定打开淘宝
exec("netabrowser-cli open https://www.taobao.com \
--session=geo-1 \
--proxy=http://e50b26:7ecdccfd@210.51.27.112:10000 \
--fingerprint-seed=42 \
--profile-dir=geo-1 \
--headed")
netabrowser-cli/src/bin/main.ts
→ 解析参数
→ 读 runtime-info.json 拿 backend 端口 + secret
→ POST http://127.0.0.1:8003/admin/browser-daemon/open
Headers: x-neta-control-secret: <secret>
Body: {sessionName, url, proxy, fingerprintSeed, profileDir, headed}
backend → AdminBrowserDaemonSessionController
→ @Inject BrowserDaemonService
→ daemonService.open({...})
BrowserDaemonService.open():
1. 校验 sessionName 不重复(查 session-registry
2. 拼 launch args
- executablePath = <安装目录>/chromium/win64/chrome.exe
- args = ['--fingerprint=42', '--fingerprint-platform=Windows', ...]
- userDataDir = <dataDir>/browser-profiles/<profileDir>
- proxy = {server, username, password}
3. patchright chromium.launchPersistentContext() 启动
4. 把 BrowserContext 存入 session-registry
5. 返回 {sessionName, pageCount, url}
HTTP 200 → cli 输出格式化结果给 AI
```
### 5.2 拟人化点击
```
AI: netabrowser-cli click e15 --session=geo-1
cli → POST /admin/browser-daemon/click {sessionName, ref}
daemonService.click(sessionName, ref):
const ctx = registry.get(sessionName);
const page = ctx.pages()[0];
const locator = page.locator(`[data-ai-ref="${ref}"]`);
await humanizer.click(page, locator);
Humanizer.click:
1. 计算目标元素 box 中心
2. ghost-cursor 生成贝塞尔轨迹(鼠标当前位置 → 目标)
3. 沿轨迹移动,每段 5-15ms10% 概率轨迹中段微停顿 50-200ms
4. 到达目标后停顿 100-300ms人类视觉确认
5. mousedown → 50-150ms → mouseup
6. 20% 概率 click 后 micro-scroll 2-10px
返回 {clicked: true, snapshotRef: 'e16'}
```
### 5.3 抓 Cookie + 持久登录态(固化期 service 路径示例)
```ts
// 未来 S3 的 XiaohongshuLoginService不在 S1 范围)
@Provide()
class XiaohongshuLoginService {
@Inject() browserDaemon: BrowserDaemonService;
@Inject() encryptService: GeoEncryptService;
async loginAndCapture(account: GeoAccountEntity) {
const session = `geo-${account.id}`;
await this.browserDaemon.open({
sessionName: session,
url: 'https://www.xiaohongshu.com',
proxy: this.toProxyConfig(account.proxy),
fingerprintSeed: account.fingerprintSeed,
profileDir: `geo-${account.id}`,
headed: true,
});
await this.browserDaemon.click(session, '登录按钮 selector');
await this.browserDaemon.waitForUrl(session, /\/explore\//, 60_000);
const cookies = await this.browserDaemon.getCookies(session, 'xiaohongshu.com');
await this.browserDaemon.saveState(session, `<dataDir>/states/geo-${account.id}.json`);
account.cookies = this.encryptService.encrypt(JSON.stringify(cookies));
account.loginStatus = 'logged_in';
await this.accountRepo.save(account);
await this.browserDaemon.close(session);
}
}
```
**关键点**S3 业务 service 通过 @Inject 直接调 `BrowserDaemonService`,不走 HTTP不走 cli。性能 = 直接 patchright API 调用。
### 5.3.1 service ↔ HTTP 路径等价性约束(强制)
CLI 路径与业务 @Inject 路径必须**严格等价**:调用同一个 BrowserDaemonService 方法,得到同样的副作用。
**强制规则**
1. `controller/admin/*.ts` 必须是 service 方法的 thin wrapper**不允许加任何业务逻辑**参数转换、auth 检查、日志记录都不行)
2. controller 只做 4 件事:① 解析 HTTP body → DTO② 校验 auth见 §5.5);③ 调 `this.daemonService.xxx(dto)`;④ 序列化返回值
3. 业务逻辑(重试、降级、补偿、校验)必须在 service 层实现
4. 测试加 contract test每个 HTTP endpoint 跟同名 service 方法行为一一对照mock serviceverify HTTP 调用 1:1 转发)
### 5.4 Backend 重启时的会话恢复
```
backend 启动 → BrowserDaemonService @Init:
1. 扫描 <dataDir>/states/*.json
2. 不主动 launch避免启动阻塞仅记录 known sessions
3. 当 cli/business 第一次 open(sessionName='geo-1'),发现该 sessionName 有持久化 state自动 loadState
backend 关闭 → BrowserDaemonService @Destroy:
1. 遍历 session-registry
2. 每个 context 先 saveState 到 dataDir
3. context.close() 优雅关闭
4. 超时 5s 强制 kill
```
**重启后状态语义**(重要):
-**可恢复**sessionName、cookie、localStorage通过 storageState 文件)
-**不可恢复**snapshot ref`e15`、page handles、in-memory selectors
→ AI Agent 在 backend 重启后**必须重新调 `snapshot` 命令获取新 ref**。spec/skill 文档明确说明此约束。
### 5.5 HTTP API 鉴权模型
**威胁模型**netabrowser-cli daemon API 能驱动浏览器登录用户社媒账号、抓 cookie权限极高。本机其他进程也可能调用 → 必须鉴权。
**方案**(与 Neta 现有 `/app/base/runtime/*` 一致):
| 层 | 措施 |
|---|---|
| 网络层 | controller 中间件强制 `req.ip === '127.0.0.1'``::1`,非 loopback 直接 403 |
| 凭据层 | 复用 `runtime-info.json` 中的 `controlSecret`(已有机制)。每次 HTTP 请求 header `x-neta-control-secret: <secret>` 必填,不匹配 401 |
| CLI 端 | netabrowser-cli 启动时自动读 `<dataDir>/runtime-info.json` 获取 secret每次请求自动附 header |
| service 层 | @Inject 调用方(业务代码)天然在 backend 进程内,不需要 secret |
**实现锚点**:参考 `packages/backend/src/modules/base/middleware/runtime-control.middleware.ts`runtime-info secret 现有中间件,按需复用或新建一个 `browser-daemon-auth.middleware.ts`)。
### 5.6 并发与会话锁
**问题**cli 和 service 可能同时调 `open(sessionName='geo-1')`,两个请求都看到 sessionName 不存在 → 都启动 chrome → registry 第二次 set 覆盖第一次 → 第一个 chrome 进程残留。
**方案**BrowserDaemonService 内部维护 `Map<sessionName, Promise<void>>` 作为每会话的串行化锁:
```ts
private locks = new Map<string, Promise<void>>();
async withLock<T>(sessionName: string, fn: () => Promise<T>): Promise<T> {
const prev = this.locks.get(sessionName) ?? Promise.resolve();
let release!: () => void;
const next = new Promise<void>(r => (release = r));
this.locks.set(sessionName, prev.then(() => next));
try {
await prev;
return await fn();
} finally {
release();
if (this.locks.get(sessionName) === next) this.locks.delete(sessionName);
}
}
async open(opts) {
return this.withLock(opts.sessionName, async () => {
// 安全地 check-then-launch
});
}
```
**所有 service 方法**open/close/click/fill/cookie-list/...)都在该锁内执行,保证同 sessionName 操作严格串行。
### 5.7 内存与会话调度策略
**问题**100 个 chrome × 200-400MB ≈ 20-40GB RAM普通 16GB 机器扛不住。
**策略**
| 维度 | 决策 |
|---|---|
| **软上限** | 默认同时活跃 session 数上限 = `min(物理内存GB / 0.5, 50)`。可通过 `config.yaml``browserDaemon.maxActiveSessions` 覆写 |
| **空闲回收** | session 超过 60min 无命令 → 自动 saveState 后 close释放 chrome 进程内存);下次 open 同 sessionName 自动 loadState 重启 |
| **触达上限** | 新 open 请求 → 先 LRU 回收最久未使用的 idle session如全部 active 中无 idle 可回收 → 返回 503 + 重试建议 |
| **优先级** | DTO 加 `priority?: 'low' \| 'normal' \| 'high'` 字段high 优先级抢占 low 优先级的 session 位 |
| **拒绝策略** | 默认是 fail-fast503DTO 可选 `--queue=true` 让请求排队等位(最长 30s |
| **指标** | service 暴露 `getStats()`activeCount/idleCount/totalRamMB/queueLength供监控面板和决策使用 |
**实现锚点**`runtime/session-scheduler.ts` 实现 LRU + 软上限 + 队列。
### 5.5 错误处理
| 场景 | 处理 |
|---|---|
| sessionName 已存在 | 返回 409 Conflict + 现有 session 信息 |
| 找不到 chrome.exe | 抛 `ChromiumNotInstalled`,提示重新安装 |
| chrome 启动失败 | 抛 `LaunchFailed`,附带 chromium stderr 最后 50 行 |
| 元素 ref 找不到 | 抛 `RefNotFound`,附带最近 snapshot 相邻元素 |
| 拟人化轨迹超时10s | 降级直接 click记 warn |
| backend 崩溃 | 所有 context 跟着崩;持久化 state 仍在;下次启动重新 launch |
---
## 6. 拟人化与指纹细节
### 6.1 拟人化能力清单(默认开启)
| 操作 | 行为 |
|---|---|
| **click** | ghost-cursor 贝塞尔轨迹 → 停顿 100-300ms → mousedown(50-150ms) → mouseup |
| **type** | 字符间随机间隔 80-250ms5% 概率模拟错字 → backspace → 重输 |
| **fill** | 先 click focus再走 type 流程 |
| **press** | 按下后 30-100ms 抬起 |
| **hover** | 贝塞尔轨迹移到目标,停留 200-800ms |
| **scroll** | wheel events 而非 jump-to-position多次 30-80px间隔 50-150ms |
| **goto** | 页面 load 后随机停留 1-3s |
| **idle**(隐式) | 每命令完成后 50-200ms 默认延迟 |
### 6.2 拟人化的三档可调
**问题**:完整拟人化每命令 +2-5s。100 账号 × 30 操作 = 4 小时纯延迟,**批量场景会卡死**。
**方案**:三档 `--humanize-mode`
| 档位 | 行为 | 单命令开销 | 适用 |
|---|---|---|---|
| **`full`**(默认) | 贝塞尔轨迹 + 视觉停顿 + 字符间隔 + 错字回退 + micro-scroll | 2-5s | 养号、敏感操作、AI 探索 |
| **`fast`** | 保留延迟200-500ms但**去掉**鼠标轨迹、错字、micro-scroll | 0.3-0.7s | 批量发布、批量评论、批量数据采集 |
| **`off`** | 完全关闭,立即执行 | <100ms | 测试CI |
**触发方式**
```bash
# 命令级(最高优先级)
netabrowser-cli click e15 --humanize-mode=fast
# 会话级open 时设默认值)
netabrowser-cli open <url> --session=xx --humanize-mode=fast
# 全局环境变量
NETA_BROWSER_HUMANIZE_MODE=fast netabrowser-cli click e15
# 兼容老开关
netabrowser-cli click e15 --no-humanize # 等价于 --humanize-mode=off
```
**实现要点**humanizer 接受 `mode: 'full' | 'fast' | 'off'`每个动作内部按 mode 分支选择行为
### 6.3 ghost-cursor 集成
```ts
import { createCursor } from 'ghost-cursor';
class Humanizer {
async click(page: Page, locator: Locator) {
const box = await locator.boundingBox();
if (!box) throw new Error('Element not visible');
const cursor = createCursor(page as any, undefined, true);
await cursor.moveTo({ x: box.x + box.width/2, y: box.y + box.height/2 }, {
randomizeMoveDelay: true,
moveDelay: random(50, 150),
});
await sleep(random(100, 300));
await page.mouse.down();
await sleep(random(50, 150));
await page.mouse.up();
if (Math.random() < 0.2) {
await page.mouse.wheel(0, random(2, 10));
}
}
}
```
### 6.4 neta-chromium 指纹参数
```bash
netabrowser-cli open <url> \
--session=geo-1 \
--fingerprint-seed=42 # 单一 seed 自动派生所有维度(推荐)
# 或细粒度控制:
--fingerprint-platform=Windows \
--fingerprint-platform-version=10.0.0 \
--fingerprint-brand=Chrome \
--fingerprint-brand-version=144 \
--fingerprint-hardware-concurrency=8 \
--fingerprint-language=zh-CN \
--fingerprint-timezone=Asia/Shanghai
```
底层翻译为 chrome.exe 命令行参数已通过 chrome 144.0.7559.132 验证seed=11111 seed=22222 产生不同 canvas/webgl 指纹)。
### 6.5 代理参数
```bash
# 完整代理 URL最常用
netabrowser-cli open <url> --session=xx \
--proxy=http://e50b26:7ecdccfd@210.51.27.112:10000
# 拆分参数
--proxy-server=http://210.51.27.112:10000 \
--proxy-username=e50b26 \
--proxy-password=7ecdccfd
```
底层用 patchright `launchPersistentContext({proxy: {...}})`已验证出口 IP 真实变成代理 IP
### 6.6 会话生命周期
| 状态 | 触发 | 行为 |
|---|---|---|
| **created** | `open` 调用 | 内存 context + 持久 profileDir + session-registry 记录 |
| **active** | 同上 | 等待命令 |
| **closed** | `close` 调用 / backend 退出 | saveState context.close() registry 删除 |
| **idle-timeout** | 60min 无命令 | 自动 saveState 到磁盘关闭 context下次 open sessionName 自动 loadState |
`--no-idle-timeout` 关闭自动空闲回收养号常驻场景)。
---
## 7. 集成 + 打包
### 7.1 与 Neta 现有代码集成清单
| 组件 | 改动 |
|---|---|
| `pnpm-workspace.yaml` | `packages/netabrowser-cli` |
| `packages/backend/package.json` | `patchright`npm 编译产物****依赖 vendor patches 源码+ `ghost-cursor` 依赖 |
| `packages/backend/src/modules/netaclaw/` | 新增 browser-daemon 子目录 |
| `packages/backend/src/comm/data-dir.ts` | **不改**daemon 内拼子路径 |
| `packages/backend/src/comm/runtime-info.ts` | **不改** |
| `packages/backend/installer/setup.iss` | 增加 `[Files]``chromium/win64/*` |
| `packages/backend/scripts/build-windows-installer.js` | 增加复制 chromium 二进制 |
| `packages/backend/scripts/pkg-build.js` | **不改**netabrowser-cli 单独 pkg 打包 |
| `packages/backend/skills/netabrowser-cli/` | 新增 skill 目录 |
### 7.2 chromium 路径解析
```ts
function resolveChromiumPath(): string {
if (process.env.NETA_CHROMIUM_PATH) return process.env.NETA_CHROMIUM_PATH;
if (isPkg()) {
return path.join(path.dirname(process.execPath), 'chromium', 'win64', 'chrome.exe');
}
return path.resolve(__dirname, '../../../../../../netabrowser-cli/chromium/win64/chrome.exe');
}
```
### 7.3 安装包目录结构prod 模式)
```
C:\Program Files\Neta\
├── backend.exe
├── Neta.Tray.exe
├── netabrowser-cli.exe ★ 新增
├── config.yaml
├── chromium\
│ └── win64\ ★ 新增 397MB
│ ├── chrome.exe / chrome.dll / ...
├── data\
│ ├── browser-profiles\ # 每会话独立 profile
│ ├── states\ # 持久化登录态
│ ├── logs\
│ └── runtime-info.json
└── skills\
├── netabrowser-cli\ ★ 新增
├── playwright-cli\
└── patchwright-cli\
```
---
## 8. 测试策略
| | 方法 |
|---|---|
| **Humanizer 单元** | mock page验证 ghost-cursor 调用 + 延迟范围 + 错字回退 |
| **BrowserDaemonService 单元** | mock patchright验证 session 注册/launch args 拼装/清理 |
| **chromium-launcher 单元** | 验证路径解析dev/pkg/env 三种|
| **Controller 集成** | jest + supertest open click cookie closemock page|
| **CLI bin 单元** | mock HTTP验证参数解析output formatter |
| **真机冒烟** | 手工启动 backendcli `open https://httpbin.org/ip --proxy=...`验证出口 IP访问 https://abrahamjuliot.github.io/creepjs/ trust score |
---
## 9. 验收标准
1. `pnpm dev` 启动 backend`<dataDir>/runtime-info.json` 写入
2. `netabrowser-cli list` 返回空 `[]`
3. `netabrowser-cli open https://httpbin.org/ip --session=test --proxy=http://e50b26:7ecdccfd@210.51.27.112:10000` 返回 200出口 IP **210.51.27.112**
4. `netabrowser-cli open https://creepjs.com --session=test --fingerprint-seed=42 --headed` 启动有头浏览器creepjs **trust score ≥ 70%**
5. `netabrowser-cli click <ref> --session=test` 触发拟人化轨迹mock 验证 + 真机肉眼
5b. `--humanize-mode=fast` 单命令延迟 < 1s`--humanize-mode=off` 单命令延迟 < 200ms
5c. HTTP API 不带 `x-neta-control-secret` header 401 loopback 403
5d. sessionName 并发 open 两次第二次返回 409 Conflict锁机制有效
5e. 软上限触达后 open 返回 503 `retryAfter` 字段`--queue=true` 时排队等位
6. `netabrowser-cli cookie-list --session=test --domain=httpbin.org` 返回 cookie JSON
7. `netabrowser-cli state-save --session=test --output=/tmp/state.json` 写文件`state-load` 能恢复
8. `netabrowser-cli close --session=test` 关闭`list` 不再显示
9. backend 重启后 saveState session 通过 `open --session=同名` 自动 loadState 恢复
10. Inno Setup 打包后的安装包安装到全新 Windows启动后能跑通 1-9
11. Skill 元数据完整SKILL.md 命令清单 + references/NetaClaw Agent 能识别
---
## 10. 范围红线(不在 S1
- 平台特定补丁小红书/抖音/淘宝特殊反风控)→ S2
- 业务 serviceXiaohongshuLoginService )→ S2/S3
- geo BrowserAutomationService 迁移 S2 单独 spec
- macOS / Linux 支持chromium 只打包 win64)→ 后续
- chrome 实例分布式调度 后续
- skill SKILL.md 国内平台示例仅留通用例子)→ S2
- 替换/删除现有 playwright-cli / patchwright-cli skill 不动
---
## 11. 风险与依赖
| 风险 | 缓解 |
|---|---|
| 上游 patchright 修复 bug npm 包发布慢 | 通常上游 release npm publish 间隔几天可接受急用时启用 vendor patches 本地构建 |
| 国内场景出现 patchright 默认未覆盖的反检测 | vendor patches 源码是改造起点那时下载 playwright 源码本地跑 patches 流程发布到 monorepo 内部 npm |
| ghost-cursor 不兼容 patchright | 已知 ghost-cursor 接受 puppeteer-style pagepatchright 兼容冒烟阶段实测 |
| 安装包 400MB 用户嫌大 | 安装界面提示"含浏览器内核 ~300MB"后续可选"在线安装版" |
| chrome.exe 进程残留 | daemon @Destroy 优雅关闭 + 启动时扫 chromium/win64/chrome.exe 进程清理 |
| 100 账号同时启动 OOM | session-registry 设上限 50超过自动 idle-timeout 回收最久未用的 |
| AI 误调拟人化命令导致超慢 | 拟人化每命令 +2-5sfull提供 `--humanize-mode=fast/off` 三档兜底SKILL.md 明确说明性能特征与档位选择 |
| chromium 144.0.7559.132 不是真 fingerprint-chromium | 已验证seed=11111 seed=22222 产生不同 canvas/webgl确认是真 fingerprint-chromium |
| **AI ref 协议e15依赖 playwright-cli 内部状态patchright 无现成实现** | **plan 阶段 spike #1**选定方案自实现 ref 协议 / 改用 selector / vendor playwright-cli ref 代码|
| **ghost-cursor 是为 puppeteer/playwright 设计patchright 改了 mouse.* 实现,可能不兼容** | **plan 阶段 spike #2**跑一个 demo patchright + neta-chromium 上用 ghost-cursor click验证轨迹和反检测都生效 |
---
## 12. 工作流
完成本 spec
1. user 复核本文档
2. 调用 `superpowers:writing-plans` 生成 `docs/superpowers/plans/2026-05-04-netabrowser-cli-s1-plan.md`
3. **plan 第一阶段必须包含两个 spike**在写实现代码前完成
- **Spike #1AI ref 协议**——验证一种方案:① 自实现snapshot inject `data-ai-ref` DOM + 内存 refselector );② 改用 selector 字符串作为命令参数;③ vendor playwright-cli ref 协议代码进 netabrowser-cli。**spike 产出**选定一种 + demo 跑通 click(e15) 整套流程
- **Spike #2ghost-cursor on patchright + neta-chromium**——demo patchright launchPersistentContext 启动 neta-chromium ghost-cursor 在页面上轨迹移动鼠标 click 一个按钮 验证 鼠标确实走贝塞尔轨迹 brotector/creepjs 上仍过反检测
4. user 复核 plan
5. `superpowers:subagent-driven-development` 实施
6. `superpowers:verification-before-completion` 逐条核对验收标准
7. `superpowers:requesting-code-review`
8. 完成后回头继续 geo S1 联调Task 22geo PlainChromiumProvider 改调 BrowserDaemonService
---
## 13. 变更日志
| 日期 | 变更 |
|---|---|
| 2026-05-04 | 初稿brainstorming 完成后产出 |
| 2026-05-04 | 架构 review v2 修复 7 项必修:①patchright 改用 npm +vendor 仅参考 HTTP API auth 模型loopback+secret serviceHTTP 等价性约束 sessionName 串行化锁 内存调度策略LRU+软上限+优先级 拟人化三档full/fast/off backend 重启后 ref 必须重 snapshotplan 阶段加两个 spikeAI ref 协议 + ghost-cursor 兼容 |