From efb0e7f0b06aa7dba6b0f2d60be9e75a3f6bd5f8 Mon Sep 17 00:00:00 2001 From: xiaoxixi Date: Tue, 28 Jul 2026 23:50:57 +0800 Subject: [PATCH] docs: document persistent browser workflows --- AGENTS.md | 4 +- README.md | 14 +++- docs/AGENT_BROWSER_INTEGRATION.md | 73 +++++++++++++------ docs/ARCHITECTURE.md | 6 +- .../about-picobot/assets/config.example.json | 5 +- .../about-picobot/references/architecture.md | 3 +- .../skills/about-picobot/references/config.md | 9 ++- .../skills/about-picobot/references/tools.md | 8 +- resources/templates/config.example.json | 5 +- 9 files changed, 88 insertions(+), 39 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9525427..5be3dcc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,8 +103,8 @@ Scheduler → SessionManager scheduled execution → AgentLoop → Scheduler del - **WebUI authentication** protects every management API and `/ws`; only static pairing assets, public health/status, and pairing submission may bypass device auth. Pair-code issuance requires both a real loopback peer and the filesystem-held admin token. The same conjunction may authenticate only `/ws` for local one-shot `run`; it must never authorize management APIs. Never put bearer or admin tokens in URLs or logs - **Providers** are pure HTTP clients; no bus/session/channel awareness - **Provider reasoning state** is private replay data: persist it, replay it only to the matching provider, and never expose it to clients, channels, or logs -- **Tools** are executed by `AgentLoop`; they receive raw arguments and normally return text. Tools that produce model-consumable media use the structured `execute_with_media` side channel; model capability checks and provider content-block serialization stay outside tools -- **Stateful tools** receive `ToolExecutionContext`; browser automation maps each PicoBot dialog to an opaque agent-browser session, uses per-session serialization, and returns screenshots through structured media. Do not reintroduce Fantoccini, ChromeDriver, WebDriver, or model-controlled raw browser session IDs +- **Tools** are executed by `AgentLoop`; every invocation is normalized to `ToolOutput` and passes through `ToolOutputProcessor`. Plain `ToolResult` implementations use the default conversion, while artifact-producing tools declare model/user audience explicitly; model capability checks, final-reply attachment, channel delivery, and Provider serialization stay outside tools +- **Stateful tools** receive `ToolExecutionContext`; browser calls without `persistent_id` map each PicoBot dialog to an opaque transient agent-browser session. For long-running work an Agent may autonomously create a persistent identity and must pass its validated ID on every related action; the same ID shares one agent-browser session and serialization gate across dialogs, while different IDs have independent sessions/gates. `browser_profiles` may create IDs, persist bounded semantic labels, list, or delete only validated IDs beneath the configured profile root. Do not introduce a global persistence mode switch, a default persistent ID, automatic per-dialog persistent Profiles, Fantoccini, ChromeDriver, WebDriver, model-controlled raw agent-browser session IDs, or arbitrary Profile paths - **Health diagnostics** are read-only and share `HealthService` across `picobot health`, the `health` tool, and `/health`; checks must not install/fix dependencies, call Provider APIs, or expose secrets ### Concurrency and Lifecycle Invariants diff --git a/README.md b/README.md index ee28a09..5723400 100644 --- a/README.md +++ b/README.md @@ -347,7 +347,8 @@ PicoBot 有两类记忆: | `cron_add/list/remove/enable/disable/update` | 管理定时任务 | | `routine_maintenance` | 安全清理超过保留期的 Timeline,不删除 Knowledge | | `health` | 检查核心、配置相关和可选运行依赖 | -| `browser` | 可选 agent-browser 浏览器自动化;每个 dialog 独立会话 | +| `browser` | 可选 agent-browser 浏览器自动化;默认按 dialog 临时使用,长期任务可用 `persistent_id` 复用个人 Profile | +| `browser_profiles` | 创建、设置语义标签、列出或删除浏览器持久 ID 及其 Profile 目录 | | MCP tools | 从配置的 MCP Server 动态发现并注册 | ### Skills @@ -438,12 +439,19 @@ agent-browser install "content_boundaries": true, "allowed_domains": [], "allow_private_hosts": false, - "artifact_dir": "~/.picobot/media/browser" + "artifact_dir": "~/.picobot/media/browser", + "persistence": { + "profile_dir": "~/.picobot/browser/profiles" + } } } ``` -浏览器工具默认启用;缺少 agent-browser 或 Chrome 不阻止 Gateway 启动,但实际调用会失败并给出安装提示,`picobot health` 也会提前报告。修改后建议先运行 health,再启动或重载 Gateway。旧的 `webdriver_url`、`chrome_path` 配置已删除,出现这两个字段时配置校验会明确失败。实际使用仍由 Agent 调用 `browser`:`open` → `snapshot` 获取 `@e1` 等引用 → `click` / `fill` / `type` → 页面变化后重新 `snapshot`。截图保存到受控产物目录并作为结构化图片返回,不再生成 Base64 工具文本。 +浏览器没有全局“持久模式”开关,而是按每次调用分流:不传 `persistent_id` 时使用当前 dialog 的普通临时浏览器;涉及长期工作、需要保留登录或站点状态时,Agent 可以自主调用 `browser_profiles(create,label=...)` 生成 `picobot-profile-`,并在该工作的后续每个 `browser` action 中持续传入同一个 ID。也可以用 `set_label` 随时修改语义化标签。PicoBot 不设置默认 ID,也不会按 dialog 自动选择持久 Profile。同一 ID 跨 dialog 共享 agent-browser session 和串行锁,不同 ID 使用各自的 session、锁与 Chrome Profile,因而可以并发操作。Cookie、localStorage、IndexedDB、Service Worker、缓存和标签随各自目录持久化。 + +`browser_profiles` 支持 `create`、`set_label`、`list`、`delete`;`list` 返回 ID、标签、目录和 active 状态,浏览器仍始终用不可变 ID 选择,重命名标签不会破坏现有调用。Profile 根目录位于 `~/.picobot` 内,现有 Docker `picobot_data` 卷会一并持久化。agent-browser 无法同时保证 Profile 复用与 `allowed_domains` 域名隔离;配置非空白名单后,普通临时浏览器仍可用,持久身份的创建和使用会被拒绝,health 会给出可选能力警告。 + +缺少 agent-browser 或 Chrome 不阻止 Gateway 启动,但实际调用会失败并给出安装提示,`picobot health` 也会提前报告。修改后建议先运行 health,再启动或重载 Gateway。旧的 `webdriver_url`、`chrome_path` 配置已删除,出现这两个字段时配置校验会明确失败。实际使用仍由 Agent 调用 `browser`:`open` → `snapshot` 获取 `@e1` 等引用 → `click` / `fill` / `type` → 页面变化后重新 `snapshot`。截图保存到受控产物目录,通过统一工具输出管线交给多模态模型,并默认附到本轮最终回复给用户查看,不再生成 Base64 工具文本;仅需模型内部检查时可显式设置 `present_to_user=false`。 详细开发分层、进程协议、并发/安全边界和故障语义见 [agent-browser 集成设计](docs/AGENT_BROWSER_INTEGRATION.md)。 diff --git a/docs/AGENT_BROWSER_INTEGRATION.md b/docs/AGENT_BROWSER_INTEGRATION.md index b924d28..b960f34 100644 --- a/docs/AGENT_BROWSER_INTEGRATION.md +++ b/docs/AGENT_BROWSER_INTEGRATION.md @@ -1,6 +1,6 @@ # agent-browser 集成设计 -本文描述 PicoBot 1.3.1 的浏览器工具实现。目标是在保持模型侧单一 `browser` 工具协议的同时,用 agent-browser 完全替代 Fantoccini、ChromeDriver 和 WebDriver,并让浏览器状态、并发、产物和健康检查服从 PicoBot 的 Session 生命周期。 +本文描述 PicoBot 1.4.0 的浏览器工具实现。目标是在保持模型侧稳定 `browser` 协议的同时,用 agent-browser 完全替代 Fantoccini、ChromeDriver 和 WebDriver,并让临时会话、单用户多持久 Profile、管理操作、产物和健康检查拥有明确边界。 ## 1. 选择与边界 @@ -10,7 +10,7 @@ PicoBot 使用 agent-browser CLI 的 `--json` 协议,不直接链接其内部 - agent-browser 是原生 Rust CLI + daemon,daemon 通过 Chrome CDP 驱动浏览器;CLI 进程很短,浏览器状态跨命令保存在 daemon 中。 - CLI 是项目的稳定公开边界,PicoBot 不需要依赖 agent-browser 的内部 Rust 模块布局。 -- PicoBot 包装层可统一绑定 dialog、限制并发和输出、校验 URL、控制截图目录,并把图片接入现有 `ToolResultWithMedia`。 +- PicoBot 包装层可统一管理个人浏览器身份、限制并发和输出、校验 URL、控制 Profile/截图目录,并把图片接入统一 `ToolOutput` 后处理管线。 - 直接暴露 MCP 会让 session ID、文件路径、输出规模和安全策略落到模型参数中,也难以自动绑定当前 PicoBot dialog。 这不是把浏览器逻辑重新实现一遍。元素定位、accessibility snapshot、页面交互、Chrome 启动、CDP 通信和 daemon 生命周期均由 agent-browser 负责;PicoBot 只负责编排和边界控制。 @@ -21,9 +21,9 @@ PicoBot 使用 agent-browser CLI 的 `--json` 协议,不直接链接其内部 AgentLoop │ ToolExecutionContext(session_id, turn_id) ▼ -BrowserTool 模型侧单一 browser schema +BrowserTool / BrowserProfilesTool 浏览与持久 Profile 管理 schema ▼ -BrowserManager dialog → opaque session;并发/空闲/产物 +BrowserManager 临时 dialog session / 共享持久 Profile ├─ security URL、DNS、私网与 allowlist 前置校验 ├─ action browser action → CLI argv └─ AgentBrowserRunner timeout、env、--json、错误与输出解析 @@ -33,26 +33,30 @@ agent-browser CLI → Rust daemon → Chrome/Chromium CDP 源文件: -- `src/tools/browser/mod.rs`:工具 schema 与入口。 +- `src/tools/browser/mod.rs`:`browser`、`browser_profiles` schema 与入口。 - `src/tools/browser/action.rs`:严格参数解析和 argv 映射。 -- `src/tools/browser/manager.rs`:会话表、per-session mutex、空闲回收、截图媒体。 +- `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. 会话与并发 +## 3. 会话、持久 ID 与并发 -`SessionManager` 为每次 AgentLoop 执行传入完整 PicoBot session ID。BrowserManager 第一次看到该 ID 时生成随机、不透明的 `picobot-` agent-browser session,模型不能选择或猜测底层 session。 +BrowserManager 按每次调用是否携带 `persistent_id` 分流,两种浏览器可在同一个 Gateway 中同时使用: -- 同一个 dialog:所有浏览器 action 由该 session 的 mutex 串行,cookie、storage、历史和当前页面连续。 -- 不同 dialog:使用不同 agent-browser session,可并发执行。 -- 子 Agent:若显式获准使用 `browser`,沿用发起任务的 PicoBot session,因此与主 Agent 共享同一浏览器并受同一 mutex 保护。 -- Scheduler:使用 `cron:` 隔离,不与交互 dialog 混用。 -- `close`:先从 PicoBot 映射表移除,再调用 agent-browser close;重复关闭幂等。 -- 空闲回收:创建新会话前移除超过 `idle_timeout_secs` 的映射并尽力关闭底层 session。 -- 容量:达到 `max_sessions` 且没有可回收会话时明确失败,不静默复用别人的浏览器。 +- 省略 `persistent_id` 时使用普通临时浏览器。`SessionManager` 传入完整 PicoBot session ID,BrowserManager 第一次看到该 ID 时生成随机、不透明的 `picobot-` agent-browser session。同一 dialog 串行、不同 dialog 可并发;空闲会话按 `idle_timeout_secs` 回收,数量受 `max_sessions` 限制。 +- 需要长期保留登录或站点状态时,Agent 可以自主调用 `browser_profiles(action=create,label=...)` 创建持久身份,并在后续相关的每个 `browser` action 中显式传入返回的 `persistent_id`。 -Gateway 配置重载会构造新的 ToolRegistry/BrowserManager;旧运行代按现有 drain 规则退出。agent-browser daemon 的空闲退出时间通过 `AGENT_BROWSER_IDLE_TIMEOUT_MS` 同步设置,避免遗留浏览器无限驻留。 +- 用户可以拥有多个 `picobot-profile-<32 hex>` ID。`browser_profiles(action=create,label=...)` 创建 `persistence.profile_dir/` 专用目录和可选语义标签;标签可通过 `set_label` 重命名,ID 保持不变。 +- PicoBot 不保存默认 ID,也不按 dialog 自动选择或绑定持久 Profile。没有 ID 的调用始终回到该 dialog 的临时浏览器,不会隐式选中任何持久身份。 +- 每个 ID 分别映射 agent-browser `--session`、Chrome `--profile` 路径和 mutex。同一 ID 可从不同 dialog、子 Agent 或 Scheduler 使用并保持串行;不同 ID 的浏览器状态和锁相互独立,可以并发。 +- ID 与 Profile 目录跨 Gateway 重启、配置重载和 agent-browser daemon 空闲退出保持不变;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` 同步设置。 ## 4. Action 映射 @@ -67,9 +71,9 @@ Gateway 配置重载会构造新的 ToolRegistry/BrowserManager;旧运行代 | `screenshot` | `screenshot [--full] [--annotate]` | | `close` | `close` | -每次调用都使用 argv 数组直接启动进程,不经过 Shell。`fill` / `type` 的内容不会写入 PicoBot 日志;日志只记录 action 名和是否绑定 session。 +每次调用都使用 argv 数组直接启动进程,不经过 Shell。`fill` / `type` 的内容不会写入 PicoBot 日志;日志只记录 action 名、是否绑定 session 和所选持久 ID。 -Runner 固定传入 `--session`、`--json` 和明确的 headed 状态,并设置: +Runner 固定传入 `--session`、`--json` 和明确的 headed 状态;携带持久 ID 的调用额外传入由 Manager 生成的 `--profile `。Runner 还设置: - `AGENT_BROWSER_EXECUTABLE_PATH`(配置后) - `AGENT_BROWSER_CONTENT_BOUNDARIES` @@ -86,13 +90,16 @@ Runner 固定传入 `--session`、`--json` 和明确的 headed 状态,并设 命令成功后 PicoBot 再验证文件存在、是普通文件且非空,然后返回: ```text -ToolResultWithMedia { +ToolOutput { result: ToolResult { output: "Screenshot saved: ..." }, - media_refs: [MediaRef { media_type: "image", path: "..." }] + artifacts: [ToolArtifact { + media_ref: MediaRef { media_type: "image", path: "..." }, + audience: ModelAndUser + }] } ``` -因此多模态 Provider 可在下一轮直接看到图片,历史中仍只保存短路径清单,不产生 Base64 上下文膨胀。 +统一处理器会把截图同时交给下一轮多模态 Provider,并附到本 Turn 最终 assistant 回复供用户查看。`present_to_user` 默认为 `true`;显式设为 `false` 时 audience 改为 `Model`,适用于无需展示的内部视觉检查。历史中仍只保存短路径清单,不产生 Base64 上下文膨胀。 ## 6. 安全模型 @@ -104,9 +111,12 @@ ToolResultWithMedia { - 默认开启 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`: @@ -146,7 +156,7 @@ agent-browser install ## 8. 使用 -1. 浏览器工具默认启用;从旧配置删除 `webdriver_url`、`chrome_path`,加入新的 browser 字段。缺少依赖不会阻止 Gateway 启动,只会让 health 和实际浏览器调用失败。 +1. 浏览器工具默认启用;从旧配置删除 `webdriver_url`、`chrome_path`,仅在需要改变持久目录位置时配置 `persistence.profile_dir`。没有持久化模式开关。缺少依赖不会阻止 Gateway 启动,只会让 health 和实际浏览器调用失败。 2. 运行 `picobot health`;应看到 agent-browser CLI 版本和 offline quick doctor 通过。 3. 启动或重载 Gateway。 4. 对 Agent 说“使用浏览器打开 …”。模型的推荐动作序列是: @@ -162,6 +172,19 @@ 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` 是唯一检查实现: @@ -170,14 +193,16 @@ agent-browser 的 `@e` 引用属于当前页面快照。导航、弹窗或 DOM - `/health`:当前 Gateway 配置的聊天入口。 - `health` Tool:Agent 可调用的只读入口,支持 `json=true`。 -检查项包括 workspace、Bash、内容/文件搜索后端、可选 systemctl、配置中的 stdio MCP 命令,以及浏览器启用时的 agent-browser 版本、显式浏览器路径和 `doctor --offline --quick --json`。检查不安装软件、不执行 `doctor --fix`、不访问 Provider API,也不输出配置密钥。 +检查项包括 workspace、Bash、内容/文件搜索后端、可选 systemctl、配置中的 stdio MCP 命令,以及浏览器启用时的持久 Profile 可用性、agent-browser 版本、显式浏览器路径和 `doctor --offline --quick --json`。检查不创建或删除 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`;不要让多个 dialog 共享同一底层 ID。 +- 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。 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b67eb39..ab9b821 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -195,7 +195,7 @@ Session ID 格式为: 当前 WebUI/TUI Turn 通过 `send_message(files=...)` 向自身 session 投递文件时,文件先进入 task-local Turn delivery 暂存区,成功结束后附加到最终 assistant 消息,与工具链一起原子提交;因此持久化和刷新后的顺序都是工具调用/结果在前、携带附件的最终回复在后,也不会生成带 `[message from ...]` 的自投递气泡。其他同 Turn 自投递仍是受控例外:只有 task-local Turn ID 仍匹配该 session 的 active Turn,写入才允许不递增 `state_version`。跨 Turn、跨 session 以及无法证明所有权的写入仍必须递增版本。Provider 回放历史附件时,只有 user 输入和当前工具结果可生成模型原生媒体块;assistant/system 附件只回放文本清单,避免把图片放到供应商不接受的角色。 -SessionManager 负责组装会话上下文:系统提示、Skills、召回的 Knowledge、压缩后的 Timeline、可选的 active plan 摘要和当前消息历史。`session::turn_input` 在 Session 锁外并行读取 Knowledge、active plan 并压缩历史,然后通过同一个 assembly 路径生成首次请求和 context-overflow 重试输入;重试不得复制系统提示或 runtime context 拼接逻辑。普通闲聊 session 没有 plan 摘要;计划状态由 `WorkManager` 从 SQLite 读取,因此不以自然语言摘要作为权威来源。`AgentLoop` 接收完整输入执行一次模型/工具循环,本身不拥有会话状态。执行工具时额外传递只包含 session/turn 身份的 `ToolExecutionContext`;无状态工具使用默认实现忽略它,有状态外部适配器必须用它隔离资源,不能自行反向查询 SessionManager。 +SessionManager 负责组装会话上下文:系统提示、Skills、召回的 Knowledge、压缩后的 Timeline、可选的 active plan 摘要和当前消息历史。`session::turn_input` 在 Session 锁外并行读取 Knowledge、active plan 并压缩历史,然后通过同一个 assembly 路径生成首次请求和 context-overflow 重试输入;重试不得复制系统提示或 runtime context 拼接逻辑。普通闲聊 session 没有 plan 摘要;计划状态由 `WorkManager` 从 SQLite 读取,因此不以自然语言摘要作为权威来源。`AgentLoop` 接收完整输入执行一次模型/工具循环,本身不拥有会话状态。执行工具时额外传递只包含 session/turn 身份的 `ToolExecutionContext`;无状态工具使用默认实现忽略它,有状态外部适配器用它路由资源,但可按明确的单用户配置跨 dialog 共享,且不能自行反向查询 SessionManager。 当前 Turn 的工具进度只从 `AgentLoop` 的结构化 `TurnEvent` 进入 `TurnController`,不能另建字符串 notification 通道重复投递。后台子 Agent 的 `TaskNotification` 表达跨 Turn 的任务完成,仍由独立的受监督消费者投递。自动标题属于非关键派生工作:Turn 持久化完成后由 `TaskSupervisor` 调度,Session worker 不等待模型生成;同一 Session 同时最多有一个标题任务,提交时仍校验标题保持默认值,避免覆盖用户改名。 @@ -251,9 +251,9 @@ Gateway 在 `/` 提供随二进制编译的 HTML/CSS/JavaScript,不依赖外 WebUI/TUI 文件字节通过受鉴权的 HTTP 接口流式传输,WebSocket 只携带短期 `upload_id` 和结构化附件描述。`UploadRegistry` 在内存中按 `cli_chat` chat scope 校验并消费待发送上传;消息继续以 `media_refs` 保存 Gateway 本地路径,不建立永久附件资产。下载接口必须通过 client、session、message 和附件序号反查路径,不能接受客户端路径。历史附件路径失效属于正常状态,不得影响历史文本读取。待发送但未进入消息的上传由 `TaskSupervisor` 所有的限时清理任务回收。Agent 上下文会为所有媒体注入内部路径清单,客户端响应不得暴露该路径。 -工具默认通过 `ToolResult` 返回文本;需要把图片等产物交给模型时,通过 `Tool::execute_with_media` 返回文本和结构化 `MediaRef`。工具只负责经过自身路径策略校验后声明媒体,不感知当前模型或 Provider。`AgentLoop` 仅将最新连续工具结果批次的媒体交给 `MediaHandlerRegistry`,旧工具媒体只回放文本和路径,避免历史 Base64 膨胀。OpenAI-compatible Provider 保持 `tool` 结果为文本,并在完整工具批次后构造仅存在于请求内的临时多模态 `user` 消息;Anthropic Provider 将媒体放入对应 `tool_result.content`。媒体加载、格式或能力检查失败必须降级成文本,不得使历史记录不可读取。 +所有工具调用统一归一化为 `ToolOutput`,并由 `AgentLoop` 中唯一的 `ToolOutputProcessor` 后处理。普通文本工具仍实现 `ToolResult`,默认转换会将其包装为无产物的 `ToolOutput`;产物工具返回带 `ToolArtifact` 的输出,并用 `Model`、`User` 或 `ModelAndUser` 声明受众。处理器只发布成功工具的产物,去重后分别形成下一轮模型媒体和最终用户回复附件。工具只负责经过自身路径策略校验后声明产物与意图,不感知当前模型、Provider、Session 或 Channel。`AgentLoop` 仅将最新连续工具结果批次的模型媒体交给 `MediaHandlerRegistry`,旧工具媒体只回放文本和路径,避免历史 Base64 膨胀;用户媒体累积到本 Turn 最终 assistant 消息,随工具链原子持久化,并由 committed-history 或普通出站路径呈现。OpenAI-compatible Provider 保持 `tool` 结果为文本,并在完整工具批次后构造仅存在于请求内的临时多模态 `user` 消息;Anthropic Provider 将媒体放入对应 `tool_result.content`。媒体加载、格式或能力检查失败必须降级成文本,不得使历史记录不可读取。 -`browser` 是有状态工具适配器:`BrowserTool` 保持模型侧 action schema,`BrowserManager` 把 PicoBot dialog 映射到随机 agent-browser session,并用每 session mutex 保证同一页面串行、不同 dialog 并发;`AgentBrowserRunner` 以 argv 和 `--json` 调用外部原生 CLI,设置硬超时、输出/content boundaries/domain allowlist,底层 daemon 通过 Chrome CDP 工作。PicoBot 不链接 agent-browser 内部 crate、不直接暴露其 MCP、不使用 Fantoccini/ChromeDriver/WebDriver。截图只能写入配置的 artifact directory,并经 `ToolResultWithMedia` 返回。完整边界见 [AGENT_BROWSER_INTEGRATION.md](AGENT_BROWSER_INTEGRATION.md)。 +`browser` 是有状态工具适配器:`BrowserTool` 保持模型侧 action schema,`BrowserManager` 按每次调用是否带 `persistent_id` 分流。省略 ID 时把 PicoBot dialog 映射到随机临时 agent-browser session,并用每 session mutex 保证同一页面串行、不同 dialog 并发;长期工作需要保留登录或站点状态时,Agent 可自主创建持久身份并在后续相关 action 中持续传入同一个 ID。Manager 按持久 ID 保存 agent-browser session 和 mutex,同一 ID 跨 dialog 共享且串行,不同 ID 相互独立并可并发,Gateway 重启或 daemon 退出后继续使用原 Profile;没有全局持久化开关、默认 ID 或按 dialog 隐式选择。`browser_profiles` 在受控根目录下创建、设置语义化标签、列出或删除格式合法的 ID;标签只负责识别,选择仍使用不可变 ID,删除活动 ID 时先等待其 action 并关闭浏览器。`AgentBrowserRunner` 以 argv 和 `--json` 调用外部原生 CLI,设置硬超时、输出/content boundaries/domain allowlist,并在持久调用中传入受控 `--profile` 路径,底层 daemon 通过 Chrome CDP 工作。PicoBot 不链接 agent-browser 内部 crate、不直接暴露其 MCP、不使用 Fantoccini/ChromeDriver/WebDriver。持久 Profile 与 `allowed_domains` 因上游安全边界互斥;设置域名限制时临时浏览器仍可用,持久调用会被拒绝。截图只能写入配置的 artifact directory,并作为 `ModelAndUser` 产物返回,默认附到最终用户回复;仅当调用显式设置 `present_to_user=false` 时才作为模型内部观察。完整边界见 [AGENT_BROWSER_INTEGRATION.md](AGENT_BROWSER_INTEGRATION.md)。 `HealthService` 是依赖检查的唯一实现。CLI `picobot health`、只读 `health` 工具和 `/health` 斜杠命令必须复用它;检查可探测命令、版本、配置路径和 agent-browser offline quick doctor,但不能安装/修复软件、连接模型 API 或泄漏配置秘密。 diff --git a/resources/skills/about-picobot/assets/config.example.json b/resources/skills/about-picobot/assets/config.example.json index cf71937..76f53aa 100644 --- a/resources/skills/about-picobot/assets/config.example.json +++ b/resources/skills/about-picobot/assets/config.example.json @@ -96,7 +96,10 @@ "content_boundaries": true, "allowed_domains": [], "allow_private_hosts": false, - "artifact_dir": "~/.picobot/media/browser" + "artifact_dir": "~/.picobot/media/browser", + "persistence": { + "profile_dir": "~/.picobot/browser/profiles" + } }, "workspace_dir": "~/.picobot/workspace" } diff --git a/resources/skills/about-picobot/references/architecture.md b/resources/skills/about-picobot/references/architecture.md index 582c639..83aa1c9 100644 --- a/resources/skills/about-picobot/references/architecture.md +++ b/resources/skills/about-picobot/references/architecture.md @@ -62,7 +62,8 @@ Scheduler → SessionManager.handle_cron_message → AgentLoop → send_message - ChannelManager 持有 MessageBus 和所有 channel - OutboundDispatcher 通过 ChannelManager 路由出站消息 - 配置目录 `.env` 与 workspace `.env` 仅在单线程启动阶段分层加载,并使用 `unsafe { env::set_var(...) }` 写入进程环境;优先级为既有进程环境 > workspace > 配置目录 -- `browser` 工具默认启用,只有 `browser.enabled=false` 时不注册;缺少 CLI/Chrome 不阻止 Gateway 启动,但 health 和实际调用会给出安装错误。每个 PicoBot dialog 映射到独立 agent-browser session,底层原生 daemon 使用 Chrome CDP,不依赖 Fantoccini/ChromeDriver/WebDriver +- `browser` 工具默认启用,只有 `browser.enabled=false` 时不注册;缺少 CLI/Chrome 不阻止 Gateway 启动,但 health 和实际调用会给出安装错误。每次调用按参数分流:不传 `persistent_id` 时按 dialog 使用普通临时浏览器;长期工作时 Agent 可自主创建持久身份,并在后续相关 action 中持续传入同一个 ID。同一 ID 跨 dialog 共享 session/锁,不同 ID 相互独立并可并发。`browser_profiles` 只在受控根目录中创建、设置语义标签、列出或删除合法 ID;没有全局持久化开关、默认 ID,也不自动按 dialog 建立或选择持久 Profile,不依赖 Fantoccini/ChromeDriver/WebDriver +- 所有工具调用统一包装为 `ToolOutput` 并经过公共处理器;产物按模型/用户受众分流。浏览器截图默认同时供模型查看并附到最终回复,`file_read` 图片默认仅供模型理解 - 同一 session 的普通消息串行处理,不同 session 可并发;session 队列容量为 32,满时明确拒绝 - 出站消息按 `(channel, chat_id)` 分 lane 保序;lane 容量为 64,慢目标不阻塞其他目标 - 活动 Turn 与普通出站消息共享 `(channel, chat_id)` 写锁;禁止把 token delta 放入 MessageBus diff --git a/resources/skills/about-picobot/references/config.md b/resources/skills/about-picobot/references/config.md index 471bbf1..7319e60 100644 --- a/resources/skills/about-picobot/references/config.md +++ b/resources/skills/about-picobot/references/config.md @@ -129,7 +129,7 @@ MCP 服务器单条配置: ## browser 字段 -浏览器工具默认开启并注册 `browser` 工具。缺少外部依赖不会阻止 Gateway 启动,但实际调用会返回安装错误,`picobot health` 会提前判定。上层由 PicoBot 管理 dialog 会话与媒体,底层调用 agent-browser JSON CLI;不再依赖 Fantoccini、ChromeDriver 或 WebDriver。 +浏览器工具默认开启并注册 `browser` 与 `browser_profiles` 工具。缺少外部依赖不会阻止 Gateway 启动,但实际调用会返回安装错误,`picobot health` 会提前判定。上层由 PicoBot 管理浏览器生命周期与媒体,底层调用 agent-browser JSON CLI;不再依赖 Fantoccini、ChromeDriver 或 WebDriver。 | 字段 | 类型 | 默认 | 说明 | |------|------|------|------| @@ -137,7 +137,7 @@ MCP 服务器单条配置: | `command` | string | agent-browser | CLI 名称或绝对路径 | | `headless` | bool | true | 是否无头运行 | | `browser_executable_path` | string | - | 自定义 Chrome/Chromium 可执行文件路径 | -| `max_sessions` | int | 4 | 同时保留的 dialog 浏览器会话上限 | +| `max_sessions` | int | 4 | 同时保留的普通 dialog 临时浏览器会话上限;持久身份不计入 | | `idle_timeout_secs` | int | 900 | PicoBot 会话清理及 agent-browser daemon 空闲退出时间 | | `command_timeout_secs` | int | 120 | 单次 CLI 调用硬超时 | | `max_output_chars` | int | 50000 | 页面来源文本输出上限 | @@ -145,6 +145,11 @@ MCP 服务器单条配置: | `allowed_domains` | []string | [] | 可选域名白名单;空数组表示不启用域名限制 | | `allow_private_hosts` | bool | false | 是否允许回环、私网和本地域名 | | `artifact_dir` | string | ~/.picobot/media/browser | 截图产物目录 | +| `persistence.profile_dir` | string | ~/.picobot/browser/profiles | 持久 ID、语义标签和 Profile 目录的受控根目录 | + +持久化不是配置模式。`browser` 调用省略 `persistent_id` 时使用当前 dialog 的普通临时浏览器;长期工作需要保留登录或站点状态时,Agent 可自主调用 `browser_profiles(create,label=...)` 生成 `picobot-profile-`,对应数据位于 `profile_dir/`,然后在该工作的后续每个 action 中持续传入同一个 ID。没有默认 ID,也不会按 dialog 自动选择持久身份。同一 ID 跨 dialog 共享底层 session 与串行锁,不同 ID 各自独立并可并发。`browser_profiles` 还支持 `set_label`、`list` 和使用精确 ID 的 `delete`;标签可重命名,但选择浏览器始终使用不可变 ID。 + +agent-browser 0.33.0 不允许持久 Profile 与 `allowed_domains` 同时使用。配置域名限制后普通临时浏览器仍可用,创建或使用持久身份会被拒绝,health 会提示该可选能力受限。Profile 包含登录凭据,应把目录视为敏感数据,不得提交到版本控制、跨用户共享或放在不受信任的网络文件系统中。 旧字段 `webdriver_url`、`chrome_path` 不再接受。推荐安装 `agent-browser@0.33.0` 后运行 `agent-browser install`;Linux 可运行 `agent-browser install --with-deps`。使用前用 `picobot health` 检查 CLI 与 Chrome 环境。 diff --git a/resources/skills/about-picobot/references/tools.md b/resources/skills/about-picobot/references/tools.md index 8b44c18..d99cdaa 100644 --- a/resources/skills/about-picobot/references/tools.md +++ b/resources/skills/about-picobot/references/tools.md @@ -157,7 +157,7 @@ Cron 不是一个带 `action` 的统一工具,而是六个独立工具;仅 ## browser — 浏览器自动化 -默认注册;设置 `browser.enabled=false` 后不注册。PicoBot 上层包装统一 action 和结构化媒体,底层逐次调用 agent-browser `--json`;每个 PicoBot dialog 映射到一个不透明的 agent-browser session,同 dialog 串行、不同 dialog 可并发。CLI daemon 自动常驻并通过 Chrome CDP 工作,不使用 Fantoccini、ChromeDriver 或 WebDriver。 +默认注册;设置 `browser.enabled=false` 后不注册。PicoBot 上层包装统一 action 和结构化媒体,底层逐次调用 agent-browser `--json`。调用时不传 `persistent_id`,每个 dialog 使用各自的普通临时 session;长期工作需要保留登录或站点状态时,Agent 可自主用 `browser_profiles(create,label=...)` 创建身份,并在后续相关 action 中持续传入同一个 ID。PicoBot 没有全局持久化开关或默认持久 ID,也不按 dialog 自动选择持久身份;同一 ID 跨 dialog 共享 session 和串行锁,不同 ID 使用独立 session/锁并可并发。CLI daemon 通过 Chrome CDP 工作,不使用 Fantoccini、ChromeDriver 或 WebDriver。 | action | 说明 | |--------|------| @@ -166,12 +166,16 @@ Cron 不是一个带 `action` 的统一工具,而是六个独立工具;仅 | `click`, `click_at` | 点击元素或坐标 | | `fill`, `type`, `press` | 输入文本或按键 | | `get_text`, `get_title`, `get_url` | 读取页面信息 | -| `screenshot` | 保存到 `browser.artifact_dir` 并返回结构化图片媒体;支持 `full_page`、`annotate` | +| `screenshot` | 保存到 `browser.artifact_dir`,交给模型并默认附到最终用户回复;支持 `full_page`、`annotate`,可用 `present_to_user=false` 仅供模型检查 | | `focus`, `hover`, `scroll`, `wait` | 常见交互和等待 | | `close` | 关闭浏览器会话 | 典型流程:`open` → `snapshot` 获取 `@e` 引用 → 交互 → 页面变化后重新 `snapshot`。`path` 只接受 `.png` 文件名,不能逃逸产物目录。`open` 默认拒绝非 HTTP(S)、userinfo、回环、私网、本地域名及 DNS 解析到私网的地址;配置 `allowed_domains` 后,agent-browser 同时限制导航、子资源、WebSocket、EventSource 与 WebRTC。页面输出是不可信内容,默认开启 content boundary 元数据和 50,000 字符上限。 +## browser_profiles — 持久浏览器身份管理 + +与 `browser` 一同注册。`create` 生成新的持久 ID 和 Profile 目录,并可接受 1–80 字符的语义标签;`set_label` 按精确 ID 重命名标签;`list` 返回每个合法 Profile 的 ID、标签、目录和 active 状态;`delete` 必须传入 `create/list` 返回的精确 ID,并删除该 ID 的完整 Chrome Profile、标签与登录态。删除活动 Profile 时会等待其操作完成并尝试关闭浏览器。标签只用于识别,不能代替 ID 选择;该工具不能接收任意目录。非空 `browser.allowed_domains` 下普通临时浏览器仍可用,但持久身份不能创建或使用。 + 依赖缺失时必须把错误和处置命令返回给用户,不能声称已浏览,也不能在工具内部静默安装:CLI 不存在时安装 `agent-browser@0.33.0`;Chrome 不存在时运行 `agent-browser install`;Linux 共享库不完整时运行 `agent-browser install --with-deps`。用 `picobot health` 复查,再用 `agent-browser doctor` 获取详细上游诊断。用户明确不需要浏览器时才建议 `browser.enabled=false`。 ## health — 依赖检查 diff --git a/resources/templates/config.example.json b/resources/templates/config.example.json index 42aee2d..9c36030 100644 --- a/resources/templates/config.example.json +++ b/resources/templates/config.example.json @@ -104,7 +104,10 @@ "content_boundaries": true, "allowed_domains": [], "allow_private_hosts": false, - "artifact_dir": "~/.picobot/media/browser" + "artifact_dir": "~/.picobot/media/browser", + "persistence": { + "profile_dir": "~/.picobot/browser/profiles" + } }, "workspace_dir": "~/.picobot/workspace" }