# PicoBot

PicoBot 是一个用 Rust 编写的个人 AI 助手运行时。它在本地启动 Gateway，接入 CLI TUI、飞书/Lark 等聊天渠道，把会话、消息、记忆和定时任务持久化到 SQLite，并为 Agent 提供文件、Shell、HTTP、Web、MCP、Skill、记忆、浏览器和子 Agent 等工具能力。

它更像一个可扩展的“个人助手操作系统”：渠道负责收发消息，SessionManager 负责会话和上下文，AgentLoop 负责模型与工具循环，Storage 负责可靠落盘。

完整的组件边界、并发不变量、启动/关停顺序和扩展指南见 [架构文档](docs/ARCHITECTURE.md)。

## 适合做什么

- 在终端里和本地 AI 助手持续对话。
- 从脚本或命令行发送一条任务，等待完整的模型/工具循环后只输出最终结果。
- 在 TUI 或浏览器中实时查看正文、思考过程和工具执行状态，并在完成后收敛到持久化历史。
- 在浏览器中查看日志、任务和记忆，修改运行配置与助手档案。
- 在 WebUI 顶栏查看当前会话的累计输入/输出 Token、上下文窗口和占用比例。
- 复杂任务可创建 session 级 Todo 计划，把不同子项并行委托给多个子 Agent；聊天页侧栏实时显示进度。
- 将同一套 Agent 能力接入飞书/Lark，并可选用单张卡片实时更新回复。
- 让 Agent 使用本地文件、Shell、搜索、HTTP、浏览器、MCP 工具完成任务。
- 把长期偏好、事实和历史摘要存成可检索记忆。
- 用 Cron 运行隔离的 Root 或命名 Agent，以结构化结果决定始终通知、异常通知或静默记录。
- 通过 Skills 为 Agent 注入项目知识和专用操作指南。

## 快速开始

### 1. 准备环境

需要：

- Rust toolchain，项目使用 edition 2024。
- 一个可用的 LLM Provider API Key。

### 2. 构建项目

```bash
cargo build
```

### 3. 准备配置

PicoBot 按以下顺序加载配置：

1. `~/.picobot/config.json`
2. 当前目录 `./config.json`

Gateway 首次启动时会把模板释放到 `~/.picobot/config.example.json`。模板源文件在 [resources/templates/config.example.json](resources/templates/config.example.json)。

最小配置示例：

```json
{
  "providers": {
    "openai": {
      "type": "openai",
      "base_url": "https://api.openai.com/v1",
      "api_key": "<OPENAI_API_KEY>",
      "extra_headers": {}
    }
  },
  "models": {
    "gpt-4o": {
      "model_id": "gpt-4o",
      "temperature": 0.7,
      "max_tokens": 4096,
      "token_limit": 128000,
      "input_type": ["text", "image"]
    }
  },
  "agents": {
    "default": {
      "provider": "openai",
      "model": "gpt-4o",
      "max_tool_iterations": 99
    }
  },
  "workspace_dir": "~/.picobot/workspace"
}
```

`.env` 会在启动时由 PicoBot 自己解析，不依赖 dotenv。环境变量按以下顺序分层，越靠后优先级越高：

1. `config.json` 所在目录的 `.env`，作为所有 workspace 共用的基础配置。
2. `workspace_dir/.env`，用于当前 workspace 的覆盖值。
3. 启动 PicoBot 时进程中已有的环境变量，例如 Docker Compose 的 `environment`，优先级最高且不会被文件覆盖。

合并后的值既用于替换配置里的 `<OPENAI_API_KEY>` 等占位符，也会写入 PicoBot 进程环境，供 MCP Server 和工具子进程继承。`workspace_dir` 的位置由配置目录层和进程环境决定；workspace 自己的 `.env` 不能反过来修改 `workspace_dir`。

### 4. 启动 Gateway

```bash
cargo run -- gateway
```

默认监听 `127.0.0.1:19876`。Gateway 启动后会把进程工作目录切到 `workspace_dir`，默认 SQLite 数据库写到配置目录（`~/.picobot`）`data/` 下的 `picobot.db`，与 workspace 相互独立。

监听地址可通过配置文件或命令行覆盖。命令行参数优先于 `config.json`：

```json
{
  "gateway": {
    "host": "0.0.0.0",
    "port": 19876
  }
}
```

```bash
picobot gateway --host 0.0.0.0 --port 19876
```

Docker Compose 默认让容器内 Gateway 监听所有 IPv4 接口。监听地址、宿主机发布地址和端口均可通过环境变量调整：

