# PicoBot 配置说明

配置文件加载顺序：`~/.picobot/config.json` → 当前目录 `./config.json`。
占位符 `<VAR_NAME>` 从启动环境替换。PicoBot 依次加载 `config.json` 同目录的 `.env`、`workspace_dir/.env`，最后保留启动进程已有环境变量作为最高优先级；workspace 层覆盖配置目录层。合并值也会进入进程环境，供 MCP 和工具子进程继承。workspace `.env` 不能修改用于定位自身的 `workspace_dir`。

Gateway WebUI 的“配置”页可以编辑实际加载的配置文件。读取时 API Key、secret、password 和 token 会显示为 `********`，保持掩码不变再保存会保留原值；写入采用同目录临时文件替换。Gateway 会忽略可恢复的历史未知字段、类型不匹配字段和失效的非核心命名条目，在配置页按 JSON Pointer 显示诊断，但不会自动改写原始文件；“一键清除”由后端校验文件 revision 后原子删除已诊断项，普通保存仍严格拒绝无效配置。JSON 损坏、不可用的 `default` Agent 链路和无法安全构造运行代的错误不会被忽略。运行配置保存或清理后可执行 `picobot reload`、发送 `/reload`，或由根交互 Agent 在用户明确要求时调用 `reload_config` 工具。Gateway 会先校验候选配置，停止接收新工作并等待交互 Turn、Scheduler job 和后台子 Agent 到达安全边界后切换；失败时继续使用旧配置。`GET /api/config/reload/status` 可查询 generation、相位与最近错误。`gateway.host`、`gateway.port`、`workspace_dir` 和 `gateway.session_db_path` 的有效路径必须通过完整重启变更。`USER.md` 与 `AGENTS.md` 的修改用于后续构建的 Agent 上下文。

## config.json 结构

```jsonc
{
  "providers": {},    // LLM 提供商配置
  "models": {},       // 模型配置
  "agents": {},       // Provider/Model profile
  "context_compaction": {}, // 上下文 reserve 预算与近期保留量
  "agent_orchestration": {}, // 具名子 Agent Definition 与编排上限
  "gateway": {},      // 网关配置
  "client": {},       // 客户端配置
  "channels": {},     // 渠道配置
  "memory": {},       // 记忆系统配置
  "workspace_dir": "", // 工作目录，默认 ~/.picobot/workspace
  "mcp": {},           // MCP 服务器配置
  "browser": {}        // 可选浏览器自动化配置
}
```

完整示例见 `assets/config.example.json`。

## providers 字段

| 字段 | 说明 |
|------|------|
| `type` | 提供商类型: `openai`（兼容 OpenAI API）、`anthropic` |
| `base_url` | API 端点地址 |
| `api_key` | API 密钥，支持 `<ENV_VAR>` 占位符 |
| `extra_headers` | 额外 HTTP 头 |

## models 字段

| 字段 | 说明 |
|------|------|
| `model_id` | 模型标识名称 |
| `temperature` | 采样温度，可选 |
| `max_tokens` | 最大输出 token 数，可选 |
| `token_limit` | 模型上下文窗口硬上限，可选；未配置时默认为 128000，Agent 只能进一步收紧 |
| `input_type` | 模型支持的输入类型，如 `["text"]` 或 `["text", "image"]`，默认 `["text"]`. 纯内部使用，不会传递给 LLM API |

## agents 字段

| 字段 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `provider` | string | - | 提供商名称（对应 providers key） |
| `model` | string | - | 模型名称（对应 models key） |
| `max_tool_iterations` | int | 99 | 最大工具调用轮数 |
| `token_limit` | int | 使用模型上限 | 可选的 Agent 上限；有效窗口取 Agent 与模型（模型未配置时为 128000）的最小值 |

## context_compaction 字段

自动压缩只使用 reserve 公式 `context_tokens > context_window - effective_reserve`。小窗口下 `effective_reserve = min(reserve_tokens, context_window / 2)`，近期原样保留量最多为有效阈值的一半。

| 字段 | 默认 | 说明 |
|------|------|------|
| `enabled` | true | 只控制 Turn 前自动压缩；不禁用 `/compact` 或 overflow 恢复 |
| `reserve_tokens` | 16384 | 为输出、工具迭代和估算误差预留的输入窗口 |
| `keep_recent_tokens` | 20000 | checkpoint 后尽量原样保留的近期历史 token |

## agent_orchestration 字段

子 Agent 编排是 PicoBot 的内在机制，始终启用、不可关闭；该配置块只控制定义目录与各类上限。`definitions_dir` 相对 `config.json` 所在目录解析，且不得通过绝对路径或 symlink 逃逸该受信任配置目录。Gateway 启动和热重载会严格校验全部 Markdown Definition；任一无效 Provider profile、工具、Skill 或委托目标会拒绝整个候选运行代。

