# agent-browser 集成设计

本文描述 PicoBot 1.4.0 的浏览器工具实现。目标是在保持模型侧稳定 `browser` 协议的同时，用 agent-browser 完全替代 Fantoccini、ChromeDriver 和 WebDriver，并让临时会话、单用户多持久 Profile、管理操作、产物和健康检查拥有明确边界。

## 1. 选择与边界

PicoBot 使用 agent-browser CLI 的 `--json` 协议，不直接链接其内部 crate，也不把 agent-browser MCP Server 原样暴露给模型。

原因：

- agent-browser 是原生 Rust CLI + daemon，daemon 通过 Chrome CDP 驱动浏览器；CLI 进程很短，浏览器状态跨命令保存在 daemon 中。
- CLI 是项目的稳定公开边界，PicoBot 不需要依赖 agent-browser 的内部 Rust 模块布局。
- PicoBot 包装层可统一管理个人浏览器身份、限制并发和输出、校验 URL、控制 Profile/截图目录，并把图片接入统一 `ToolOutput` 后处理管线。
- 直接暴露 MCP 会让 session ID、文件路径、输出规模和安全策略落到模型参数中，也难以自动绑定当前 PicoBot dialog。

这不是把浏览器逻辑重新实现一遍。元素定位、accessibility snapshot、页面交互、Chrome 启动、CDP 通信和 daemon 生命周期均由 agent-browser 负责；PicoBot 只负责编排和边界控制。

## 2. 分层

```text
AgentLoop
  │ ToolExecutionContext(session_id, turn_id)
  ▼
BrowserTool / BrowserProfilesTool   浏览与持久 Profile 管理 schema
  ▼
BrowserManager                      临时 dialog session / 共享持久 Profile
  ├─ security                       URL、DNS、私网与 allowlist 前置校验
  ├─ action                         browser action → CLI argv
  └─ AgentBrowserRunner             timeout、env、--json、错误与输出解析
       ▼
agent-browser CLI → Rust daemon → Chrome/Chromium CDP
```

源文件：

- `src/tools/browser/mod.rs`：`browser`、`browser_profiles` schema 与入口。
- `src/tools/browser/action.rs`：严格参数解析和 argv 映射。
- `src/tools/browser/manager.rs`：临时会话表、按 ID 管理的持久 Profile、并发、回收和截图媒体。
- `src/tools/browser/runner.rs`：无 Shell 的子进程调用、硬超时、JSON/错误解析。
- `src/tools/browser/security.rs`：导航策略。
- `src/tools/traits.rs`：向有状态工具提供 `ToolExecutionContext`；其他工具沿用默认实现。

## 3. 会话、持久 ID 与并发

BrowserManager 按每次调用是否携带 `persistent_id` 分流，两种浏览器可在同一个 Gateway 中同时使用：

- 省略 `persistent_id` 时使用普通临时浏览器。`SessionManager` 传入完整 PicoBot session ID，BrowserManager 第一次看到该 ID 时生成随机、不透明的 `picobot-<uuid>` agent-browser session。同一 dialog 串行、不同 dialog 可并发；临时 daemon 连续 `idle_timeout_secs`（默认 3600 秒）没有操作后自动退出，Manager 在容量检查时惰性回收空闲条目，数量受 `max_sessions` 限制。
- 需要长期保留登录或站点状态时，Agent 可以自主调用 `browser_profiles(action=create,label=...)` 创建持久身份，并在后续相关的每个 `browser` action 中显式传入返回的 `persistent_id`。

- 用户可以拥有多个 `picobot-profile-<32 hex>` ID。`browser_profiles(action=create,label=...)` 创建 `persistence.profile_dir/<id>` 专用目录和可选语义标签；标签可通过 `set_label` 重命名，ID 保持不变。
- PicoBot 不保存默认 ID，也不按 dialog 自动选择或绑定持久 Profile。没有 ID 的调用始终回到该 dialog 的临时浏览器，不会隐式选中任何持久身份。
- 每个 ID 分别映射 agent-browser `--session`、Chrome `--profile` 路径和 mutex。同一 ID 可从不同 dialog、子 Agent 或 Scheduler 使用并保持串行；不同 ID 的浏览器状态和锁相互独立，可以并发。
- 持久 daemon 禁用空闲自动退出，适合需要长时间等待的浏览器作业。ID 与 Profile 目录跨 Gateway 重启、配置重载和显式浏览器关闭保持不变；Cookie、localStorage、IndexedDB、Service Worker 和缓存由各 Chrome Profile 自身保存。
- `close` 关闭显式选择的浏览器进程但保留 ID、标签和 Profile；下一次使用该 ID 时从相同目录重新打开。
- `browser_profiles(action=list)` 返回每个合法 ID 的标签、完整目录和当前 Manager 是否 active。
- `browser_profiles(action=set_label,id=...,label=...)` 写入语义标签并同步当前活动实例；标签去除首尾空白，限制为 1–80 个非控制字符。
- `browser_profiles(action=delete,id=...)` 只接受 `create/list` 返回的完整格式 ID。删除时持有管理锁、等待该 ID 的活动 action、尝试关闭其浏览器，再递归删除对应目录。