```bash
PICOBOT_GATEWAY_HOST=0.0.0.0 \
PICOBOT_PUBLISH_HOST=192.168.1.10 \
PICOBOT_GATEWAY_PORT=19876 \
docker compose up -d
```

`PICOBOT_GATEWAY_HOST` 是容器内进程的监听地址；`PICOBOT_PUBLISH_HOST` 是 Docker 在宿主机上发布端口的地址。对局域网开放时应保持 `gateway.require_pairing=true`，并由防火墙限制可信网段。

### 5. 启动 CLI 客户端

另开一个终端：

```bash
cargo run -- chat
```

CLI 默认连接 `ws://127.0.0.1:19876/ws`。TUI 首次使用先运行 `picobot pair`，再执行 `picobot chat --pair-code <CODE>`；客户端令牌会以 `0600` 权限保存到 `~/.picobot/tui_auth_token`。如需指定地址，可使用 `--gateway-url`。

### 5.1 一次性执行

`run` 通过 Gateway 发送一条消息，复用正常的 SessionManager、AgentLoop 和工具调用流程，收到 Turn 终态后打印最终回复并退出：

```bash
picobot run "检查这个项目并总结测试结果"
printf '使用浏览器打开 example.com 并返回页面标题\n' | picobot run
```

默认情况下 stdout 只包含最终回复，便于管道和脚本消费。`--verbose` 把阶段和工具状态写到 stderr；`--json` 输出包含 session、turn、状态、正文、usage 和错误的一行 JSON；`--timeout` 设置最大等待秒数。超时或按下 Ctrl-C 时，客户端会先向当前会话发送 `/stop`。

连接本机回环地址时不需要人工配对：`run` 自动读取 `~/.picobot/web_admin_token`，Gateway 只有在真实 TCP 对端也是回环地址时才允许该凭据访问 `/ws`。每次调用使用独立的临时 chat scope，不会替换正在运行的 TUI 连接。连接远程 Gateway 时仍使用 `~/.picobot/tui_auth_token` 中已有的配对令牌。

### 5.2 健康检查

启动 Gateway 前可检查 PicoBot 核心命令、已启用功能的依赖、stdio MCP 命令和浏览器运行环境：

```bash
picobot health
picobot health --json
```

缺少核心或当前配置要求的依赖时退出码为 `1`；`rg` / `fd` 等有回退实现的加速项只会标记为 `DEGRADED`。运行中的 Gateway 也提供 `/health` 斜杠命令，Agent 可调用同名 `health` 工具，WebUI 的“配置 → 健康检查”可显示相同的结构化结果并手动复查；这些入口共享同一套只读检查逻辑。

Debian/Ubuntu 将同一个 fd 程序安装为 `fdfind`，两者都视为首选文件搜索后端；只有退回传统 `find` 时才提示性能警告。启用浏览器工具后，Health 除了检查 agent-browser 版本和浏览器路径，还会在隔离的临时 socket namespace 中执行完整离线 doctor，分别报告浏览器安装、真实 headless 启动和运行环境，因此可发现“文件存在但 Chrome 无法启动”或缺少 Linux 共享库等问题。Gateway 内按需检查还会报告定时任务的无效 Agent/渠道引用、投递积压、最近失败/超时/unknown、静默 unknown 和执行周期覆盖；Health 不会触发任务或连接 Provider。

### 5.3 使用 WebUI

Gateway 启动后直接打开：

```text
http://127.0.0.1:19876/
```

新浏览器默认不能直接进入。请在运行 Gateway 的同一台设备上生成一次性配对码：

```bash
picobot pair
```

在浏览器配对页输入输出的 8 位代码即可。配对码 5 分钟内有效且只能使用一次；浏览器凭据由 HttpOnly Cookie 保存。需要撤销全部浏览器和 CLI 客户端时运行 `picobot pair --revoke-all`，再用新代码重新配对。

Docker 部署必须在 Gateway 容器内签发配对码，使请求来自容器自身回环地址并能读取映射目录中的管理密钥：

```bash
docker compose exec picobot picobot pair --gateway-url http://127.0.0.1:19876
```

不要从宿主机经发布端口直接调用签发接口；容器会把该连接识别为非回环来源并拒绝。`picobot` 已加入正式镜像的 `PATH`，可在容器 shell 中直接调用。

WebUI 随二进制嵌入，不需要 Node.js、npm 或单独部署静态文件。界面采用本地实现的 Microsoft Fluent 2 视觉系统，提供语义化中性色表面、品牌蓝交互状态、统一组件层级和完整的浅色/深色主题，并提供：