| 字段 | 默认 | 说明 |
|------|------|------|
| `definitions_dir` | agents | 第一层 `*.md` Definition 目录 |
| `max_tree_depth` | 4 | Root-relative 委托深度硬上限 |
| `max_runs_per_tree` | 16 | 单任务树 run 预算（树级原子计数强制） |
| `max_concurrent_runs` / `max_concurrent_runs_per_session` | 6 / 4 | background run 接纳配额（global→session 顺序获取，runner 持有至 terminal commit；foreground 不占） |
| `max_concurrent_provider_steps` / `..._per_session` | 8 / 4 | Provider step 上限（global→session） |
| `max_concurrent_tool_steps` / `..._per_session` | 16 / 8 | 普通工具 step 上限（global→session） |
| `max_pending_inbox_events_per_session` | 128 | durable inbox 容量（条件更新，预留槽不可被信号挤占） |
| `inbox_event_ttl_hours` | 168 | inbox 事件 TTL（已预留配置；TTL 清理尚未实现） |
| `max_inbox_delivery_attempts` | 8 | inbox 最大投递次数，超限 dead-letter |
| `max_user_turn_burst_before_inbox` | 4 | 用户 Turn 公平调度阈值 |
| `max_inbox_wait_secs` | 30 | inbox 最大等待阈值 |

已实现：具名 foreground 与 background（Root 单任务或批量）、内联 `provider`/`model` 或 `llm_profile`、工具集由定义文件 `tools` 决定、批量并发、父子委托边校验、durable inbox continuation（空闲时完成即返回）、`emit_signal`（queue/steer）、run quota 与 step gate、内置 general-purpose 定义与 WebUI「子 Agent」管理页。未开放：子 Agent 发起的 background、`idempotency_key` 工具入口。旧匿名 general 兼容路径已移除，委托必须指定具名 `target`。

## gateway 字段

| 字段 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `host` | string | 127.0.0.1 | 监听地址 |
| `port` | int | 19876 | 监听端口 |
| `require_pairing` | bool | true | 是否要求 WebUI 与 CLI 设备先使用一次性代码配对 |
| `session_ttl_hours` | int | - | 兼容/预留字段；当前没有会话 TTL 清理循环 |
| `session_db_path` | string | - | SQLite 数据库路径，默认在配置目录 `data/` 下 |
| `cleanup_interval_minutes` | int | - | 兼容/预留字段；当前没有按此间隔运行的 session 清理任务 |
| `scheduler` | object | - | 调度器配置 |

### gateway.scheduler 字段

| 字段 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `enabled` | bool | true | 是否启动调度器并注册 cron 工具 |
| `poll_interval_secs` | int | 60 | 检查到期任务的轮询间隔 |
| `max_concurrent` | int | 1 | 同时执行的 Scheduled Run 上限，运行时限制在 1–256；投递使用独立有界并发 |
| `execution_timeout_secs` | int | 900 | 单个定时任务 Agent 执行的硬超时；Job 执行租约额外覆盖关停宽限，投递由持久化 outbox 独立恢复 |

## memory 字段

| 字段 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `consolidation_provider` | string | 主 Agent provider | 当前记录在 MemoryManager 中供后续归并使用；压缩摘要仍使用 Session provider |
| `consolidation_model` | string | 主 Agent model | 当前记录在 MemoryManager 中供后续归并使用；压缩摘要仍使用 Session model |
| `recall_limit` | int | 5 | 预期的每轮知识召回上限；当前 worker 固定使用 5 |
| `idle_consolidation_minutes` | int | 10 | 预留的空闲归并阈值；当前无对应循环 |
| `timeline_retention_days` | int | 90 | 默认日常维护巡检删除超过该期限的 Timeline；Knowledge 不受影响 |
| `max_failures_before_degrade` | int | 3 | 预留的归并失败阈值；当前无失败降级循环 |

注意：当前 worker 的 Knowledge 召回数量仍固定为 5；idle consolidation 和失败降级循环尚未接入。Timeline 清理由默认启用的 `picobot-routine-maintenance` Scheduled Run 执行；该任务使用 `never` 策略，结构化结果只进入运行审计和 Health。

## channels.feishu 字段

| 字段 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `enabled` | bool | false | 是否启用 |
| `app_id` | string | - | 飞书应用 ID |
| `app_secret` | string | - | 飞书应用密钥 |
| `allow_from` | []string | ["*"] | 允许交互的用户列表 |
| `require_mention` | bool | true | 群聊中是否必须明确 @ 机器人；无法解析机器人身份时安全地忽略群消息 |
| `agent` | string | - | 使用的 agent 名称 |
| `media_dir` | string | ~/.picobot/media/feishu | 配置默认值；Gateway 注册渠道时会覆盖为 `{workspace}/media/feishu` |
| `reaction_emoji` | string | "Typing" | 回复意向表达的表情 |
| `live_updates` | bool | false | 是否用单张卡片实时编辑活动 Turn；关闭时只发送终态 |
| `live_update_interval_ms` | int | 500 | 卡片更新最小间隔，运行时限制在 250–5000ms |
| `max_image_bytes` | int | 10485760 | 单个入站/出站图片的最大字节数 |
| `max_file_bytes` | int | 26214400 | 单个入站/出站文件、音频或视频的最大字节数 |
| `media_dir_max_bytes` | int | 536870912 | 飞书媒体目录容量上限；达到上限后拒绝新下载，不自动删除旧文件 |
| `request_timeout_secs` | int | 30 | 单次飞书 HTTP 请求及响应体读取的硬超时，运行时限制在 5–120 秒 |