Profile 根目录和每个生成目录在 Unix 上收敛为 `0700`，目录内 `.picobot-label` 标签文件为 `0600`。列表忽略格式非法的目录和符号链接，选择、改标签和删除拒绝路径穿越、符号链接及非目录目标。Gateway 配置重载会构造新的 ToolRegistry/BrowserManager；旧运行代按现有 drain 规则退出。临时 agent-browser daemon 的空闲退出时间通过 `AGENT_BROWSER_IDLE_TIMEOUT_MS` 设置为配置值，持久 daemon 则固定设置为 `0`（禁用）。

## 4. Action 映射

| PicoBot action | agent-browser 命令 |
|---|---|
| `open` | `open <url>` |
| `snapshot` | `snapshot --interactive --compact [--depth N]` |
| `click` / `fill` / `type` | 同名命令；无 selector 的 type 使用 `keyboard type` |
| `get_text` / `get_title` / `get_url` | `get text/title/url` |
| `focus` / `wait` / `press` / `hover` / `scroll` | 对应原生命令 |
| `click_at` | `mouse move` + `mouse down left` + `mouse up left` |
| `screenshot` | `screenshot <controlled-path> [--full] [--annotate]` |
| `close` | `close` |

每次调用都使用 argv 数组直接启动进程，不经过 Shell。`fill` / `type` 的内容不会写入 PicoBot 日志；日志只记录 action 名、是否绑定 session 和所选持久 ID。

Runner 固定传入 `--session`、`--json` 和明确的 headed 状态；携带持久 ID 的调用额外传入由 Manager 生成的 `--profile <controlled-path>`。Runner 还设置：

- `AGENT_BROWSER_EXECUTABLE_PATH`（配置后）
- `AGENT_BROWSER_CONTENT_BOUNDARIES`
- `AGENT_BROWSER_MAX_OUTPUT`
- `AGENT_BROWSER_ALLOWED_DOMAINS`（非空时）
- `AGENT_BROWSER_IDLE_TIMEOUT_MS`（临时会话使用配置值；持久会话固定为 `0`）

非零退出码、JSON 中 `success=false`、无效 JSON和超时都转换为工具失败。stdout/stderr 在返回模型前有长度上限；页面类结果保留 agent-browser `_boundary` 元数据。

## 5. 截图与媒体

截图绝不返回 Base64。调用方可省略 `path` 自动生成文件名，也可提供单个 `.png` 文件名；绝对路径、目录分隔、`.` 和 `..` 均拒绝。实际文件始终位于 `browser.artifact_dir`。

命令成功后 PicoBot 再验证文件存在、是普通文件且非空，然后返回：

```text
ToolOutput {
  result: ToolResult { output: "Screenshot saved: ..." },
  artifacts: [ToolArtifact {
    media_ref: MediaRef { media_type: "image", path: "..." },
    audience: ModelAndUser
  }]
}
```

统一处理器会把截图同时交给下一轮多模态 Provider，并附到本 Turn 最终 assistant 回复供用户查看。`present_to_user` 默认为 `true`；显式设为 `false` 时 audience 改为 `Model`，适用于无需展示的内部视觉检查。历史中仍只保存短路径清单，不产生 Base64 上下文膨胀。

## 6. 安全模型

默认策略：

- 只允许 `http://` 和 `https://`，拒绝 URL userinfo。
- `allow_private_hosts=false` 时拒绝 localhost、`.local`、回环、私网、link-local、未指定和组播地址；域名会先解析 DNS，任一结果为私网即拒绝。
- `allowed_domains` 非空时 PicoBot 先校验首个 URL，agent-browser 再对导航、重定向、子资源、WebSocket、EventSource、sendBeacon 和受支持 Chromium 的 WebRTC 实施域名边界。
- 默认开启 content boundaries，并把页面文本限制为 50,000 字符。
- 包装层没有 `eval`、上传、下载、cookie/storage 写入或任意 agent-browser 命令透传，模型只能使用 allowlisted action。
- 截图有单独的产物目录，不能用来覆盖任意文件。
- `browser_profiles` 只允许创建受控 ID，或按格式严格的 ID 设置受限标签、列出和删除 `persistence.profile_dir` 的直接子目录；模型不能提交任意 Profile 路径。

`allowed_domains=[]` 表示不启用 agent-browser 域名过滤，适合通用浏览；这不是 OS 网络沙箱。需要强隔离时，应同时设置明确域名表和容器/主机 egress 策略。允许私网浏览是显式配置，适合本地应用测试，但会扩大 SSRF 风险。

agent-browser 0.33.0 明确拒绝在 `allowed_domains` 启用时复用 Chrome Profile，因为无法保证页面脚本执行前完整安装同等域名约束。PicoBot 因此允许受域名限制的普通临时浏览器继续工作，但会拒绝创建或使用持久 Profile，并在 health 中给出可选能力警告；列表、改标签和删除仍可用于管理已有目录。Profile 包含可直接代表用户身份的登录信息，目录必须视作敏感凭据，不得提交版本控制或跨用户共享。