- 在线聊天、会话创建/切换、历史回放、流式 Markdown、独立思考区、实时工具状态、可折叠历史工具调用卡片，以及基于 Gateway 实时命令清单的 `/` 斜杠命令补全。
- 文件选择、拖放和剪贴板图片上传；消息中的附件可预览或下载。附件按服务端路径引用，原文件移动或删除后历史附件可能不可用。
- “配置 → 外观”提供浅色/深色模式和六套 Fluent 品牌色；选择即时生效并保存在当前浏览器中，首次访问时明暗模式跟随系统偏好。
- Cron 定时任务、最近运行记录和后台子任务状态。
- 当前聊天 session 的可展开 Todo 侧栏；计划变化时自动展开，其他 session 的变化显示未读提示。
- Knowledge/Timeline 记忆的分类与全文检索。
- 本地滚动日志的尾部查看、过滤和自动刷新。
- 健康检查结果：按核心与已配置功能展示通过、警告、失败及处理建议。
- `config.json`、`~/.picobot/USER.md`、`~/.picobot/AGENTS.md` 编辑。

配置接口会掩码 API Key、secret、password 和 token；保留 `********` 再保存不会覆盖原密钥。Gateway 加载历史配置时会忽略可恢复的未知字段、类型不匹配字段和不再可用的非核心命名条目，并在日志、Health 和 WebUI 配置页显示对应 JSON Pointer；原始文件不会被自动修改。WebUI 的“一键清除”由后端按配置 revision 删除这些已忽略项，文件在展示后发生变化时会拒绝覆盖；普通保存仍严格拒绝包含无效项的新配置。JSON 语法错误、不可用的 `default` Agent 链路以及无法安全构造 Gateway 的错误仍会阻止启动或重载。运行配置采用原子写入，保存或清理后可执行 `picobot reload` 或发送 `/reload` 热重载；`USER.md` 和 `AGENTS.md` 的修改用于后续构建的 Agent 上下文。

WebUI 默认启用设备配对鉴权，管理 API 与 `/ws` 都拒绝未配对客户端；静态配对页、公开健康检查和配对提交接口除外。唯一的 WebSocket 例外是本机 `picobot run`：请求必须同时来自真实回环对端并持有权限为 `0600` 的 `~/.picobot/web_admin_token`，该管理令牌不能绕过任何管理 API 的设备鉴权。配对令牌只以 SHA-256 哈希写入 `~/.picobot/web_auth.json`。鉴权不提供传输加密；如果通过 `--host 0.0.0.0`、反向代理或端口转发暴露 Gateway，仍必须使用 TLS。可通过 `gateway.require_pairing=false` 显式关闭配对，但不建议在非隔离环境使用。

#### WebUI 开发

WebUI 源码位于 `webui/`，使用 Svelte 5、Vite 和无样式的 Bits UI 可访问组件原语。Fluent 2 外观由 `webui/src/styles.css` 中的本地语义令牌与 Svelte 组件实现，不加载 CDN 或外部 UI 运行库；明暗模式和品牌色分别保存在浏览器 `picobot-theme`、`picobot-accent` 项中，并由 `theme-init.js` 在应用挂载前恢复。运行发布版 PicoBot 不需要 Node.js；从源码编译或修改前端时需要 Node.js 20+：

```bash
cd webui
npm ci
npm run check
npm run build
```

直接运行 `npm run build` 会在被忽略的 `webui/dist/` 生成独立检查产物。正常执行 `cargo build` 时，`build.rs` 会监听前端源码和构建配置，只有它们发生变化时才调用 Vite，将生产资源生成到 Cargo `OUT_DIR` 并嵌入二进制；`node_modules` 缺失或 `package-lock.json` 变化时会先自动运行 `npm ci`。前端产物不提交到仓库。

### 6. 作为 systemd 用户服务运行（Linux）

安装会把当前 PicoBot 可执行文件注册为 `picobot.service` 并设置为登录后自动启动；安装本身不会立即启动 Gateway：

```bash
picobot service install
picobot service start
```

服务管理命令：

```bash
picobot service status
picobot service restart
picobot service stop
picobot service uninstall
```

修改配置后无需重启 systemd service：

```bash
picobot reload
```

该命令连接正在运行的 Gateway，先解析并校验新配置，再停止接收新工作，等待当前交互 Turn、Scheduler job 和后台子 Agent 到达安全边界后切换运行代。也可在聊天中发送 `/reload`，或让根交互 Agent 在用户明确要求时调用 `reload_config` 工具；子 Agent 与定时任务不能触发重载。监听地址、workspace 和数据库路径涉及进程级资源，不能热重载；修改这些字段时命令会保留旧配置并提示使用 `picobot service restart`。重载会主动断开 WebSocket，TUI/WebUI 随后可重新连接并从持久化历史恢复。受认证客户端可通过 `GET /api/config/reload/status` 查询 generation、切换阶段和最近错误。