飞书属于外部渠道：无论是否开启实时卡片，都不会接收模型 reasoning；工具只显示紧凑状态。渠道配置可通过 Gateway 配置重载生效。

## mcp 字段

| 字段 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `servers` | array | [] | MCP 服务器列表 |
| `tool_timeout_secs` | int | 180 | 工具调用超时秒数 |

MCP 服务器单条配置:

| 字段 | 说明 |
|------|------|
| `name` | 服务器名称 |
| `enabled` | 是否启用，默认 true；关闭后启动/重载时不连接该服务器 |
| `transport` | 传输方式: `stdio`、`sse`、`streamable-http` |
| `command` | 启动命令（stdio 模式） |
| `args` | 命令参数 |
| `env` | 子进程环境变量 |
| `url` | URL（sse / streamable-http 模式） |
| `headers` | HTTP 传输额外请求头 |
| `tool_timeout_secs` | 单独的超时设置 |
| `tool_settings` | 按 MCP 原始工具名索引的本地执行属性声明；每项可设 `read_only` 与 `exclusive`，均默认 false |

`tool_settings` 示例：

```json
{
  "tool_settings": {
    "read_file": { "read_only": true },
    "refresh_index": { "exclusive": true }
  }
}
```

`可并发` 不需要也不能单独配置：它始终等于 `read_only && !exclusive`。未声明的 MCP 工具按可能有副作用且顺序执行处理。WebUI「工具 → MCP」展开服务器后可编辑这些复选框；编辑先暂存，可跨多个工具一次“保存并应用”并热重载，离开 MCP 标签或刷新页面会丢弃未保存草稿。

## browser 字段

浏览器工具默认开启并注册 `browser` 与 `browser_profiles` 工具。缺少外部依赖不会阻止 Gateway 启动，但实际调用会返回安装错误，`picobot health` 会提前判定。上层由 PicoBot 管理浏览器生命周期与媒体，底层调用 agent-browser JSON CLI；不再依赖 Fantoccini、ChromeDriver 或 WebDriver。

| 字段 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `enabled` | bool | true | 是否启用浏览器工具；关闭后不注册 `browser` |
| `command` | string | agent-browser | CLI 名称或绝对路径 |
| `headless` | bool | true | 是否无头运行 |
| `browser_executable_path` | string | - | 自定义 Chrome/Chromium 可执行文件路径 |
| `max_sessions` | int | 4 | 同时保留的普通 dialog 临时浏览器会话上限；持久身份不计入 |
| `idle_timeout_secs` | int | 3600 | 大于零；普通 dialog 临时浏览器的空闲退出及 Manager 回收时间；持久浏览器不使用该超时 |
| `command_timeout_secs` | int | 120 | 单次 CLI 调用硬超时 |
| `max_output_chars` | int | 50000 | 页面来源文本输出上限 |
| `content_boundaries` | bool | true | 启用 agent-browser 不可信页面边界元数据 |
| `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 的普通临时浏览器，并在达到 `idle_timeout_secs` 后自动退出；长期工作需要保持浏览器进程或保留登录和站点状态时，Agent 可自主调用 `browser_profiles(create,label=...)` 生成 `picobot-profile-<uuid>`，对应数据位于 `profile_dir/<id>`，然后在该工作的后续每个 action 中持续传入同一个 ID。持久浏览器禁用 daemon 空闲自动退出，只通过显式 `browser(close)` 或 Profile 删除关闭。没有默认 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、浏览器安装和真实 headless 启动环境。

### 浏览器依赖故障处置

| health / 工具错误 | 处置 |
|---|---|
| `agent-browser` 未找到 | 运行 `npm install -g agent-browser@0.33.0`，或 `cargo install agent-browser --version 0.33.0 --locked` |
| CLI 已安装但找不到 Chrome/Chromium | 运行 `agent-browser install`；已有浏览器则设置 `browser_executable_path` 或 `AGENT_BROWSER_EXECUTABLE_PATH` |
| Linux 缺少共享库/系统包 | 运行 `agent-browser install --with-deps`，然后再运行 `agent-browser doctor` |
| 安装状态不明确 | 先运行 `picobot health` 获取 PicoBot 视角的结果，再运行 `agent-browser doctor` 查看完整上游诊断 |

不得在 Agent 工具调用中自动安装或执行 `doctor --fix`；安装会修改系统且可能需要管理员权限，应把命令报告给用户，由用户确认后执行。临时不需要浏览器时可设置 `browser.enabled=false`，此时 health 不要求 agent-browser/Chrome。