## 7. 安装

验证版本为 `0.33.0`：

```bash
npm install -g agent-browser@0.33.0
agent-browser install
```

Linux 自动补系统依赖：

```bash
agent-browser install --with-deps
```

不使用 npm 时：

```bash
cargo install agent-browser --version 0.33.0 --locked
agent-browser install
```

macOS 也可执行：

```bash
brew install agent-browser
agent-browser install
```

`agent-browser install` 下载 Chrome for Testing。已有浏览器时设置：

```json
"browser_executable_path": "/usr/bin/chromium"
```

或设置环境变量 `AGENT_BROWSER_EXECUTABLE_PATH`。agent-browser 的 daemon 与 CDP 路径不需要 Node.js；npm 安装方式只需要 npm 用来放置预编译 CLI。Dockerfile 安装 Debian Chromium、`agent-browser@0.33.0` 并设置 executable path。

## 8. 使用

1. 浏览器工具默认启用；从旧配置删除 `webdriver_url`、`chrome_path`，仅在需要改变持久目录位置时配置 `persistence.profile_dir`。没有持久化模式开关。缺少依赖不会阻止 Gateway 启动，只会让 health 和实际浏览器调用失败。
2. 运行 `picobot health`；应看到 agent-browser CLI 版本、浏览器安装、隔离的 offline headless 启动和环境检查通过。
3. 启动或重载 Gateway。
4. 对 Agent 说“使用浏览器打开 …”。模型的推荐动作序列是：

```text
browser(open, url)
browser(snapshot, interactive_only=true, compact=true)
browser(click/fill/type, selector=@eN)
browser(snapshot)  # 页面改变后刷新 refs
browser(screenshot, annotate=true)  # 需要视觉上下文时
browser(close)
```

agent-browser 的 `@e` 引用属于当前页面快照。导航、弹窗或 DOM 大幅变化后必须重新 snapshot，不能长期缓存旧引用。

持久 Profile 管理：

```text
browser_profiles(create, label="工作账号")  # 返回 persistent ID
browser_profiles(list)
browser_profiles(set_label, id=picobot-profile-..., label="个人账号")
browser(open, url, persistent_id=picobot-profile-...)
browser(snapshot, persistent_id=picobot-profile-...)
browser_profiles(delete, id=picobot-profile-...)
```

同一个持久操作链必须持续传入同一 `persistent_id`；一旦省略，调用会明确转到当前 dialog 的普通临时浏览器。不同对话可以同时操作不同 ID。标签用于识别，不能代替 ID 选择浏览器；删除会清除该 ID 的全部浏览器数据、标签和登录态，调用前必须有用户要求并使用 `create/list` 返回的精确 ID。

## 9. Health 三入口

`src/health.rs` 的 `HealthService` 是唯一检查实现：

- `picobot health [--json]`：本机运维入口；核心/配置必需项失败时退出 `1`。
- `/health`：当前 Gateway 配置的聊天入口。
- `health` Tool：Agent 可调用的只读入口，支持 `json=true`。

检查项包括 workspace、Bash、内容/文件搜索后端、可选 systemctl、配置中的 stdio MCP 命令，以及浏览器启用时的持久 Profile 可用性、agent-browser 版本、显式浏览器路径和完整的 offline doctor。Health 为 doctor 创建临时 socket 目录并使用专用 namespace，不读取或清理活动 daemon socket；不使用会跳过真实启动测试的 `--quick`。结构化结果分别展示浏览器安装、headless launch 和其余环境诊断。检查不创建或删除 PicoBot 持久 Profile、不安装软件、不执行 `doctor --fix`、不访问 Provider API，也不输出配置密钥。

## 10. 迁移和故障处理

- 配置使用 `deny_unknown_fields`；遗留 WebDriver 字段会在加载时失败，而不是被静默忽略。
- `failed to start 'agent-browser'`：CLI 不在 PATH，或 `browser.command` 错误；运行 health。
- doctor 失败：运行 `agent-browser doctor` 查看完整诊断，再安装浏览器/系统库。
- session limit：仅临时模式适用；关闭不再使用的 dialog 浏览器，或调整 `max_sessions`。
- persistence policy：非空 `allowed_domains` 下普通临时浏览器仍可用，但创建或使用持久 Profile 会失败；根据需求选择个人登录态复用或严格域名隔离。
- Profile 删除失败：先确认没有外部 Chrome 使用该目录，再用 `browser_profiles(list)` 核对精确 ID 后重试；不要手工扩大删除路径。
- domain blocked：补充站点和必要 CDN 域名；不要用空白 allowlist 绕过生产隔离策略。
- command timeout：确认页面/浏览器未卡死，再按部署风险调整 `command_timeout_secs`。
- 截图不存在：视为工具失败，不构造失效 MediaRef。

Fantoccini crate、旧 `src/tools/browser.rs` WebDriver 实现、ChromeDriver Docker 包和相关配置已全部删除。Cargo 不链接 agent-browser；它是由 health 管理的外部运行依赖。