unit 位于 `~/.config/systemd/user/picobot.service`，以执行 `service install` 时的当前目录作为初始工作目录。服务异常退出时由 systemd 自动重启；`stop` 和 `restart` 会通过 SIGTERM 触发 Gateway 的有界优雅关停。

TUI 会把一个随机客户端标识保存到 `~/.picobot/tui_client_id`，因此关闭并重新打开客户端后会恢复同一组 dialog 和最近使用的会话。界面支持流式正文、独立思考与工具状态、历史回放、会话列表与归档筛选、命令补全、Unicode/中文编辑、括号粘贴、多行输入和文件传输。

常用快捷键：

| 快捷键 | 操作 |
|--------|------|
| `F1` / `Ctrl+H` | 打开帮助 |
| `Tab` / `Ctrl+S` | 切换焦点 / 聚焦会话列表 |
| `Ctrl+N` | 新建会话 |
| `Ctrl+R` / `Ctrl+A` / `Ctrl+D` | 重命名 / 归档 / 删除所选会话 |
| `Ctrl+L` / `Ctrl+O` | 清空历史 / 显示归档会话 |
| `Enter` / `Shift+Enter` | 发送 / 换行 |
| `Ctrl+F` / `F2` | 添加附件 / 下载历史中最近的附件 |

WebUI/TUI 上传文件默认保存到 `~/.picobot/media/cli_chat`，单文件上限 25 MiB；可通过 `gateway.file_transfer` 调整目录、上限和待发送上传的有效期。消息只保存文件路径，不保证文件被移动或删除后仍能下载。
| `PageUp` / `PageDown` | 滚动对话历史 |
| 连按两次 `Ctrl+C` | 退出客户端 |

## 运行时数据流

用户消息进入 PicoBot 后，会被转换为统一的 inbound message，经由 MessageBus 交给 SessionManager。SessionManager 选择当前 dialog、组装上下文并创建活动 Turn；AgentLoop 消费 Provider 原生流、执行工具并发出结构化事件，TurnController 将它们归约成可丢中间帧的完整快照。DeliveryCoordinator 把快照投影给 TUI、WebUI 或 Channel，最终消息在 SQLite 原子提交成功后才进入 `Completed`。

