{"owner":"epiral","repo":"bb-browser","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# bb-browser Agent 开发规范\n\n## 架构\n\n```\nCLI ──HTTP──▶ Daemon ──controlConn CDP──▶ Chrome\n                │\n                │ --hub 模式 (可选)\n                ├── Hub ProviderStream ──▶ Pinix Hub\n                │\n                │ Protocol 2\n                └── Streamer (bb-viewer) ──captureConn + inputConn CDP──▶ Chrome\n                         │\n                         └── WebRTC video + DataChannel ──▶ Clip Web UI\n```\n\n**三根 CDP 连接到同一个 Chrome：**\n\n| 连接 | Owner | 职责 |\n|------|-------|------|\n| controlConn | daemon | Agent 操作：tab/nav/site/DOM/click-by-ref |\n| inputConn | streamer | Human 实时输入：鼠标/键盘/IME (DataChannel → CDP) |\n| captureConn | streamer | 视频流：screencast → VP8 → WebRTC |\n\n**组件：**\n\n| 组件 | 位置 | 职责 |\n|------|------|------|\n| CLI | `packages/cli/` | 命令行入口，HTTP 调 daemon |\n| Daemon | `packages/daemon/` | 控制中心：CDP controlConn、HTTP API、Hub 连接、site 执行、streamer 管理 |\n| Shared | `packages/shared/` | 统一命令定义 (`commands.ts`)、协议类型 (`protocol.ts`) |\n| Streamer | `bb-viewer` repo | 纯视频 + 实时输入（Go binary，daemon 子进程） |\n\n## Daemon 两种模式\n\n```bash\n# 本地模式：Agent 用 CLI 操作浏览器\nbb-browser daemon start\n\n# Hub 模式：注册到 Pinix Hub，远程可用\nbb-browser daemon start --hub https://hub.pinixai.com --hub-token xxx\n```\n\n## 协议\n\n### Protocol 1: CLI ↔ daemon (`POST /command`)\n\n请求：`{\"method\": \"snap\", \"params\": {\"tab\": \"3ef9\"}}`\n成功：`{\"result\": {\"tab\": \"3ef9\", \"title\": \"Google\", \"snapshot\": \"...\"}}`\n失败：`{\"error\": {\"message\": \"Missing --tab\", \"hint\": \"Run 'bb-browser tab list'\"}}`\n\n### Protocol 2: daemon ↔ streamer (`POST /command`)\n\n| Method | Params | 说明 |\n|--------|--------|------|\n| connect | {cdpUrl, ice?} | 连接 CDP，创建 WebRTC peer |\n| answer | {answer_sdp, candidates} | 完成 WebRTC 信令 |\n| switch | {cdpUrl} | 切到新 tab（peer 不变） |\n| stop | {} | 停止 |\n\n## 统一命令定义\n\n`packages/shared/src/commands.ts` 是所有命令的单一定义源。每个命令包含 `method`、`group`、`description`、`requiresTab`、`params`。CLI 解析、daemon dispatch、Hub 注册都从这里读取。\n\n添加新命令：\n1. `commands.ts` — 添加 CommandDef\n2. `protocol.ts` — 添加 ActionType\n3. `command-dispatch.ts` — 添加处理分支\n4. `packages/cli/src/commands/<name>.ts` + `index.ts` — CLI 命令\n\n## CLI 命令\n\n`--tab` 必填（除 `open`、`site` 组、`tab list/new`、`daemon`）。\n\n| 组 | 命令 |\n|----|------|\n| 导航 | `open <url> [--tab]`, `back --tab`, `forward --tab`, `reload --tab`, `close --tab` |\n| 观察 | `snap --tab`, `screenshot --tab`, `get <attr> --tab`, `eval <js> --tab` |\n| 交互 | `click/hover/fill/type/press/scroll/check/uncheck/select --tab` |\n| Tab | `tab list`, `tab new [url]` |\n| Site | `site list`, `site info <name>`, `site run <name>` |\n| 调试 | `network/console/errors/trace/cookies/source --tab` |\n| 进程 | `daemon start [--hub]`, `daemon stop`, `daemon status` |\n\n### Debug 命令参数约定\n\nDebug 命令（`network`, `console`, `errors`, `trace`, `source`）有子命令的统一用 `--action` 参数：\n\n```bash\n# CLI 用法（位置参数）\nbb-browser source grep \"api.example\" --tab <id>\nbb-browser trace start --tab <id>\n\n# Hub invoke 用法（命名参数）\npinix invoke browser source --action grep --pattern \"api.example\" --tab <id>\npinix invoke browser trace --action start --tab <id>\npinix invoke browser network --action requests --excludeStatic true --tab <id>\n```\n\n旧参数名（`sourceCommand`、`traceCommand` 等）仍可用但已 deprecated。\n\n## 设计不变量\n\n1. **Daemon 是唯一操作 API**。CLI 和 Hub invoke 都通过 daemon。\n2. **Streamer 不做业务逻辑**。不管 tab、不做导航。只做帧编码和输入转发。\n3. **Tab ID 统一用 daemon 分配的短 ID**（如 `3ef9`）。Streamer 不知道 tab ID，只接收 CDP WebSocket URL。\n4. **Site 执行在 daemon 内**。不 shell-out CLI。\n5. **所有操作响应包含 `tab`**（短 ID）。观察类响应包含 `cursor`。\n6. **Per-tab 事件隔离**。tab 关闭时释放短 ID 和事件缓冲。\n7. **`seq` 全局单调递增**，不可回退。\n8. **Daemon 启动时清理旧进程**。`cleanupStaleDaemon()` 确保不残留。\n\n## Hub Clip 注册\n\nHub 模式下 daemon 注册：\n\n| Clip | 命令 |\n|------|------|\n| browser | 所有标准命令 + `stream.start/answer/close/switch` |\n| \\<platform\\> | 每个 site adapter 一个命令（如 `google/search`） |\n\nClip Web UI 通过 Hub invoke 调用标准命令（tab_list、open、reload 等）和 stream 命令。\n\n## Stream 生命周期 (view.html)\n\nClip Web UI (`web/view.html`) 通过心跳 + visibility 检测管理 stream 生命周期：\n\n```\nACTIVE (visible) → Ping 每 5s via DataChannel (opcode 0x08)\nIDLE   (hidden)  → Ping 每 10s, 30s 后自动 disconnect\nCLOSED           → overlay 显示断开原因, 可点 Connect 重连\n```\n\n**前端 30s idle disconnect 是第一道防线，后端 45s watchdog 是安全网。**\n\n`cleanup(reason)` 根据原因显示不同 overlay：\n- `user` → \"Disconnected\"\n- `idle` → \"Stream closed — tab was inactive\"\n- `network` → \"Connection lost\"\n\n关键实现约束：\n- 不用 `setTimeout`（hidden tab 被浏览器节流），用 `setInterval` + 时间戳\n- 不用 `BigInt`（兼容性），用 `Math.floor` + `>>>`\n- `disconnect` 里 `invoke(\"stream.close\")` 是 fire-and-forget，不 await\n\n## Docker 运行模式\n\nDocker 中 Chrome 必须以**有头模式**运行在 Xvfb 虚拟帧缓冲上。\n\n```\nXvfb (:99) ← Chrome (headed, DISPLAY=:99) ← CDP screencast → bb-viewer → WebRTC\n```\n\n**为什么不能用 `--headless=new`：** Linux 上 `--headless=new` 跳过整个 X11/Ozone 显示层，导致 WebGL、`navigator.plugins`、屏幕信息等 API 返回异常值。Google 等反自动化系统检测这些底层渲染差异来识别 headless 浏览器。macOS 上 headless 没问题，因为 Chrome 仍链接完整的 Cocoa/CoreGraphics 框架，只是不显示窗口。\n\nChrome 始终以有头模式启动，不使用 `--headless=new`。macOS 上没有聚焦的用户 session 时 Chrome 自动离屏渲染（无可见窗口），效果等同 headless 但保留完整 GUI API。\n\n**Docker 关键环境变量：**\n- `DISPLAY=:99` — Xvfb 虚拟显示（Dockerfile 已设置）\n\n**Stealth 注入 = 有害：** Google 的反自动化不检测 CDP 本身，而是检测 `Emulation.setUserAgentOverride`、`Page.addScriptToEvaluateOnNewDocument` 等 CDP domain 调用。不注入任何 stealth，用裸 CDP 即可。\n\n### Docker 构建\n\n```bash\n# 构建（单阶段，下载预编译 bb-viewer + Chrome，阿里云 apt/npm 镜像）\ndocker build --platform linux/amd64 -t bb-browser:amd64 .\n\n# 运行（TURN 凭证自动从 Hub API 获取，不需要配置）\ndocker run -d --platform linux/amd64 --name bb-browser \\\n  -v bb-browser-data:/data -p 19825:19824 --shm-size=2g \\\n  -e CHROME_WINDOW_SIZE=1280,720 \\\n  bb-browser:amd64 --host 0.0.0.0 \\\n  --hub https://hub.pinixai.com --hub-token <token>\n```\n\n镜像内预装了 Chrome for Testing + bb-viewer（静态链接），运行时无需下载。\n`--shm-size=2g` 防止 Chrome 因共享内存不足 crash。\nTURN 环境变量（`TURN_URL`/`TURN_SECRET`）仍可用于覆盖默认值。\n\n**推荐：使用 Pinix 全家桶镜像 `lueco/pinix`，包含 pinixd + bb-browser + Chrome，一个 token 启动一切。**\n\n### bb-viewer (streamer)\n\n- 预编译 binary 在 COS 上，daemon 首次 stream 时自动下载（`~/.bb-browser/bin/bb-viewer`）\n- 静态链接 libvpx + libturbojpeg，不依赖用户系统库版本\n- JPEG → I420 解码使用 `tjDecompress2` (RGB) 再手动转 I420，而非 `tjDecompressToYUVPlanes`（后者在 libturbojpeg 2.1.x 对奇数高度帧有 bug）\n- Decoder 自动 round down 到偶数尺寸\n\n### TURN relay\n\nTURN 凭证通过 Hub API `GET /turn/credentials` 动态获取（24h TTL），secret 只在服务端。\ndaemon 启动 streamer 时自动请求，不需要用户配置。\n环境变量 `TURN_URL` / `TURN_SECRET` 可覆盖（standalone 模式）。\n\n## 代码规范\n\n- Commit：`<type>(<scope>): <summary>`，英文\n- 类型：`fix` / `feat` / `refactor` / `chore` / `docs`\n- 构建：`pnpm build`\n- 测试：`pnpm test`\n- lint：`pnpm lint`\n\n## 参考\n\n- [bb-viewer issue #5](https://github.com/epiral/bb-viewer/issues/5) — Stream 生命周期设计\n- [issue #224](https://github.com/epiral/bb-browser/issues/224) — daemon 生命周期（已修复）\n"},"files":{"AGENTS.md":"# bb-browser Agent 开发规范\n\n## 架构\n\n```\nCLI ──HTTP──▶ Daemon ──controlConn CDP──▶ Chrome\n                │\n                │ --hub 模式 (可选)\n                ├── Hub ProviderStream ──▶ Pinix Hub\n                │\n                │ Protocol 2\n                └── Streamer (bb-viewer) ──captureConn + inputConn CDP──▶ Chrome\n                         │\n                         └── WebRTC video + DataChannel ──▶ Clip Web UI\n```\n\n**三根 CDP 连接到同一个 Chrome：**\n\n| 连接 | Owner | 职责 |\n|------|-------|------|\n| controlConn | daemon | Agent 操作：tab/nav/site/DOM/click-by-ref |\n| inputConn | streamer | Human 实时输入：鼠标/键盘/IME (DataChannel → CDP) |\n| captureConn | streamer | 视频流：screencast → VP8 → WebRTC |\n\n**组件：**\n\n| 组件 | 位置 | 职责 |\n|------|------|------|\n| CLI | `packages/cli/` | 命令行入口，HTTP 调 daemon |\n| Daemon | `packages/daemon/` | 控制中心：CDP controlConn、HTTP API、Hub 连接、site 执行、streamer 管理 |\n| Shared | `packages/shared/` | 统一命令定义 (`commands.ts`)、协议类型 (`protocol.ts`) |\n| Streamer | `bb-viewer` repo | 纯视频 + 实时输入（Go binary，daemon 子进程） |\n\n## Daemon 两种模式\n\n```bash\n# 本地模式：Agent 用 CLI 操作浏览器\nbb-browser daemon start\n\n# Hub 模式：注册到 Pinix Hub，远程可用\nbb-browser daemon start --hub https://hub.pinixai.com --hub-token xxx\n```\n\n## 协议\n\n### Protocol 1: CLI ↔ daemon (`POST /command`)\n\n请求：`{\"method\": \"snap\", \"params\": {\"tab\": \"3ef9\"}}`\n成功：`{\"result\": {\"tab\": \"3ef9\", \"title\": \"Google\", \"snapshot\": \"...\"}}`\n失败：`{\"error\": {\"message\": \"Missing --tab\", \"hint\": \"Run 'bb-browser tab list'\"}}`\n\n### Protocol 2: daemon ↔ streamer (`POST /command`)\n\n| Method | Params | 说明 |\n|--------|--------|------|\n| connect | {cdpUrl, ice?} | 连接 CDP，创建 WebRTC peer |\n| answer | {answer_sdp, candidates} | 完成 WebRTC 信令 |\n| switch | {cdpUrl} | 切到新 tab（peer 不变） |\n| stop | {} | 停止 |\n\n## 统一命令定义\n\n`packages/shared/src/commands.ts` 是所有命令的单一定义源。每个命令包含 `method`、`group`、`description`、`requiresTab`、`params`。CLI 解析、daemon dispatch、Hub 注册都从这里读取。\n\n添加新命令：\n1. `commands.ts` — 添加 CommandDef\n2. `protocol.ts` — 添加 ActionType\n3. `command-dispatch.ts` — 添加处理分支\n4. `packages/cli/src/commands/<name>.ts` + `index.ts` — CLI 命令\n\n## CLI 命令\n\n`--tab` 必填（除 `open`、`site` 组、`tab list/new`、`daemon`）。\n\n| 组 | 命令 |\n|----|------|\n| 导航 | `open <url> [--tab]`, `back --tab`, `forward --tab`, `reload --tab`, `close --tab` |\n| 观察 | `snap --tab`, `screenshot --tab`, `get <attr> --tab`, `eval <js> --tab` |\n| 交互 | `click/hover/fill/type/press/scroll/check/uncheck/select --tab` |\n| Tab | `tab list`, `tab new [url]` |\n| Site | `site list`, `site info <name>`, `site run <name>` |\n| 调试 | `network/console/errors/trace/cookies/source --tab` |\n| 进程 | `daemon start [--hub]`, `daemon stop`, `daemon status` |\n\n### Debug 命令参数约定\n\nDebug 命令（`network`, `console`, `errors`, `trace`, `source`）有子命令的统一用 `--action` 参数：\n\n```bash\n# CLI 用法（位置参数）\nbb-browser source grep \"api.example\" --tab <id>\nbb-browser trace start --tab <id>\n\n# Hub invoke 用法（命名参数）\npinix invoke browser source --action grep --pattern \"api.example\" --tab <id>\npinix invoke browser trace --action start --tab <id>\npinix invoke browser network --action requests --excludeStatic true --tab <id>\n```\n\n旧参数名（`sourceCommand`、`traceCommand` 等）仍可用但已 deprecated。\n\n## 设计不变量\n\n1. **Daemon 是唯一操作 API**。CLI 和 Hub invoke 都通过 daemon。\n2. **Streamer 不做业务逻辑**。不管 tab、不做导航。只做帧编码和输入转发。\n3. **Tab ID 统一用 daemon 分配的短 ID**（如 `3ef9`）。Streamer 不知道 tab ID，只接收 CDP WebSocket URL。\n4. **Site 执行在 daemon 内**。不 shell-out CLI。\n5. **所有操作响应包含 `tab`**（短 ID）。观察类响应包含 `cursor`。\n6. **Per-tab 事件隔离**。tab 关闭时释放短 ID 和事件缓冲。\n7. **`seq` 全局单调递增**，不可回退。\n8. **Daemon 启动时清理旧进程**。`cleanupStaleDaemon()` 确保不残留。\n\n## Hub Clip 注册\n\nHub 模式下 daemon 注册：\n\n| Clip | 命令 |\n|------|------|\n| browser | 所有标准命令 + `stream.start/answer/close/switch` |\n| \\<platform\\> | 每个 site adapter 一个命令（如 `google/search`） |\n\nClip Web UI 通过 Hub invoke 调用标准命令（tab_list、open、reload 等）和 stream 命令。\n\n## Stream 生命周期 (view.html)\n\nClip Web UI (`web/view.html`) 通过心跳 + visibility 检测管理 stream 生命周期：\n\n```\nACTIVE (visible) → Ping 每 5s via DataChannel (opcode 0x08)\nIDLE   (hidden)  → Ping 每 10s, 30s 后自动 disconnect\nCLOSED           → overlay 显示断开原因, 可点 Connect 重连\n```\n\n**前端 30s idle disconnect 是第一道防线，后端 45s watchdog 是安全网。**\n\n`cleanup(reason)` 根据原因显示不同 overlay：\n- `user` → \"Disconnected\"\n- `idle` → \"Stream closed — tab was inactive\"\n- `network` → \"Connection lost\"\n\n关键实现约束：\n- 不用 `setTimeout`（hidden tab 被浏览器节流），用 `setInterval` + 时间戳\n- 不用 `BigInt`（兼容性），用 `Math.floor` + `>>>`\n- `disconnect` 里 `invoke(\"stream.close\")` 是 fire-and-forget，不 await\n\n## Docker 运行模式\n\nDocker 中 Chrome 必须以**有头模式**运行在 Xvfb 虚拟帧缓冲上。\n\n```\nXvfb (:99) ← Chrome (headed, DISPLAY=:99) ← CDP screencast → bb-viewer → WebRTC\n```\n\n**为什么不能用 `--headless=new`：** Linux 上 `--headless=new` 跳过整个 X11/Ozone 显示层，导致 WebGL、`navigator.plugins`、屏幕信息等 API 返回异常值。Google 等反自动化系统检测这些底层渲染差异来识别 headless 浏览器。macOS 上 headless 没问题，因为 Chrome 仍链接完整的 Cocoa/CoreGraphics 框架，只是不显示窗口。\n\nChrome 始终以有头模式启动，不使用 `--headless=new`。macOS 上没有聚焦的用户 session 时 Chrome 自动离屏渲染（无可见窗口），效果等同 headless 但保留完整 GUI API。\n\n**Docker 关键环境变量：**\n- `DISPLAY=:99` — Xvfb 虚拟显示（Dockerfile 已设置）\n\n**Stealth 注入 = 有害：** Google 的反自动化不检测 CDP 本身，而是检测 `Emulation.setUserAgentOverride`、`Page.addScriptToEvaluateOnNewDocument` 等 CDP domain 调用。不注入任何 stealth，用裸 CDP 即可。\n\n### Docker 构建\n\n```bash\n# 构建（单阶段，下载预编译 bb-viewer + Chrome，阿里云 apt/npm 镜像）\ndocker build --platform linux/amd64 -t bb-browser:amd64 .\n\n# 运行（TURN 凭证自动从 Hub API 获取，不需要配置）\ndocker run -d --platform linux/amd64 --name bb-browser \\\n  -v bb-browser-data:/data -p 19825:19824 --shm-size=2g \\\n  -e CHROME_WINDOW_SIZE=1280,720 \\\n  bb-browser:amd64 --host 0.0.0.0 \\\n  --hub https://hub.pinixai.com --hub-token <token>\n```\n\n镜像内预装了 Chrome for Testing + bb-viewer（静态链接），运行时无需下载。\n`--shm-size=2g` 防止 Chrome 因共享内存不足 crash。\nTURN 环境变量（`TURN_URL`/`TURN_SECRET`）仍可用于覆盖默认值。\n\n**推荐：使用 Pinix 全家桶镜像 `lueco/pinix`，包含 pinixd + bb-browser + Chrome，一个 token 启动一切。**\n\n### bb-viewer (streamer)\n\n- 预编译 binary 在 COS 上，daemon 首次 stream 时自动下载（`~/.bb-browser/bin/bb-viewer`）\n- 静态链接 libvpx + libturbojpeg，不依赖用户系统库版本\n- JPEG → I420 解码使用 `tjDecompress2` (RGB) 再手动转 I420，而非 `tjDecompressToYUVPlanes`（后者在 libturbojpeg 2.1.x 对奇数高度帧有 bug）\n- Decoder 自动 round down 到偶数尺寸\n\n### TURN relay\n\nTURN 凭证通过 Hub API `GET /turn/credentials` 动态获取（24h TTL），secret 只在服务端。\ndaemon 启动 streamer 时自动请求，不需要用户配置。\n环境变量 `TURN_URL` / `TURN_SECRET` 可覆盖（standalone 模式）。\n\n## 代码规范\n\n- Commit：`<type>(<scope>): <summary>`，英文\n- 类型：`fix` / `feat` / `refactor` / `chore` / `docs`\n- 构建：`pnpm build`\n- 测试：`pnpm test`\n- lint：`pnpm lint`\n\n## 参考\n\n- [bb-viewer issue #5](https://github.com/epiral/bb-viewer/issues/5) — Stream 生命周期设计\n- [issue #224](https://github.com/epiral/bb-browser/issues/224) — daemon 生命周期（已修复）\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# bb-browser Agent 开发规范\n\n## 架构\n\n```\nCLI ──HTTP──▶ Daemon ──controlConn CDP──▶ Chrome\n                │\n                │ --hub 模式 (可选)\n                ├── Hub ProviderStream ──▶ Pinix Hub\n                │\n                │ Protocol 2\n                └── Streamer (bb-viewer) ──captureConn + inputConn CDP──▶ Chrome\n                         │\n                         └── WebRTC video + DataChannel ──▶ Clip Web UI\n```\n\n**三根 CDP 连接到同一个 Chrome：**\n\n| 连接 | Owner | 职责 |\n|------|-------|------|\n| controlConn | daemon | Agent 操作：tab/nav/site/DOM/click-by-ref |\n| inputConn | streamer | Human 实时输入：鼠标/键盘/IME (DataChannel → CDP) |\n| captureConn | streamer | 视频流：screencast → VP8 → WebRTC |\n\n**组件：**\n\n| 组件 | 位置 | 职责 |\n|------|------|------|\n| CLI | `packages/cli/` | 命令行入口，HTTP 调 daemon |\n| Daemon | `packages/daemon/` | 控制中心：CDP controlConn、HTTP API、Hub 连接、site 执行、streamer 管理 |\n| Shared | `packages/shared/` | 统一命令定义 (`commands.ts`)、协议类型 (`protocol.ts`) |\n| Streamer | `bb-viewer` repo | 纯视频 + 实时输入（Go binary，daemon 子进程） |\n\n## Daemon 两种模式\n\n```bash\n# 本地模式：Agent 用 CLI 操作浏览器\nbb-browser daemon start\n\n# Hub 模式：注册到 Pinix Hub，远程可用\nbb-browser daemon start --hub https://hub.pinixai.com --hub-token xxx\n```\n\n## 协议\n\n### Protocol 1: CLI ↔ daemon (`POST /command`)\n\n请求：`{\"method\": \"snap\", \"params\": {\"tab\": \"3ef9\"}}`\n成功：`{\"result\": {\"tab\": \"3ef9\", \"title\": \"Google\", \"snapshot\": \"...\"}}`\n失败：`{\"error\": {\"message\": \"Missing --tab\", \"hint\": \"Run 'bb-browser tab list'\"}}`\n\n### Protocol 2: daemon ↔ streamer (`POST /command`)\n\n| Method | Params | 说明 |\n|--------|--------|------|\n| connect | {cdpUrl, ice?} | 连接 CDP，创建 WebRTC peer |\n| answer | {answer_sdp, candidates} | 完成 WebRTC 信令 |\n| switch | {cdpUrl} | 切到新 tab（peer 不变） |\n| stop | {} | 停止 |\n\n## 统一命令定义\n\n`packages/shared/src/commands.ts` 是所有命令的单一定义源。每个命令包含 `method`、`group`、`description`、`requiresTab`、`params`。CLI 解析、daemon dispatch、Hub 注册都从这里读取。\n\n添加新命令：\n1. `commands.ts` — 添加 CommandDef\n2. `protocol.ts` — 添加 ActionType\n3. `command-dispatch.ts` — 添加处理分支\n4. `packages/cli/src/commands/<name>.ts` + `index.ts` — CLI 命令\n\n## CLI 命令\n\n`--tab` 必填（除 `open`、`site` 组、`tab list/new`、`daemon`）。\n\n| 组 | 命令 |\n|----|------|\n| 导航 | `open <url> [--tab]`, `back --tab`, `forward --tab`, `reload --tab`, `close --tab` |\n| 观察 | `snap --tab`, `screenshot --tab`, `get <attr> --tab`, `eval <js> --tab` |\n| 交互 | `click/hover/fill/type/press/scroll/check/uncheck/select --tab` |\n| Tab | `tab list`, `tab new [url]` |\n| Site | `site list`, `site info <name>`, `site run <name>` |\n| 调试 | `network/console/errors/trace/cookies/source --tab` |\n| 进程 | `daemon start [--hub]`, `daemon stop`, `daemon status` |\n\n### Debug 命令参数约定\n\nDebug 命令（`network`, `console`, `errors`, `trace`, `source`）有子命令的统一用 `--action` 参数：\n\n```bash\n# CLI 用法（位置参数）\nbb-browser source grep \"api.example\" --tab <id>\nbb-browser trace start --tab <id>\n\n# Hub invoke 用法（命名参数）\npinix invoke browser source --action grep --pattern \"api.example\" --tab <id>\npinix invoke browser trace --action start --tab <id>\npinix invoke browser network --action requests --excludeStatic true --tab <id>\n```\n\n旧参数名（`sourceCommand`、`traceCommand` 等）仍可用但已 deprecated。\n\n## 设计不变量\n\n1. **Daemon 是唯一操作 API**。CLI 和 Hub invoke 都通过 daemon。\n2. **Streamer 不做业务逻辑**。不管 tab、不做导航。只做帧编码和输入转发。\n3. **Tab ID 统一用 daemon 分配的短 ID**（如 `3ef9`）。Streamer 不知道 tab ID，只接收 CDP WebSocket URL。\n4. **Site 执行在 daemon 内**。不 shell-out CLI。\n5. **所有操作响应包含 `tab`**（短 ID）。观察类响应包含 `cursor`。\n6. **Per-tab 事件隔离**。tab 关闭时释放短 ID 和事件缓冲。\n7. **`seq` 全局单调递增**，不可回退。\n8. **Daemon 启动时清理旧进程**。`cleanupStaleDaemon()` 确保不残留。\n\n## Hub Clip 注册\n\nHub 模式下 daemon 注册：\n\n| Clip | 命令 |\n|------|------|\n| browser | 所有标准命令 + `stream.start/answer/close/switch` |\n| \\<platform\\> | 每个 site adapter 一个命令（如 `google/search`） |\n\nClip Web UI 通过 Hub invoke 调用标准命令（tab_list、open、reload 等）和 stream 命令。\n\n## Stream 生命周期 (view.html)\n\nClip Web UI (`web/view.html`) 通过心跳 + visibility 检测管理 stream 生命周期：\n\n```\nACTIVE (visible) → Ping 每 5s via DataChannel (opcode 0x08)\nIDLE   (hidden)  → Ping 每 10s, 30s 后自动 disconnect\nCLOSED           → overlay 显示断开原因, 可点 Connect 重连\n```\n\n**前端 30s idle disconnect 是第一道防线，后端 45s watchdog 是安全网。**\n\n`cleanup(reason)` 根据原因显示不同 overlay：\n- `user` → \"Disconnected\"\n- `idle` → \"Stream closed — tab was inactive\"\n- `network` → \"Connection lost\"\n\n关键实现约束：\n- 不用 `setTimeout`（hidden tab 被浏览器节流），用 `setInterval` + 时间戳\n- 不用 `BigInt`（兼容性），用 `Math.floor` + `>>>`\n- `disconnect` 里 `invoke(\"stream.close\")` 是 fire-and-forget，不 await\n\n## Docker 运行模式\n\nDocker 中 Chrome 必须以**有头模式**运行在 Xvfb 虚拟帧缓冲上。\n\n```\nXvfb (:99) ← Chrome (headed, DISPLAY=:99) ← CDP screencast → bb-viewer → WebRTC\n```\n\n**为什么不能用 `--headless=new`：** Linux 上 `--headless=new` 跳过整个 X11/Ozone 显示层，导致 WebGL、`navigator.plugins`、屏幕信息等 API 返回异常值。Google 等反自动化系统检测这些底层渲染差异来识别 headless 浏览器。macOS 上 headless 没问题，因为 Chrome 仍链接完整的 Cocoa/CoreGraphics 框架，只是不显示窗口。\n\nChrome 始终以有头模式启动，不使用 `--headless=new`。macOS 上没有聚焦的用户 session 时 Chrome 自动离屏渲染（无可见窗口），效果等同 headless 但保留完整 GUI API。\n\n**Docker 关键环境变量：**\n- `DISPLAY=:99` — Xvfb 虚拟显示（Dockerfile 已设置）\n\n**Stealth 注入 = 有害：** Google 的反自动化不检测 CDP 本身，而是检测 `Emulation.setUserAgentOverride`、`Page.addScriptToEvaluateOnNewDocument` 等 CDP domain 调用。不注入任何 stealth，用裸 CDP 即可。\n\n### Docker 构建\n\n```bash\n# 构建（单阶段，下载预编译 bb-viewer + Chrome，阿里云 apt/npm 镜像）\ndocker build --platform linux/amd64 -t bb-browser:amd64 .\n\n# 运行（TURN 凭证自动从 Hub API 获取，不需要配置）\ndocker run -d --platform linux/amd64 --name bb-browser \\\n  -v bb-browser-data:/data -p 19825:19824 --shm-size=2g \\\n  -e CHROME_WINDOW_SIZE=1280,720 \\\n  bb-browser:amd64 --host 0.0.0.0 \\\n  --hub https://hub.pinixai.com --hub-token <token>\n```\n\n镜像内预装了 Chrome for Testing + bb-viewer（静态链接），运行时无需下载。\n`--shm-size=2g` 防止 Chrome 因共享内存不足 crash。\nTURN 环境变量（`TURN_URL`/`TURN_SECRET`）仍可用于覆盖默认值。\n\n**推荐：使用 Pinix 全家桶镜像 `lueco/pinix`，包含 pinixd + bb-browser + Chrome，一个 token 启动一切。**\n\n### bb-viewer (streamer)\n\n- 预编译 binary 在 COS 上，daemon 首次 stream 时自动下载（`~/.bb-browser/bin/bb-viewer`）\n- 静态链接 libvpx + libturbojpeg，不依赖用户系统库版本\n- JPEG → I420 解码使用 `tjDecompress2` (RGB) 再手动转 I420，而非 `tjDecompressToYUVPlanes`（后者在 libturbojpeg 2.1.x 对奇数高度帧有 bug）\n- Decoder 自动 round down 到偶数尺寸\n\n### TURN relay\n\nTURN 凭证通过 Hub API `GET /turn/credentials` 动态获取（24h TTL），secret 只在服务端。\ndaemon 启动 streamer 时自动请求，不需要用户配置。\n环境变量 `TURN_URL` / `TURN_SECRET` 可覆盖（standalone 模式）。\n\n## 代码规范\n\n- Commit：`<type>(<scope>): <summary>`，英文\n- 类型：`fix` / `feat` / `refactor` / `chore` / `docs`\n- 构建：`pnpm build`\n- 测试：`pnpm test`\n- lint：`pnpm lint`\n\n## 参考\n\n- [bb-viewer issue #5](https://github.com/epiral/bb-viewer/issues/5) — Stream 生命周期设计\n- [issue #224](https://github.com/epiral/bb-browser/issues/224) — daemon 生命周期（已修复）\n","category":"root","tokens":1642}]}