详细时序和失败语义见 [架构文档：消息与控制数据流](docs/ARCHITECTURE.md#4-消息与控制数据流)。

同一 session 始终只运行一个 Turn，不同 session 可以并发。Turn 执行期间新发的普通消息默认 steering 当前工作：系统在完整工具批次后或最终回复边界把它作为真实用户消息加入下一次模型调用；使用 `/queue <message>` 可明确等当前 Turn 完成后再处理，使用 `/stop` 可中断当前 Turn 并清空等待输入。Steering mailbox 和 session 队列都有界且带可靠回退。活动 Turn 使用 latest-wins 快照，慢展示端只跳过中间状态，不反压模型。普通出站消息按 `(channel, chat_id)` 分 lane 保序，两条投递路径共享目标写锁。

核心边界：

| 模块 | 职责 |
|------|------|
| `channels` | 接入外部渠道，只做收发，不直接处理会话或 LLM |
| `bus` | 异步消息队列，承载 inbound、outbound、control 三类消息 |
| `session` | 管理会话生命周期、dialog 操作、上下文、记忆召回、压缩和持久化 |
| `agent` | 执行无状态 LLM/tool 循环，处理模型响应和工具调用 |
| `providers` | OpenAI 兼容接口和 Anthropic Messages API 的原生流解析与回放 |
| `delivery` | 活动 Turn 的展示过滤、latest-wins 节流、终态投递与 TurnSink 生命周期 |
| `tools` | Agent 可调用工具集合 |
| `storage` | SQLite schema、CRUD、消息和任务持久化 |
| `scheduler` | 原子领取 occurrence，运行隔离的 Scheduled Agent，并通过持久化 outbox 按策略投递结构化结果 |
| `work` | 管理 session 级单 active plan、并行子项状态和 WebSocket 变更事件 |
| `skills` | 加载 Skill，并把 Skill 指南注入系统提示 |
| `mcp` | 连接 MCP Server，将远端工具包装成普通 Tool |
| `task_supervisor` | 统一管理 Gateway 后台任务的取消和有界关停 |

## 核心能力

### 渠道

| 渠道 | 说明 |
|------|------|
| `cli_chat` | Ratatui 终端客户端，通过 WebSocket 连接 Gateway |
| `feishu` | 飞书/Lark 消息、反应、文件上传下载和媒体引用 |

飞书默认只接受 `allow_from` 中的用户，且群聊消息必须明确 @ 机器人（可通过 `channels.feishu.require_mention=false` 关闭）。回复会使用飞书原生引用/话题语义保持在原消息位置。默认只发送终态结果；设置 `channels.feishu.live_updates=true` 后会创建一张卡片并持续编辑，`live_update_interval_ms` 默认 500ms，运行时限制在 250–5000ms。外部渠道始终不会收到模型 reasoning。

### 会话

Session ID 使用三段式：

```text
<channel>:<chat_id>:<dialog_id>
```

同一个 `channel:chat_id` 下可以有多个 dialog。当前支持的 dialog 操作包括创建、列表、切换、重命名、归档、删除、清空历史、压缩、导出、查看信息和停止任务。

常用 slash commands：

| 命令 | 说明 |
|------|------|
| `/new` | 创建新 dialog |
| `/sessions` | 列出最近 dialog |
| `/switch <dialog_id>` | 切换 dialog |
| `/rename <title>` | 重命名当前 dialog |
| `/delete` | 删除当前 dialog 并创建新 dialog |
| `/compact` | 强制把可压缩的旧完整 Turn 汇总为活动 checkpoint；不改写原始历史 |
| `/info [--json]` | 查看当前 dialog、累计 Token 与上下文窗口信息；可选 JSON 输出 |
| `/dump` | 导出当前 dialog 为 Markdown |
| `/mcp` | 查看 MCP 服务器和工具状态 |
| `/health` | 检查 PicoBot 运行依赖 |
| `/queue <message>` | 等当前 Turn 完成后再把消息作为下一 Turn 处理 |
| `/stop` | 停止当前任务并清空队列 |
| `/todo [done\|cancel]` | 查看、完成或取消当前 session 的任务计划 |
| `/reload` | 校验并重新加载 Gateway 配置 |
| `/?`, `/help` | 查看帮助 |

### 记忆

PicoBot 有两类记忆：

| 类型 | 用途 | 生命周期 |
|------|------|----------|
| Knowledge | 偏好、事实、项目规则、长期可复用信息 | 长期保留，手动删除 |
| Timeline | 长对话压缩后的历史摘要 | 默认保留 90 天 |

每轮处理用户消息时，MemoryManager 会按用户输入召回 Knowledge，并作为运行时上下文附加到本轮用户消息。当前召回上限固定为 5；`memory.recall_limit` 已支持解析但尚未接入 worker。长会话使用一个活动 checkpoint：累计摘要加 `first_retained_seq` 之后的原始消息尾部构成模型上下文，原始消息、工具调用结果、ID 和 seq 均不会被压缩改写。旧工具结果会保留在原始历史中，但 checkpoint 边界推进后不再永久占用 Provider 上下文。成功的语义摘要还会 best-effort 保存为 Timeline，供 `timeline_recall` 检索；Timeline 不参与会话恢复正确性。Scheduler 默认创建一个每日维护任务，按 `memory.timeline_retention_days` 清理过期 Timeline；结果通过 `complete_scheduled_run` 结构化提交，Knowledge 不会被自动删除。

模型的 `models.<name>.token_limit` 给出上下文窗口上限，未配置时默认为 128,000；Agent 的 `agents.<name>.token_limit` 是可选的收紧上限，两者都有配置时有效窗口取二者最小值，因此 Agent 不能扩大模型窗口。自动压缩使用保留量阈值 `context_tokens > context_window - effective_reserve`，默认 reserve 为 16,384 tokens，并尽量原样保留最近 20,000 tokens。小窗口会自动把 reserve 限制为窗口的一半、把近期保留量限制为有效阈值的一半。摘要请求不使用固定 32K 输入上限，而是按有效窗口扣除摘要输出、提示词和安全余量；超大历史只在摘要请求副本中按“已有 checkpoint + 最新消息优先”生成有界 head/tail 转录，SQLite 原文不变。手动 `/compact` 跳过自动阈值；换成小模型后若发送前预检已发现硬超限，或首次请求返回真实 context overflow，语义摘要不可用时才使用明确标记的确定性降级裁剪，正式请求最多重试一次。若 overflow 发生在工具已经执行之后，AgentLoop 只在当前内存转录上裁掉旧完整 Turn 并重试当前模型步骤一次，不会从数据库历史重跑工具。

### 工具

基础工具集：

| 工具 | 说明 |
|------|------|
| `calculator` | 数学表达式和统计计算 |
| `file_read` / `file_write` / `file_edit` | 文件读写和编辑；`file_read` 读取受支持图片时可将图片直接提供给多模态模型 |
| `file_search` / `content_search` | 文件名和内容搜索 |
| `bash` | 在 workspace 中执行 Shell 命令 |
| `http_request` / `web_fetch` | HTTP 请求和网页文本抽取 |
| `get_skill` | 列出或读取本地 Skill |
| `memory_store` / `memory_recall` / `timeline_recall` / `memory_forget` | 长期记忆操作 |
| `reload_config` | 在用户明确要求时校验并重新加载 Gateway 配置 |
| `delegate` | 向具名 Agent 委托单个或批量任务；`foreground` 等待结果，`background` 异步执行。批量 foreground 会并发运行并按请求顺序聚合 |
| `agent_task` | 查询/列出/读取结果/取消已持久化的具名 Agent run（仅编排启用时注册） |
| `emit_signal` | 后台 run 向主 Agent 发送结构化内部信号（queue/steer 投递；仅带 signal 契约的 run 注册） |
| `todo` | 为复杂、多轮任务创建并更新当前 session 的持久化计划 |
| `send_message` | 向指定渠道或当前会话发送消息，可附带文件/截图；WebUI/TUI 当前 Turn 的附件并入最终回复 |
| `chat_manager` | 查看渠道、会话和历史消息 |
| `cron_add/list/remove/enable/disable/update` | 管理定时任务；`agent_id` 选择 Root/命名 Agent，`delivery_policy` 支持 `always/on_alert/never` |
| `cron_runs` | 查询定时任务的结构化执行结果、诊断和投递状态，包括静默任务 |
| `routine_maintenance` | 安全清理超过保留期的 Timeline，不删除 Knowledge |
| `health` | 检查核心、配置相关和可选运行依赖 |
| `browser` | 可选 agent-browser 浏览器自动化；默认按 dialog 临时使用，长期任务可用 `persistent_id` 复用个人 Profile |
| `browser_profiles` | 创建、设置语义标签、列出或删除浏览器持久 ID 及其 Profile 目录 |
| MCP tools | 从配置的 MCP Server 动态发现并注册 |

### Skills

Skill 是包含 `SKILL.md` 的目录。加载优先级从高到低：

1. `{workspace}/skills`
2. `~/.picobot/skills`
3. `~/.agents/skills`

同名 Skill 会按高优先级覆盖低优先级。内置 Skill 位于 [resources/skills](resources/skills)，首次运行时会安装到 `~/.picobot/skills`。

## 配置速查

顶层配置字段：

| 字段 | 说明 |
|------|------|
| `providers` | LLM Provider 配置 |
| `models` | 模型参数与输入能力 |
| `agents` | Agent 使用哪个 provider/model |
| `context_compaction` | 上下文自动压缩开关、预留 token 与近期原样保留量 |
| `agent_orchestration` | 具名子 Agent 定义目录与编排上限 |
| `gateway` | HTTP/WebSocket、数据库、调度器、后台任务限制 |
| `client` | CLI 客户端默认 Gateway URL |
| `channels` | 渠道配置，目前主要是飞书/Lark |
| `memory` | 记忆召回、归并和 Timeline 保留策略 |
| `mcp` | MCP Server 配置 |
| `browser` | 可选浏览器自动化配置 |
| `workspace_dir` | 文件工具、Shell、数据库和 workspace skills 的工作目录 |

重要默认值：

| 配置 | 默认值 |
|------|--------|
| `gateway.host` | `127.0.0.1` |
| `gateway.port` | `19876` |
| `gateway.require_pairing` | `true` |
| `gateway.max_concurrent_background_tasks` | `10` |
| `gateway.scheduler.enabled` | `true` |
| `client.gateway_url` | `ws://127.0.0.1:19876/ws` |
| `context_compaction.enabled` | `true` |
| `context_compaction.reserve_tokens` | `16384` |
| `context_compaction.keep_recent_tokens` | `20000` |
| `memory.recall_limit` | `5`（当前运行时固定为 5） |
| `memory.timeline_retention_days` | `90` |
| `mcp.tool_timeout_secs` | `180` |
| `mcp.servers[].tool_settings` | `{}`；可按工具名声明 `read_only` / `exclusive`，并发状态自动推导 |
| `browser.enabled` | `true` |
| `channels.feishu.live_updates` | `false` |
| `channels.feishu.live_update_interval_ms` | `500` |
| `channels.feishu.require_mention` | `true` |
| `channels.feishu.max_image_bytes` | `10485760` |
| `channels.feishu.max_file_bytes` | `26214400` |
| `channels.feishu.media_dir_max_bytes` | `536870912` |
| `channels.feishu.request_timeout_secs` | `30` |

### 具名子 Agent（Phase 1）

PicoBot 在 Gateway 候选运行代构造时从配置目录下的 `definitions_dir` 加载 `*.md`（子 Agent 编排是内在机制，始终启用）。相对路径按 `config.json` 所在目录解析，且 canonical path 不得逃逸该目录；角色文件、Provider profile、工具、Skill 和委托边任一无效都会拒绝启动或热重载。支持具名 `foreground`（单/批量）与 Root 发起的具名 `background`（单任务或 `tasks[]` 批量）：每个 run 独立落库、预留 completion 槽、完成后由主 Agent 的 continuation Turn 单独汇总（空闲时完成即返回），可配合 `emit_signal`（queue/steer）推送内部信号。子 Agent 发起的 background 尚未开放（旧匿名 general 已移除）。

```md
---
id: researcher
description: 搜索、阅读并整理技术资料
llm_profile: research
tools:
  - file_read
  - file_search
  - content_search
delegates:
  - reviewer
limits:
  timeout_secs: 900
  max_iterations: 24
---
# Role

你是一名严谨的研究 Agent，只返回与任务有关的结论和证据。
```

每个具名 Agent 的工具集完全由其 Markdown `tools` 列表决定（管理员显式授权），不再有工具侧的可派发门槛；也可内联 `provider`/`model` 直接指定模型（或沿用 `llm_profile` 引用顶层 `agents` key）。`delegate`/`emit_signal`/`get_skill`/`agent_task` 为运行时注入工具，不能写进 `tools`（分别由 `delegates`/`signal`/`skills` 字段派生），`get_skill` 例外作为启用 scoped skill 的开关。每个定义可用 `enabled: false` 单独禁用（保留在磁盘但不加载）。主 Agent 可委托给任意具名子 Agent；子 Agent 能否继续委托由 `delegates` 决定——不写该字段时默认仅可委托内置 `general-purpose`，写 `[]` 表示不可继续委托，写 `["*"]` 表示可委托任意子代理，写列表则按列表指定（self 与祖先在运行时始终被拒绝）。WebUI「子 Agent」页可直接增删改定义、启停并选择工具/Skill/Provider/Model 与委托范围。

更完整的配置字段说明见 [resources/skills/about-picobot/references/config.md](resources/skills/about-picobot/references/config.md)。

## agent-browser 安装与使用

PicoBot 不再使用 Fantoccini、ChromeDriver 或 WebDriver。上层仍暴露一个稳定的 `browser` 工具，底层通过 agent-browser `0.33.0` 的 JSON CLI 驱动原生 Rust daemon 和 Chrome CDP。先安装 CLI 与浏览器：

```bash
# 推荐；npm 只负责安装预编译 CLI
npm install -g agent-browser@0.33.0
agent-browser install

# Linux 需要同时补齐系统库时
agent-browser install --with-deps

# 或直接通过 Rust 工具链安装
cargo install agent-browser --version 0.33.0 --locked
agent-browser install

# macOS 也可使用 Homebrew
brew install agent-browser
agent-browser install
```

已有 Chrome/Chromium 时可在配置中设置 `browser_executable_path`，或通过 `AGENT_BROWSER_EXECUTABLE_PATH` 指定。Docker 镜像已固定安装 agent-browser `0.33.0` 与 Debian Chromium，不包含 ChromeDriver。启用示例：

```json
{
  "browser": {
    "enabled": true,
    "command": "agent-browser",
    "headless": true,
    "browser_executable_path": null,
    "max_sessions": 4,
    "idle_timeout_secs": 3600,
    "command_timeout_secs": 120,
    "max_output_chars": 50000,
    "content_boundaries": true,
    "allowed_domains": [],
    "allow_private_hosts": false,
    "artifact_dir": "~/.picobot/media/browser",
    "persistence": {
      "profile_dir": "~/.picobot/browser/profiles"
    }
  }
}
```

浏览器没有全局“持久模式”开关，而是按每次调用分流：不传 `persistent_id` 时使用当前 dialog 的普通临时浏览器，连续一小时没有操作后默认自动关闭；涉及长期工作、需要保持浏览器进程或保留登录和站点状态时，Agent 可以自主调用 `browser_profiles(create,label=...)` 生成 `picobot-profile-<uuid>`，并在该工作的后续每个 `browser` action 中持续传入同一个 ID。持久浏览器禁用 daemon 空闲自动关闭，只会在显式 `browser(close)` 或 Profile 删除时关闭。也可以用 `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)。

## WebSocket API

Gateway 暴露：

| Method | Path | 说明 |
|--------|------|------|
| `GET` | `/health` | 健康检查和版本信息 |
| `GET` | `/api/auth/status` | 当前设备配对状态 |
| `POST` | `/api/auth/pair` | 用一次性代码配对设备 |
| `POST` | `/api/auth/code` | 本机 CLI 签发配对码；要求回环来源和管理密钥 |
| `GET` | `/ws` | WebSocket 聊天协议；要求配对 Cookie 或 Bearer token |

Inbound 消息类型：

| Type | 主要字段 |
|------|----------|
| `user_input` | `content`，可选 `channel`、`chat_id`、`sender_id` |
| `clear_history` | 可选 `chat_id`、`session_id` |
| `create_session` | 可选 `title` |
| `list_sessions` | `include_archived` |
| `load_session` | `session_id` |
| `get_session_history` | `session_id`，可选 `limit`（服务端限制为 1–2000） |
| `rename_session` | 可选 `session_id`，`title` |
| `archive_session` | 可选 `session_id` |
| `delete_session` | 可选 `session_id` |
| `get_slash_commands` | 无 |
| `ping` | 无 |

Outbound 消息类型包括活动 Turn 使用的 `turn_updated`，以及 `assistant_response`、`error`、`session_established`、`session_created`、`session_list`、`session_loaded`、`session_history`、`session_renamed`、`session_archived`、`session_deleted`、`history_cleared`、`slash_commands_list`、`pong`、`command_executed` 和 `system_notification`。`turn_updated` 每次携带完整快照和单调 revision，客户端只替换当前 session 的活动 Turn；`assistant_response` 保留给独立完整消息。历史消息可包含 reasoning、turn/iteration、completion status 和结构化工具元数据，但不会暴露 Provider 私有回放状态。

## 测试

```bash
# 单元测试
cargo test --lib

# 离线集成/协议测试
cargo test --test test_scheduler
cargo test --test test_request_format

# 模型 API 集成测试需要 tests/test.env 中有真实 API key
cp tests/test.env.example tests/test.env
cargo test --test test_integration -- --ignored
cargo test --test test_tool_calling -- --ignored
```

会真实调用模型 API 的测试标记为 `#[ignore]`；离线集成测试默认执行。

## 项目结构

```text
src/
  agent/          LLM loop、上下文压缩、系统提示、媒体处理、子 Agent
  bus/            inbound、outbound、control 消息队列
  channels/       CLI chat 和飞书/Lark 集成
  client/         Ratatui 终端 UI
  config/         配置加载、环境变量替换、路径展开
  delivery/       活动 Turn 快照投影、节流与 TurnSink 生命周期
  gateway/        Axum HTTP/WebSocket server 和 GatewayState 装配
  mcp/            MCP 客户端连接和工具包装
  memory/         记忆管理和记忆类型
  observability/  Agent/tool telemetry observer
  providers/      OpenAI 兼容和 Anthropic provider
  scheduler/      定时任务运行时
  work/           Session 级任务计划与并行子项状态机
  session/        会话生命周期、dialog 命令、持久化集成
  skills/         Skill 加载和内置 Skill 安装
  storage/        SQLite schema 和 CRUD
  tools/          Agent 工具实现
  task_supervisor.rs  Gateway 后台任务的生命周期管理
resources/
  skills/         构建时嵌入的内置 Skills
  templates/      首次运行释放的配置和用户模板
tests/            单元测试和 ignored 集成测试
docs/             面向维护者和 Agent 的架构与开发文档
```

## 关键依赖

| Crate | 用途 |
|-------|------|
| `axum`, `tokio`, `tokio-tungstenite` | Gateway 和 WebSocket runtime |
| `sqlx` | SQLite 持久化 |
| `reqwest` | LLM 和 HTTP 客户端 |
| `ratatui`, `crossterm`, `termimad` | 终端 UI |
| `rmcp` | MCP 客户端 |
| `cron`, `chrono-tz` | 定时任务 |
| `jieba-rs` | 中文记忆检索分词 |
| `zstd`, `tar` | 内置 Skill 打包和释放 |

## 进一步阅读

- [维护者架构文档](docs/ARCHITECTURE.md)
- [配置热重载设计与实现](docs/CONFIG_HOT_RELOAD_DESIGN.md)
- [WebUI 与 TUI 文件收发设计](docs/FILE_TRANSFER_DESIGN.md)
- [内置 Skill：架构机制](resources/skills/about-picobot/references/architecture.md)
- [配置说明](resources/skills/about-picobot/references/config.md)
- [命令说明](resources/skills/about-picobot/references/commands.md)
- [工具说明](resources/skills/about-picobot/references/tools.md)
- [数据库结构](resources/skills/about-picobot/references/db-schema.md)
