From 901b622b409d218eeab83522b41327f59cecf5e9 Mon Sep 17 00:00:00 2001 From: xiaoxixi Date: Tue, 14 Jul 2026 13:02:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 66 +++- README.md | 27 +- docs/ARCHITECTURE.md | 281 ++++++++++++++++++ resources/skills/about-picobot/SKILL.md | 5 +- .../about-picobot/references/architecture.md | 66 ++-- .../about-picobot/references/commands.md | 12 +- .../skills/about-picobot/references/config.md | 23 +- .../about-picobot/references/db-schema.md | 28 +- .../skills/about-picobot/references/faq.md | 14 +- .../skills/about-picobot/references/tools.md | 55 ++-- 10 files changed, 490 insertions(+), 87 deletions(-) create mode 100644 docs/ARCHITECTURE.md diff --git a/AGENTS.md b/AGENTS.md index 3edbf13..9b809bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,7 @@ # PicoBot +This file is the operational contract for coding agents working in this repository. Read [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) before making architectural or lifecycle changes. Code and tests are the final source of truth; update the document when an architectural invariant changes. + ## Build & Run - `cargo build` — build the binary @@ -8,16 +10,18 @@ ## Config -- Config load order: `~/.picobot/config.json` then fallback to `./config.json` (`src/config/mod.rs:237-267`) +- Config load order: `~/.picobot/config.json` then fallback to `./config.json` (`Config::load_default` in `src/config/mod.rs`) - `.env` (cwd) is loaded with a custom parser, not via dotenv crate; env var placeholders `` in config JSON are substituted - Config example: `resources/templates/config.example.json` (released to `~/.picobot/` on first run) ## Tests - `cargo test --lib` — run unit tests (runs all `#[test]` in `src/`) -- `cargo test --test test_integration -- --ignored` — run integration tests (also `test_tool_calling`, `test_request_format`) -- **All** integration tests require `tests/test.env` with real API keys; copy from `tests/test.env.example` and fill in keys -- Integration tests are `#[ignore]` by default; use `-- --ignored` to run them +- `cargo clippy --all-targets --all-features -- -D warnings` — required for Rust changes +- `cargo test --test test_scheduler` and `cargo test --test test_request_format` — offline integration/protocol tests +- `cargo test --test test_integration -- --ignored` and `cargo test --test test_tool_calling -- --ignored` — model API integration tests +- API-backed tests require `tests/test.env` with real API keys; copy from `tests/test.env.example` and fill in keys +- API-backed tests are `#[ignore]` by default; use `-- --ignored` to run them ## Reference @@ -33,9 +37,12 @@ ### Core Data Flow ``` -Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionManager → MessageBus → OutboundDispatcher → Channel - ↑ - ControlChannel ──→ SessionManager (dialog ops: create/switch/archive/delete) +Channel → MessageBus.inbound → Gateway processor → SessionManager → per-session worker → AgentLoop + ↑ │ + └── Channel ← OutboundDispatcher ← per-conversation lane ← MessageBus.outbound + +WebSocket/Channel → MessageBus.control → Gateway processor → SessionManager (dialog operations) +Scheduler → SessionManager.handle_cron_message → AgentLoop → send_message tool ``` ### Modules @@ -45,8 +52,8 @@ Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionM | `gateway` | Server lifecycle, HTTP/WS endpoints, owns `GatewayState` | `GatewayState`, `run()` | | `client` | TUI rendering, WebSocket client for CLI chat | `App`, `run()` | | `channels` | External integrations (Feishu, CLI chat) | `ChannelManager`, `Channel` trait | -| `bus` | Async message queue (inbound/outbound/control channels) | `MessageBus`, `InboundMessage`, `OutboundMessage`, `ControlMessage` | -| `session` | Conversation session lifecycle, dialog operations | `SessionManager`, `Session` | +| `bus` | Bounded async queues and ordered outbound delivery lanes | `MessageBus`, `OutboundDispatcher`, `InboundMessage`, `OutboundMessage`, `ControlMessage` | +| `session` | Conversation lifecycle, dialog operations, per-session serialization, persistence coordination | `SessionManager`, `Session` | | `agent` | LLM call loop, tool execution, context compression | `AgentLoop` | | `providers` | LLM API clients (OpenAI-compatible, Anthropic) | `LLMProvider` trait, factory `create_provider()` | | `tools` | Agent tools (bash, file ops, http, web, get_skill) | `ToolRegistry`, `Tool` trait | @@ -57,22 +64,51 @@ Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionM | `protocol` | WebSocket protocol message types | `WsInbound`, `WsOutbound`, `SessionSummary` | | `config` | Config loading, env substitution, path resolution | `Config`, `LLMProviderConfig` | | `logging` | Tracing initialization with file rotation | `init_logging()`, `init_logging_console_only()` | +| `task_supervisor` | Owns, cancels, and boundedly joins gateway background tasks | `TaskSupervisor` | ### Functional Boundaries - **Channels** only send/receive messages via `MessageBus`; they know nothing about sessions or LLM -- **MessageBus** is a pure async queue; it routes nothing, just passes messages -- **SessionManager** owns session state and dialog operations; it does NOT call LLM directly - - SessionManager is responsible for injecting skills prompt into conversation history -- **AgentLoop** receives dialog events from `SessionManager`, calls LLM via `providers`, executes tools, returns text responses - - AgentLoop is stateless; all state is managed by Session/SessionManager +- **MessageBus** owns bounded inbound/outbound/control queues; outbound routing and retries belong to `OutboundDispatcher`, not the queue +- **SessionManager** owns session state, dialog operations, context construction, per-session work queues, and persistence coordination +- **AgentLoop** is stateless across turns; it receives prepared history, calls LLM providers, executes tools, and returns one result - **Providers** are pure HTTP clients; no bus/session/channel awareness - **Tools** are executed by `AgentLoop`; they receive raw arguments and return string results +### Concurrency and Lifecycle Invariants + +- Messages in one session are processed serially through a bounded queue; different sessions may run concurrently +- Outbound messages are ordered per `(channel, chat_id)`; a slow destination must not block unrelated destinations +- Never hold a Session mutex across model, network, or database I/O unless a documented invariant requires it +- Slow work derived from session state must validate `worker_generation`/`state_version` before committing results +- Related durable mutations use Storage transaction APIs; persistence failure must not leave silent memory/database divergence +- Long-lived gateway tasks must be owned by `TaskSupervisor`; connection-local tasks must be explicitly joined or aborted by their owner +- Connection, retry sleep, queue wait, and shutdown join paths must observe cancellation and have hard time bounds +- Retry only errors explicitly classified as transient; permanent failures must surface immediately +- Never log secrets, authorization headers, or full connection URLs containing temporary credentials +- The workspace cwd is not a filesystem sandbox: default file tools accept absolute paths and Bash inherits process permissions; add explicit canonical-path/process isolation when a hard boundary is required + ### Key Constraints -- Gateway **changes working directory** to workspace on startup (`src/gateway/mod.rs:31`) +- Gateway **changes working directory** to workspace in `GatewayState::new` (`src/gateway/mod.rs`) - Session/message persistence uses SQLite via `sqlx`; DB stored in workspace as `picobot.db` by default - `ChannelManager` owns the `MessageBus` and all channel instances - `OutboundDispatcher` routes outbound messages to the correct channel via `ChannelManager` - Config `.env` loading uses `unsafe { env::set_var(...) }` — don't refactor to safer patterns without understanding side effects + +## Change Workflow + +1. Inspect the relevant implementation, tests, and `docs/ARCHITECTURE.md`; do not rely on names or old reports alone. +2. Search `reference/` only for comparison. Never edit it or copy behavior without checking PicoBot's boundaries. +3. Preserve unrelated user changes in a dirty worktree. Use `rg` for search and `apply_patch` for edits. +4. Add regression tests for bugs, especially cancellation, timeout, queue saturation, stale state, persistence failure, and retry classification. +5. For Rust changes run targeted tests, `cargo test --lib`, Clippy with warnings denied, and `cargo build`. Integration tests require real credentials. +6. For documentation-only changes verify links, commands, paths, and `git diff --check`. +7. Update README, this file, and the architecture document together when public behavior or an architectural invariant changes. + +## Documentation Roles + +- `README.md` — user-facing overview, setup, capabilities, and navigation +- `docs/ARCHITECTURE.md` — maintainer-facing runtime design, invariants, lifecycle, and extension guidance +- `AGENTS.md` — concise operational rules for repository agents +- `resources/skills/about-picobot/references/` — runtime knowledge shipped to PicoBot; update it only when the assistant's built-in product knowledge must change diff --git a/README.md b/README.md index d76c4f0..6d39a92 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ PicoBot 是一个用 Rust 编写的个人 AI 助手运行时。它在本地启 它更像一个可扩展的“个人助手操作系统”:渠道负责收发消息,SessionManager 负责会话和上下文,AgentLoop 负责模型与工具循环,Storage 负责可靠落盘。 -![PicoBot runtime architecture](docs/assets/runtime-architecture.svg) +完整的组件边界、并发不变量、启动/关停顺序和扩展指南见 [架构文档](docs/ARCHITECTURE.md)。 ## 适合做什么 @@ -95,7 +95,9 @@ CLI 默认连接 `ws://127.0.0.1:19876/ws`。如需指定地址,可使用 `--g 用户消息进入 PicoBot 后,会被转换为统一的 inbound message,经由 MessageBus 交给 SessionManager。SessionManager 选择当前 dialog、组装上下文、调用 AgentLoop;AgentLoop 调用模型和工具,最终响应通过 outbound bus 回到原渠道。 -![Message flow](docs/assets/message-flow.svg) +详细时序和失败语义见 [架构文档:消息与控制数据流](docs/ARCHITECTURE.md#4-消息与控制数据流)。 + +同一 session 的普通消息由专属有界队列串行处理,不同 session 可以并发;出站消息按 `(channel, chat_id)` 分 lane 保序,慢渠道不会阻塞其他目标。Gateway 的长生命周期任务统一由 `TaskSupervisor` 取消和限时回收。 核心边界: @@ -111,6 +113,7 @@ CLI 默认连接 `ws://127.0.0.1:19876/ws`。如需指定地址,可使用 `--g | `scheduler` | 轮询 Cron 任务并把任务 prompt 送入目标会话 | | `skills` | 加载 Skill,并把 Skill 指南注入系统提示 | | `mcp` | 连接 MCP Server,将远端工具包装成普通 Tool | +| `task_supervisor` | 统一管理 Gateway 后台任务的取消和有界关停 | ## 核心能力 @@ -156,7 +159,7 @@ PicoBot 有两类记忆: | Knowledge | 偏好、事实、项目规则、长期可复用信息 | 长期保留,手动删除 | | Timeline | 长对话压缩后的历史摘要 | 默认保留 90 天 | -每轮处理用户消息时,MemoryManager 会按用户输入召回最多 `memory.recall_limit` 条 Knowledge,并注入系统提示。上下文压缩产生的摘要会保存为 Timeline,后续可通过 `timeline_recall` 工具检索。 +每轮处理用户消息时,MemoryManager 会按用户输入召回 Knowledge,并作为运行时上下文附加到本轮用户消息。当前召回上限固定为 5;`memory.recall_limit` 已支持解析但尚未接入 worker。上下文压缩产生的摘要会保存为 Timeline,后续可通过 `timeline_recall` 工具检索。 ### 工具 @@ -214,7 +217,7 @@ Skill 是包含 `SKILL.md` 的目录。加载优先级从高到低: | `gateway.max_concurrent_background_tasks` | `10` | | `gateway.scheduler.enabled` | `true` | | `client.gateway_url` | `ws://127.0.0.1:19876/ws` | -| `memory.recall_limit` | `5` | +| `memory.recall_limit` | `5`(当前运行时固定为 5) | | `memory.timeline_retention_days` | `90` | | `mcp.tool_timeout_secs` | `180` | | `browser.enabled` | `false` | @@ -253,14 +256,17 @@ Outbound 消息类型包括 `assistant_response`、`error`、`session_establishe # 单元测试 cargo test --lib -# 集成测试需要 tests/test.env 中有真实 API key +# 离线集成/协议测试 +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 -cargo test --test test_request_format -- --ignored ``` -集成测试默认 `#[ignore]`,因为它们会真实调用模型 API。 +会真实调用模型 API 的测试标记为 `#[ignore]`;离线集成测试默认执行。 ## 项目结构 @@ -281,11 +287,12 @@ src/ skills/ Skill 加载和内置 Skill 安装 storage/ SQLite schema 和 CRUD tools/ Agent 工具实现 + task_supervisor.rs Gateway 后台任务的生命周期管理 resources/ skills/ 构建时嵌入的内置 Skills templates/ 首次运行释放的配置和用户模板 tests/ 单元测试和 ignored 集成测试 -docs/ 分析报告、文档插图和补充资料 +docs/ 面向维护者和 Agent 的架构与开发文档 ``` ## 关键依赖 @@ -304,9 +311,9 @@ docs/ 分析报告、文档插图和补充资料 ## 进一步阅读 -- [架构机制](resources/skills/about-picobot/references/architecture.md) +- [维护者架构文档](docs/ARCHITECTURE.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) -- [代码质量分析](docs/CODE_QUALITY_ANALYSIS.md) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..1076650 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,281 @@ +# PicoBot 架构 + +本文档描述 PicoBot 当前实现的运行时边界、数据流、并发模型和演进约束。它面向维护者和后续参与改进的 Agent,是代码架构的主入口;行为细节仍以代码和测试为最终依据。 + +## 1. 设计目标 + +PicoBot 是一个单进程、异步、可扩展的个人 AI 助手运行时。核心目标是: + +- 用统一消息模型接入不同聊天渠道。 +- 隔离渠道、会话、模型、工具和持久化职责。 +- 同一会话内保持消息顺序,不同会话之间允许并发。 +- 让外部 I/O、后台任务和程序关停都有明确边界。 +- 通过 SQLite 保存会话、消息、记忆、定时任务及后台任务状态。 + +当前不是分布式系统。除调度任务使用数据库租约防止重复领取外,运行时会话状态由单个 Gateway 进程持有。 + +## 2. 运行模式与进程边界 + +PicoBot 只有一个二进制,提供两种模式: + +| 模式 | 入口 | 职责 | +|------|------|------| +| Gateway | `cargo run -- gateway` | 组装服务、监听 HTTP/WebSocket、运行渠道、会话、调度器和后台任务 | +| CLI client | `cargo run -- chat` | 运行 Ratatui UI,通过 WebSocket 使用 Gateway,不持有业务状态 | + +Gateway 启动时会切换进程工作目录到 `workspace_dir`。因此所有相对路径都应按 workspace 解释,不能假设仍位于源码仓库。 + +## 3. 组件关系 + +```mermaid +flowchart LR + External[CLI / Feishu] --> Channels[channels] + Channels -->|InboundMessage| Bus[MessageBus] + Bus --> Processor[Gateway message processor] + Processor --> Sessions[SessionManager] + Sessions --> Agent[AgentLoop] + Agent --> Providers[LLM providers] + Agent --> Tools[ToolRegistry / MCP] + Sessions <--> Storage[(SQLite)] + Sessions -->|OutboundMessage| Bus + Scheduler[Scheduler] --> Sessions + Bus --> Dispatcher[OutboundDispatcher] + Dispatcher --> Channels + Supervisor[TaskSupervisor] -. lifecycle .-> Processor + Supervisor -. lifecycle .-> Dispatcher + Supervisor -. lifecycle .-> Scheduler + Supervisor -. lifecycle .-> Sessions +``` + +### 模块职责 + +| 模块 | 拥有的职责 | 不应承担的职责 | +|------|------------|----------------| +| `gateway` | 依赖装配、HTTP/WS 入口、启动和关停顺序 | 业务规则、渠道协议细节 | +| `channels` | 外部协议适配、权限检查、媒体收发 | 会话选择、LLM 调用 | +| `bus` | 三条有界异步队列与出站投递协调 | 会话路由、业务状态 | +| `session` | dialog 路由、会话状态、串行工作队列、上下文和持久化协调 | 外部渠道协议 | +| `agent` | 单次无状态模型/工具循环、上下文压缩、子 Agent | 持有 dialog 生命周期 | +| `providers` | 把统一请求映射到模型 API | Session、Bus 或 Channel 感知 | +| `tools` / `mcp` | 工具定义、注册和执行适配 | 隐式修改会话路由 | +| `storage` | SQLite schema、迁移、原子 CRUD | 运行时调度策略 | +| `memory` | Knowledge/Timeline 的存取与召回 | 直接驱动消息发送 | +| `scheduler` | 领取到期任务、限并发执行、原子记录结果 | 复用聊天会话历史 | +| `task_supervisor` | 后台任务注册、取消、限时回收 | 业务级重试和结果语义 | + +## 4. 消息与控制数据流 + +`MessageBus` 包含三条容量相同的 Tokio MPSC 队列: + +- `inbound`:Channel → Gateway message processor。 +- `outbound`:Session/Tool → `OutboundDispatcher`。 +- `control`:WebSocket/Channel → Gateway message processor,用于 dialog 操作。 + +### 普通消息 + +```mermaid +sequenceDiagram + participant C as Channel + participant B as MessageBus + participant G as Message processor + participant S as SessionManager + participant W as Per-session worker + participant A as AgentLoop + participant D as OutboundDispatcher + + C->>B: publish InboundMessage + B->>G: consume inbound + G->>S: handle_message + S->>W: try_send AgentTask + S-->>G: AgentProcessing + W->>A: process(history) + A-->>W: final response + W->>B: publish OutboundMessage + B->>D: consume outbound + D->>C: Channel::send +``` + +关键语义: + +- Gateway 的主消息处理循环不等待模型完成;普通消息进入对应 session worker 后立即返回 `AgentProcessing`。 +- 每个 session 有一条容量为 32 的队列,同一 session 串行处理,不同 session 的 worker 可并发执行。 +- 队列满时明确拒绝新消息,不允许无界积压。 +- Slash command 不进入 Agent 队列,由 `SessionManager` 直接执行,因此 `/stop` 等控制操作不会排在长模型调用之后。 + +### 出站投递 + +`OutboundDispatcher` 按 `(channel, chat_id)` 建立独立 lane: + +- 同一目标的消息保持顺序。 +- 慢目标不会阻塞其他目标。 +- 每条 lane 容量为 64,空闲 300 秒后退出。 +- 单次发送超时 30 秒;最多尝试 3 次,前两次失败后分别等待 1/2 秒。只有 `ConnectionError` 和 `SendError` 会重试。 +- `deliver_outbound` 可等待渠道真实投递结果,等待上限 120 秒;普通 `publish_outbound` 只保证成功入队。 + +不要把“已进入 Bus”误认为“外部渠道已收到”。需要确认语义时必须使用 `deliver_outbound`。 + +### Control 消息 + +WebSocket dialog 操作通过 `ControlMessage` 携带一次性回复通道。Gateway 在统一 message processor 中调用 `SessionManager`,再将 `SessionEvent` 回传给发起者。Bus 只承载消息,不解释操作。 + +## 5. 会话模型与并发不变量 + +Session ID 格式为: + +```text +:: +``` + +`SessionManagerInner` 保存: + +- `sessions`:完整 Session ID 到内存 Session 的映射。 +- `current_sessions`:`channel:chat_id` 到当前 dialog 的映射。 + +必须维护以下不变量: + +1. 同一 Session 的 Agent 工作由一个 generation 对应的 worker 串行执行。 +2. `/stop` 或 worker 替换会递增 `worker_generation`;旧 worker 不得再提交结果。 +3. 慢操作(模型、记忆召回、压缩、SQLite I/O)不能长期持有 Session mutex。 +4. 慢操作开始前记录 `state_version`,提交前重新验证,防止旧快照覆盖 `/clear`、`/delete` 等并发修改。 +5. 持久化写入由 `persistence_lock` 串行化;多条相关记录应使用 Storage 的原子接口。 +6. 内存先变更但持久化失败时,必须回滚精确匹配的消息后缀,不能删除无关的新状态。 + +SessionManager 负责组装会话上下文:系统提示、Skills、召回的 Knowledge、压缩后的 Timeline 和当前消息历史。`AgentLoop` 接收完整输入执行一次模型/工具循环,本身不拥有会话状态。 + +## 6. 持久化 + +`Storage` 使用 SQLx + SQLite,默认数据库为 `{workspace_dir}/picobot.db`。连接启用: + +- WAL journal mode。 +- foreign keys。 +- 5 秒 busy timeout。 +- schema version 迁移。 + +持久化范围包括 sessions、messages、memories、scheduled jobs、job runs 和 background tasks。修改 schema 时应: + +1. 更新集中式 schema/迁移逻辑。 +2. 保留已有数据库的升级路径。 +3. 为新库初始化和旧库迁移分别增加测试。 +4. 对“状态更新 + 执行记录”等复合写入使用事务。 + +### 安全边界 + +- API Key 和渠道凭据只来自配置占位符、`.env` 或进程环境,不得写入仓库。 +- 日志不得输出 token、secret、Authorization header,或包含临时凭据的完整 URL;应记录脱敏后的 host/path 和必要诊断字段。 +- Gateway 把 cwd 切到 workspace,因此相对文件路径和 Shell 默认从 workspace 开始;这不是硬沙箱。当前内置文件工具接受绝对路径,Bash 也可访问进程权限允许的位置。若某场景需要硬边界,必须显式配置/实现 allowed directory 和进程隔离。 +- `http_request` 和 `web_fetch` 的私网/回环地址校验属于 SSRF 防线,重构网络层时不能绕过。 +- 外部内容、Tool 输出和 MCP 响应均是不可信输入;解析错误应返回结构化失败,不能 panic。 + +## 7. 后台任务与生命周期 + +`TaskSupervisor` 是 Gateway 内部后台任务的统一所有者。message processor、outbound dispatcher、scheduler、session workers、outbound lanes、通知消费者和子 Agent 后台任务都应通过它注册。 + +两种注册方式: + +- `spawn`:收到全局取消后直接丢弃任务 future,适合无需异步清理的任务。 +- `spawn_graceful`:任务自己观察 cancellation token 并清理,Supervisor 在总宽限期结束后再强制 abort。 + +新增长生命周期任务时必须满足: + +- 有明确 owner,禁止无法回收的裸 `tokio::spawn`。 +- 能响应取消;外部连接、重试 sleep 和阻塞式等待也要纳入取消分支。 +- 等待任务退出必须有硬超时,超时后 abort 并回收 JoinHandle。 +- 任务 panic、超时和永久错误要可观测,且不能阻止其他组件清理。 + +WebSocket 每个客户端的 writer task 是连接局部任务,由连接 handler 自己限时回收;它不跨越连接生命周期。 + +## 8. 启动与关停顺序 + +### 启动 + +1. 加载配置和 `.env`,解析 workspace。 +2. 创建并切换到 workspace。 +3. 初始化 SQLite、MemoryManager、MessageBus 和 SessionManager。 +4. 注册内置工具、渠道、MCP 工具和 Cron 工具。 +5. 启动所有 Channel。 +6. 通过 TaskSupervisor 启动 message processor、dispatcher 和 scheduler。 +7. 绑定 Axum listener,开始接收请求。 + +### 关停 + +1. `Ctrl-C` 触发 Axum graceful shutdown,并取消所有 WebSocket 连接。 +2. `ChannelManager::stop_all` 先停止外部消息入口并注销渠道。 +3. 取消 TaskSupervisor,停止接受新后台任务。 +4. 在共享的 10 秒总宽限期内等待任务退出,之后 abort 剩余任务。 + +渠道自己的 `stop()` 也必须有界。以飞书为例,端点请求、WebSocket 建连、重试等待和已连接循环共享 CancellationToken,另有 5 秒强制回收兜底。 + +## 9. 扩展指南 + +### 新增 Channel + +1. 实现 `Channel` trait,仅处理外部协议和统一消息转换。 +2. 在 `ChannelManager::init` 注册,并通过同一个 MessageBus 收发。 +3. 将可重试错误表示为 `ConnectionError`/`SendError`,永久错误使用其他类型。 +4. 为 start/stop 幂等性、取消建连、投递失败和媒体边界增加测试。 +5. 不要从 Channel 直接调用 SessionManager 或 Provider。 + +### 新增 Tool + +1. 实现 `Tool`,在集中注册点加入 `ToolRegistry`。 +2. 参数 schema 必须明确;返回值保持可供模型消费的字符串协议。 +3. 明确工具需要“workspace 默认目录”还是“不可逃逸的硬边界”;后者必须显式校验 canonical path,不能只依赖 cwd。 +4. 网络工具必须保留 SSRF/私网地址校验。 +5. 长操作应有超时;后台执行应交给 SubAgentManager/TaskSupervisor。 + +### 新增 Provider + +1. 实现 `LLMProvider`,保持其为纯 HTTP/API 适配器。 +2. 统一映射文本、媒体、tool calls、usage 和错误。 +3. 不在 Provider 中访问 Session、Bus 或 Channel。 +4. 为请求序列化和响应兼容性编写离线单元测试。 + +### 修改 Session + +先画出锁、慢 I/O、版本检查和持久化顺序。任何跨 `await` 的 Session 锁都需要特别审查;任何由旧快照产生的结果都必须在提交前验证 generation/state version。 + +## 10. 验证策略 + +按改动范围选择最小但充分的验证: + +| 改动 | 至少执行 | +|------|----------| +| 文档 | 检查链接、命令和源码路径;`git diff --check` | +| Rust 实现 | 相关定向测试、`cargo test --lib`、`cargo clippy --all-targets --all-features -- -D warnings` | +| 构建/依赖 | 上述检查加 `cargo build` | +| Provider/真实渠道 | 离线测试;有凭据时再运行 ignored integration tests | +| SQLite schema | 新库测试、迁移测试、原子性测试 | +| 生命周期/并发 | 成功、失败、超时、取消、队列满和重复 start/stop 测试 | + +模型 API 集成测试需要 `tests/test.env` 中的真实 API Key,并默认 ignored;`test_scheduler` 和 `test_request_format` 是可直接运行的离线测试。不应在无凭据时假装已经验证真实 Provider。 + +## 11. 演进决策清单 + +提交架构性修改前,逐项确认: + +- 是否仍遵守 Channel → Bus → Session → Agent 的依赖方向? +- 是否引入第二个 owner、重复状态或绕过统一注册点? +- 队列、并发、重试和等待是否全部有界? +- 取消信号能否覆盖建连、sleep、I/O 和清理阶段? +- 是否在持锁时执行了网络、模型或数据库慢操作? +- 内存与 SQLite 失败时能否保持一致? +- 错误是否区分瞬态和永久语义? +- 是否有测试覆盖正常路径与最危险的失败路径? +- README、AGENTS.md 和本文档是否需要同步更新? + +## 12. 代码导航 + +| 主题 | 入口 | +|------|------| +| Gateway 装配和关停 | `src/gateway/mod.rs` | +| WebSocket 生命周期 | `src/gateway/ws.rs` | +| Bus 和消息类型 | `src/bus/mod.rs`, `src/bus/message.rs` | +| 出站并发与重试 | `src/bus/dispatcher.rs` | +| Channel 接口与注册 | `src/channels/base.rs`, `src/channels/manager.rs` | +| Session 核心 | `src/session/session.rs` | +| Session 命令/事件 | `src/session/commands.rs`, `src/session/events.rs` | +| Agent loop | `src/agent/agent_loop.rs` | +| 后台任务监督 | `src/task_supervisor.rs` | +| SQLite 初始化和迁移 | `src/storage/mod.rs` | +| Scheduler | `src/scheduler/mod.rs` | +| 配置加载 | `src/config/mod.rs` | diff --git a/resources/skills/about-picobot/SKILL.md b/resources/skills/about-picobot/SKILL.md index f81bcf4..eca9a0c 100644 --- a/resources/skills/about-picobot/SKILL.md +++ b/resources/skills/about-picobot/SKILL.md @@ -14,10 +14,11 @@ PicoBot 是一个基于 Rust 的个人 AI 助手运行时,包含本地 Gateway | 文件 | 内容 | |------|------| | `references/config.md` | 配置字段详解:providers、models、agents、gateway、client、channels、memory、mcp、browser | -| `references/db-schema.md` | 数据库表结构:sessions、messages、memories、scheduled_jobs、llm_calls、background_tasks | -| `references/architecture.md` | 核心架构:数据流、会话系统、上下文压缩、记忆系统、Skill 优先级、MCP、子 Agent | +| `references/db-schema.md` | 数据库表结构与运行约束:sessions、messages、memories、scheduled_jobs、job_runs、llm_calls、background_tasks | +| `references/architecture.md` | 核心架构:消息并发、会话系统、持久化、生命周期、上下文压缩、记忆、MCP、子 Agent | | `references/faq.md` | 常见问题:模型切换、渠道添加、Skill 安装、历史查询、定时任务、MCP 等 | | `references/commands.md` | 常用命令:编译、启动网关、启动客户端、运行测试 | +| `references/tools.md` | 内置工具名称、参数和重要使用约束 | | `assets/config.example.json` | config.json 完整示例 | Skill 根目录路径见上方 **Skill Root Directory**。 diff --git a/resources/skills/about-picobot/references/architecture.md b/resources/skills/about-picobot/references/architecture.md index e3aa463..27b6450 100644 --- a/resources/skills/about-picobot/references/architecture.md +++ b/resources/skills/about-picobot/references/architecture.md @@ -3,9 +3,12 @@ ## 核心数据流 ``` -Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionManager → MessageBus → OutboundDispatcher → Channel - ↑ - ControlChannel → SessionManager (dialog 操作: 创建/切换/归档/删除) +Channel → MessageBus.inbound → Gateway processor → SessionManager → per-session worker → AgentLoop + ↑ │ + └── Channel ← OutboundDispatcher ← per-conversation lane ← MessageBus.outbound + +WebSocket/Channel → MessageBus.control → Gateway processor → SessionManager (dialog 操作) +Scheduler → SessionManager.handle_cron_message → AgentLoop → send_message ``` ## 模块职责 @@ -15,8 +18,8 @@ Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionM | `gateway` | HTTP/WebSocket 服务器,持有 GatewayState | | `client` | TUI 聊天客户端 | | `channels` | 外部集成(飞书、CLI),仅收发消息 | -| `bus` | 异步消息队列,纯队列不路由 | -| `session` | 会话生命周期管理、dialog 操作 | +| `bus` | 有界 inbound/outbound/control 队列;出站 dispatcher 与分目标 lane | +| `session` | 会话生命周期、dialog 操作、每 session 串行队列、上下文与持久化协调 | | `agent` | LLM 调用循环、工具执行、上下文压缩、媒体处理、子 Agent | | `providers` | LLM API 客户端(OpenAI 兼容、Anthropic) | | `tools` | Agent 工具(bash、文件操作、搜索、HTTP、web、browser、memory、delegate 等) | @@ -28,13 +31,14 @@ Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionM | `config` | 配置加载、环境变量替换、路径解析 | | `memory` | 长期记忆存储与检索 | | `mcp` | MCP(Model Context Protocol)工具集成 | +| `task_supervisor` | Gateway 后台任务注册、取消、限时等待和强制回收 | ## 功能边界 - Channels 仅收发消息,不感知 session 或 LLM -- MessageBus 是纯异步队列,不路由 -- SessionManager 拥有 session 状态,不直接调 LLM;负责注入 skills prompt -- AgentLoop 无状态,接收 dialog 事件调用 LLM、执行工具 +- MessageBus 本体持有三条有界队列;出站路由、顺序和重试由 `OutboundDispatcher` 负责 +- SessionManager 拥有 session 状态、dialog 路由、上下文构建和每 session worker,并通过 worker 创建 AgentLoop +- AgentLoop 跨轮无状态,接收已准备的 history 调用 LLM、执行工具并返回一次结果 - Providers 是纯 HTTP 客户端,无 bus/session/channel 感知 - Tools 接收原始参数,返回字符串结果 - MCP 工具在 Gateway 初始化时连接服务器、发现工具,并包装成普通 Tool 注册到 ToolRegistry @@ -48,6 +52,11 @@ Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionM - OutboundDispatcher 通过 ChannelManager 路由出站消息 - Config `.env` 加载使用 `unsafe { env::set_var(...) }` - `browser` 工具只有在 `browser.enabled=true` 时注册,依赖 Chrome/Chromium 与 WebDriver +- 同一 session 的普通消息串行处理,不同 session 可并发;session 队列容量为 32,满时明确拒绝 +- 出站消息按 `(channel, chat_id)` 分 lane 保序;lane 容量为 64,慢目标不阻塞其他目标 +- 长生命周期后台任务由 TaskSupervisor 管理;连接局部任务由其 owner 限时 join 或 abort +- 外部建连、重试等待和关停 join 必须可取消且有硬超时 +- 不得记录 API Key、Authorization header 或包含临时凭据的完整连接 URL ## 上下文压缩 @@ -101,7 +110,7 @@ create → 存入 Storage → 载入 memory → 设为当前 dialog | `list_dialogs` | 列出 `channel:chat_id` 下最近 10 个 session | | `rename` | 更新标题,内存 + Storage 同步 | | `delete` | 软删除(设 deleted_at),从内存移除 | -| `archive` | 当前为空操作 | +| `archive` | 设置 archived_at,从内存和当前 dialog 追踪中移除;可通过 include_archived 查询 | ### SessionManager 数据结构 @@ -112,13 +121,17 @@ create → 存入 Storage → 载入 memory → 设为当前 dialog 消息到达时 `resolve_dialog_id()` 按顺序确定接收 session:当前 session → Storage 最近活跃 session → 新建。 -### 消息处理三阶段 +### 消息处理与并发 -**阶段 1(持锁)**:斜杠命令检测 → 用户消息入库 → 提取记忆上下文 → 构建系统提示(skills + memory_context)→ 上下文压缩 → 创建 AgentLoop +普通消息先 `try_send` 到该 session 的有界 worker 队列,Gateway 主 processor 随即返回 `AgentProcessing`。Slash command 直接执行,不进入此队列,因此 `/stop` 不会排在长模型调用后。 -**阶段 2(无锁)**:`agent.process(history)` → LLM 调用 + 工具执行。上下文溢出时自动重新压缩重试 +Worker 的处理原则: -**阶段 3(持锁)**:持久化 agent 响应消息 → 自动生成标题(消息数 ≥ 5 且标题为"新对话"时) +1. 短暂持 Session 锁抓取快照并记录 `worker_generation`/`state_version`。 +2. 释放锁后执行消息持久化、记忆召回、上下文压缩、LLM 和工具等慢操作。 +3. 提交由旧快照产生的结果前重新验证 generation/version,防止 `/stop`、`/clear` 或 `/delete` 后写回陈旧状态。 +4. Session 持久化由独立 `persistence_lock` 串行化;批量消息使用原子写入,失败时精确回滚内存后缀。 +5. 上下文溢出时按 Provider 返回的真实限制重新压缩并重试。 ### 会话恢复 @@ -136,7 +149,7 @@ create → 存入 Storage → 载入 memory → 设为当前 dialog | 类别 | 用途 | 生命周期 | 检索方式 | |------|------|----------|----------| | **Knowledge** | 事实、偏好、模式、洞察 | 长期保留,手动删除 | 每轮注入系统提示,关键词匹配 | -| **Timeline** | 历史会话摘要 | 自动清理(默认 90 天) | `timeline_recall` 工具按需检索 | +| **Timeline** | 历史会话摘要 | 配置预期保留 90 天;当前尚无自动清理循环 | `timeline_recall` 工具按需检索 | ### MemoryEntry 字段 @@ -163,7 +176,7 @@ create → 存入 Storage → 载入 memory → 设为当前 dialog → MemoryManager::recall(content, 5, Knowledge) 返回最多 5 条匹配的知识记忆(按 importance DESC) → 格式化为 "- key: content" - → 注入系统提示的 "记忆上下文" 部分 + → 作为运行时上下文附加到本轮 user message → LLM 可见,辅助回答 ``` @@ -191,10 +204,12 @@ LLM 对话上下文接近 token 限制 (默认 128K × 70%) 时自动触发压 | 时机 | 操作 | |------|------| | 每次消息处理 | `memory_manager.recall()` 提取 Knowledge 上下文 | -| 系统提示构建 | `MemorySection` 渲染记忆指南 + 匹配的记忆 | +| 系统提示构建 | `MemorySection` 渲染记忆工具指南;匹配的 Knowledge 附加到本轮 user message | | 有压缩历史时 | `HistorySection` 提示 LLM 使用 `timeline_recall` | | 压缩完成后 | 摘要自动存储为 Timeline 记忆 | -| 空闲时 | 可配置自动 consolidation(`idle_consolidation_minutes`) | +| 会话恢复 | 加载最近 Timeline 和压缩边界后的原始消息 | + +`memory.recall_limit`、`idle_consolidation_minutes`、`timeline_retention_days` 和 `max_failures_before_degrade` 当前会被配置解析;其中每轮 Knowledge 召回在 worker 中仍固定为 5,其余自动维护策略尚未接入运行循环。不要把“配置可解析”误认为“行为已生效”。 --- @@ -223,6 +238,23 @@ Gateway 初始化时读取 `config.mcp.servers`: 默认工具集是只读工具:`file_read`、`file_search`、`content_search`、`web_fetch`、`http_request`、`calculator`。调用时可通过 `allowed_tools` 显式放开其他工具。后台任务会写入 `background_tasks` 表,默认 24 小时后清理。 +后台子 Agent 通过 `TaskSupervisor::spawn_graceful` 注册,受 `gateway.max_concurrent_background_tasks` 限制;Gateway 关停时先收到取消信号,再在总宽限期内清理。 + +--- + +## 出站投递与关停 + +`OutboundDispatcher` 对每个 `(channel, chat_id)` 创建独立 lane:同一目标保持顺序,单次发送超时 30 秒,最多尝试 3 次。只有 `ConnectionError` 和 `SendError` 被视为瞬态错误;永久错误不重试。`deliver_outbound` 等待真实渠道投递结果,`publish_outbound` 只表示成功入队。 + +Gateway 关停顺序: + +1. Ctrl-C 停止 Axum 接入并取消 WebSocket 连接。 +2. `ChannelManager::stop_all` 停止外部渠道并注销它们。 +3. 取消 TaskSupervisor,并在共享的 10 秒宽限期内等待后台任务。 +4. 超时后 abort 剩余任务并回收 JoinHandle。 + +渠道 `stop()` 也必须有界。飞书端点请求、WebSocket 建连、重试 sleep 和连接循环共享 CancellationToken,另有 5 秒强制终止兜底。 + --- ## 当前斜杠命令 diff --git a/resources/skills/about-picobot/references/commands.md b/resources/skills/about-picobot/references/commands.md index aebbb9f..8129b94 100644 --- a/resources/skills/about-picobot/references/commands.md +++ b/resources/skills/about-picobot/references/commands.md @@ -13,6 +13,16 @@ cargo run -- chat # 运行单元测试 cargo test --lib -# 运行集成测试 (需配置 tests/test.env) +# 运行离线集成/协议测试 +cargo test --test test_scheduler +cargo test --test test_request_format + +# 运行代码检查(Rust 修改必跑) +cargo clippy --all-targets --all-features -- -D warnings + +# 运行模型 API 集成测试(需配置 tests/test.env) cargo test --test test_integration -- --ignored +cargo test --test test_tool_calling -- --ignored ``` + +`test_scheduler` 和 `test_request_format` 不需要 API Key,也没有标记 `#[ignore]`。只有会真实调用 Provider 的测试需要从 `tests/test.env.example` 创建 `tests/test.env` 后使用 `-- --ignored`。 diff --git a/resources/skills/about-picobot/references/config.md b/resources/skills/about-picobot/references/config.md index 522f0a9..a77c9f0 100644 --- a/resources/skills/about-picobot/references/config.md +++ b/resources/skills/about-picobot/references/config.md @@ -55,9 +55,9 @@ |------|------|------|------| | `host` | string | 127.0.0.1 | 监听地址 | | `port` | int | 19876 | 监听端口 | -| `session_ttl_hours` | int | - | 会话过期小时数 | +| `session_ttl_hours` | int | - | 兼容/预留字段;当前没有会话 TTL 清理循环 | | `session_db_path` | string | - | SQLite 数据库路径,默认在 workspace 下 | -| `cleanup_interval_minutes` | int | - | 清理间隔 | +| `cleanup_interval_minutes` | int | - | 兼容/预留字段;当前没有按此间隔运行的 session 清理任务 | | `max_concurrent_background_tasks` | int | 10 | delegate 后台子任务最大并发数 | | `scheduler` | object | - | 调度器配置 | @@ -67,18 +67,21 @@ |------|------|------|------| | `enabled` | bool | true | 是否启动调度器并注册 cron 工具 | | `poll_interval_secs` | int | 60 | 检查到期任务的轮询间隔 | -| `max_concurrent` | int | 1 | 最大并发任务数,当前实现预留 | +| `max_concurrent` | int | 1 | 每批到期任务的最大并发数,运行时限制在 1–256 | +| `execution_timeout_secs` | int | 900 | 单个定时任务 Agent 执行的硬超时;租约会额外增加 30 秒 | ## memory 字段 | 字段 | 类型 | 默认 | 说明 | |------|------|------|------| -| `consolidation_provider` | string | - | 记忆归并 LLM 提供商 | -| `consolidation_model` | string | - | 记忆归并 LLM 模型 | -| `recall_limit` | int | 5 | 每轮注入的知识记忆条数 | -| `idle_consolidation_minutes` | int | 10 | 空闲后触发归并的分钟数 | -| `timeline_retention_days` | int | 90 | 时间线记忆保留天数 | -| `max_failures_before_degrade` | int | 3 | 归并失败次数阈值 | +| `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 保留期;当前无自动清理循环 | +| `max_failures_before_degrade` | int | 3 | 预留的归并失败阈值;当前无失败降级循环 | + +注意:这些字段都会被解析,但当前 worker 的 Knowledge 召回数量仍固定为 5;idle consolidation、Timeline 自动清理和失败降级循环尚未接入。配置存在不等于对应后台行为已经生效。 ## channels.feishu 字段 @@ -89,7 +92,7 @@ | `app_secret` | string | - | 飞书应用密钥 | | `allow_from` | []string | ["*"] | 允许交互的用户列表 | | `agent` | string | - | 使用的 agent 名称 | -| `media_dir` | string | ~/.picobot/media/feishu | 媒体存储目录 | +| `media_dir` | string | ~/.picobot/media/feishu | 配置默认值;Gateway 注册渠道时会覆盖为 `{workspace}/media/feishu` | | `reaction_emoji` | string | "Typing" | 回复意向表达的表情 | ## mcp 字段 diff --git a/resources/skills/about-picobot/references/db-schema.md b/resources/skills/about-picobot/references/db-schema.md index ec1b03b..2893b29 100644 --- a/resources/skills/about-picobot/references/db-schema.md +++ b/resources/skills/about-picobot/references/db-schema.md @@ -2,6 +2,8 @@ 数据库为 SQLite,默认位于 workspace 下的 `picobot.db`。 +连接启用 WAL、`synchronous=NORMAL`、foreign keys、5 秒 busy timeout,连接池最多 8 个连接。当前 `PRAGMA user_version=1`;启动时会在事务内补齐旧库字段和索引,遇到比程序更新的 schema version 会拒绝启动。 + ## sessions 表 会话表,一个 session 对应一个 (channel, chat_id, dialog_id) 组合。 @@ -13,13 +15,16 @@ | `chat_id` | TEXT | 聊天/群组标识 | | `dialog_id` | TEXT | 对话标识 | | `title` | TEXT | 会话标题(默认 "新对话") | -| `created_at` | INTEGER | 创建时间(unix 秒) | -| `last_active_at` | INTEGER | 最后活跃时间 | +| `created_at` | INTEGER | 创建时间(Unix 毫秒) | +| `last_active_at` | INTEGER | 最后活跃时间(Unix 毫秒) | | `message_count` | INTEGER | 消息计数 | | `routing_info` | TEXT | 路由信息 | +| `archived_at` | INTEGER | 归档时间(Unix 毫秒),NULL 表示未归档 | | `deleted_at` | INTEGER | 软删除时间戳 | | `last_consolidated_at` | INTEGER | 上次记忆归并时间 | -| `last_compressed_message_at` | INTEGER | 上次上下文压缩消息序号 | +| `last_compressed_message_at` | INTEGER | 上次上下文压缩边界时间戳 | + +`(channel, chat_id, dialog_id)` 唯一。普通列表排除 `deleted_at`;是否包含归档记录由查询参数决定。 ## messages 表 @@ -35,9 +40,11 @@ | `tool_name` | TEXT | 工具名称 | | `tool_calls` | TEXT | 工具调用参数 JSON | | `source` | TEXT | 消息来源(跨会话消息时标记来源 session_id) | -| `created_at` | INTEGER | 创建时间(unix 秒) | +| `created_at` | INTEGER | 创建时间(Unix 毫秒) | | `reasoning_content` | TEXT | provider 返回的推理内容(如有) | +`(session_id, seq)` 有唯一索引,防止并发写入重复序号。删除 session 会通过外键级联删除 messages。 + ## background_tasks 表 delegate 后台子任务表。`session_id` 不使用数据库外键,因为 session 使用软删除,关联关系由应用层维护。 @@ -82,17 +89,24 @@ delegate 后台子任务表。`session_id` 不使用数据库外键,因为 ses |------|------|------| | `id` | TEXT PK | 任务 UUID | | `name` | TEXT | 任务名称 | -| `schedule` | TEXT | 调度规则 JSON(once/every/cron) | +| `schedule` | TEXT | 调度规则 JSON(at/every/cron) | | `prompt` | TEXT | 任务提示词 | | `channel` | TEXT | 执行渠道 | | `chat_id` | TEXT | 目标对话 | -| `model` | TEXT | 使用的模型(可选) | +| `model` | TEXT | 可选模型标记;当前会存储/展示,但 Scheduler 执行仍使用默认 Agent 模型 | | `enabled` | INTEGER | 是否启用 (1/0) | | `delete_after_run` | INTEGER | 执行后自动删除 (1/0) | | `next_run_at` | INTEGER | 下次执行时间 | | `last_run_at` | INTEGER | 上次执行时间 | | `last_status` | TEXT | 上次执行状态 | | `last_error` | TEXT | 上次错误信息 | +| `locked_at` | INTEGER | 本次领取时间 | +| `lock_owner` | TEXT | 领取任务的 Scheduler owner UUID | +| `lease_until` | INTEGER | 租约到期时间;进程崩溃后允许其他实例重新领取 | +| `created_at` | INTEGER | 创建时间(Unix 毫秒) | +| `updated_at` | INTEGER | 更新时间(Unix 毫秒) | + +Scheduler 使用原子 `UPDATE ... RETURNING` 领取到期任务。任务结果、下次运行时间和租约释放在同一事务中提交,并校验 owner,防止过期 worker 覆盖已恢复的任务。 ## job_runs 表 @@ -121,3 +135,5 @@ delegate 后台子任务表。`session_id` 不使用数据库外键,因为 ses | `response_body` | TEXT | 响应体 JSON | | `error` | TEXT | 错误信息 | | `duration_ms` | INTEGER | 耗时(毫秒) | + +`request_body`/`response_body` 可能包含用户内容,排障和导出数据库时应按敏感数据处理。 diff --git a/resources/skills/about-picobot/references/faq.md b/resources/skills/about-picobot/references/faq.md index aeed40c..c62fd7c 100644 --- a/resources/skills/about-picobot/references/faq.md +++ b/resources/skills/about-picobot/references/faq.md @@ -20,6 +20,8 @@ `~/.picobot/skills/about-picobot/` 下,SKILL.md 为索引,references/ 下为各详细文档,assets/ 下为 config 示例。如被删除,重启程序自动重新安装。 +内置 Skill 只在目标目录不存在时释放,不会覆盖已安装目录。升级 PicoBot 后如需获取新版内置文档,应先备份自己的修改,再删除旧的 `~/.picobot/skills/about-picobot/` 并重启。也可把定制版放在 `{workspace}/skills/about-picobot/`,它的优先级更高。 + ## Q: 数据库文件在哪里? 默认 `{workspace}/picobot.db`,workspace 默认 `~/.picobot/workspace/`。 @@ -30,7 +32,7 @@ ## Q: 如何创建定时任务? -使用 `cron` 工具,支持一次性 (`once`)、周期性 (`every`) 和 cron 表达式调度。 +使用 `cron_add` 创建,`cron_list` 查看,`cron_update`、`cron_enable`、`cron_disable`、`cron_remove` 管理。Schedule 类型为 `at`(Unix 毫秒时间戳)、`every`(毫秒间隔)或 6 段 `cron` 表达式,可指定 IANA 时区。 ## Q: 上下文压缩是什么意思? @@ -43,3 +45,13 @@ ## Q: 如何查看 LLM 调用日志? LLM 调用记录存储在 `llm_calls` 表中。可通过 SQLite 客户端直接查询,或在代码中通过 storage 模块访问。 + +该表可能包含完整用户消息、工具参数和模型响应,按敏感数据处理;不要直接上传或粘贴到公开 issue。 + +## Q: 为什么修改了某些 memory 配置却没有看到行为变化? + +当前 `recall_limit`、`idle_consolidation_minutes`、`timeline_retention_days` 和 `max_failures_before_degrade` 都能被配置解析,但每轮 Knowledge 召回仍固定为 5,自动 idle consolidation、Timeline 清理和失败降级循环尚未接入。以当前代码行为为准。 + +## Q: Gateway 为什么无法立即退出? + +正常关停会先停止渠道,再取消受 TaskSupervisor 管理的后台任务,并最多等待 10 秒。飞书自身另有 5 秒连接任务兜底。如果持续超过这些上限,应检查是否新增了未受监督的 `tokio::spawn`、不可取消的外部 I/O,或没有 timeout 的 JoinHandle 等待。 diff --git a/resources/skills/about-picobot/references/tools.md b/resources/skills/about-picobot/references/tools.md index b1a97f6..2569943 100644 --- a/resources/skills/about-picobot/references/tools.md +++ b/resources/skills/about-picobot/references/tools.md @@ -12,16 +12,8 @@ | `content` | 是 | 消息文本内容 | | `files` | 否 | 文件路径列表 | | `origin` | 否 | 消息来源标识,不填则自动使用当前 session_id | -| `file_types` | 否 | 指定文件发送类型,`{"路径": "audio"|"file"}`。未指定则自动判断 | -### file_types 说明 - -控制文件以何种消息类型发送,主要用于飞书渠道: - -- `"audio"`:作为语音消息发送(仅 opus 格式支持) -- `"file"`:作为文件附件发送 - -飞书渠道限制:上传类型和消息类型必须一致。opus 文件以 `"audio"` 发送,其他音频(mp3、wav 等)只能以 `"file"` 发送。 +`files` 支持绝对路径和 workspace 相对路径,媒体类型由文件扩展名/MIME 自动判断。目前 schema 不支持手工指定 `file_types`。 ### 示例 @@ -29,11 +21,12 @@ { "target_chat_id": "feishu:oc_abc123", "content": "这是生成的音乐文件", - "files": ["/workspace/music.mp3"], - "file_types": {"/workspace/music.mp3": "file"} + "files": ["/workspace/music.mp3"] } ``` +发送流程会先把消息写入目标 Session,再调用 `MessageBus::deliver_outbound` 等待真实渠道投递结果(上限 120 秒)。投递失败会作为工具失败返回,不应把“已入队”报告成“已送达”。 + --- ## chat_manager — 会话管理 @@ -53,20 +46,28 @@ --- -## cron — 定时任务管理 +## Cron 定时任务工具 -管理 cron 定时任务。 +Cron 不是一个带 `action` 的统一工具,而是六个独立工具;仅在 `gateway.scheduler.enabled=true` 时注册。 -### 参数 +| 工具 | 主要参数 | 说明 | +|------|----------|------| +| `cron_add` | `schedule`, `prompt`, `channel`, `chat_id`; 可选 `name`, `model` | 创建任务 | +| `cron_list` | 可选 `status=all|enabled|disabled` | 列出任务 | +| `cron_update` | `job_id`; 可选 `prompt`, `schedule`, `channel`, `chat_id`, `model` | 更新指定字段 | +| `cron_remove` | `job_id` | 永久删除任务和关联 job runs | +| `cron_enable` | `job_id` | 启用并重新计算下次运行时间 | +| `cron_disable` | `job_id` | 禁用但保留任务 | -| 参数 | 必填 | 说明 | -|------|------|------| -| `action` | 是 | 操作: `add`, `list`, `update`, `remove`, `enable`, `disable` | -| `name` | add必须 | 任务名称 | -| `schedule` | add需要 | 调度规则: `once`(时间戳), `every`(间隔秒), `cron`(表达式) | -| `prompt` | add必须 | 任务提示词 | -| `channel` | add必须 | 执行渠道 | -| `chat_id` | add必须 | 目标对话 | +`schedule` 支持: + +```json +{"type":"at","at":1750000000000} +{"type":"every","every_ms":3600000} +{"type":"cron","expr":"0 0 9 * * *","tz":"Asia/Shanghai"} +``` + +时间戳和间隔单位为毫秒;Cron 表达式为 6 段(秒、分、时、日、月、周)。定时 Agent 不复用聊天历史,`prompt` 必须包含完整上下文,并由 Agent 使用 `send_message` 投递结果。`model` 当前会持久化和展示,但执行仍使用默认 Agent Provider/Model,不能依赖它实现模型覆盖。 --- @@ -168,15 +169,19 @@ ## file_read / file_write / file_edit / file_search / content_search — 文件操作和搜索 -工作目录内的文件读写编辑、文件名搜索和内容搜索。详细的参数定义见各工具的 parameters_schema。 +文件读写编辑、文件名搜索和内容搜索。相对路径从 workspace cwd 解析;默认注册的文件工具也接受绝对路径,因此 workspace 不是硬沙箱。详细参数以各工具的 `parameters_schema` 为准。 ## bash — 执行命令 -在本地环境执行 bash 命令,有超时限制和安全检查(阻止 rm -rf /、fork bomb 等)。 +默认从 workspace cwd 执行 bash 命令。参数为 `command` 和可选 `timeout`;默认 60 秒、最大 600 秒,输出最多约 50,000 字符。命令继承 Gateway 进程权限,可以访问 workspace 外路径;危险模式拦截不是完整沙箱。 + +## pty — 持久终端会话 + +用于交互式程序和需要保持状态的长运行命令。`action` 支持 `spawn`、`write`、`read`、`kill`、`list`;`write/read/kill` 需要 `session_id`。Gateway 进程退出时 PTY manager 会清理子进程。 ## http_request / web_fetch — HTTP 和 Web 工具 -发送 HTTP 请求和获取网页内容,有 URL 安全校验(阻止内网/本地访问)。 +`http_request` 支持 GET/POST/PUT/DELETE/PATCH、headers 和字符串 body;`web_fetch` 提取 HTML/JSON 的可读文本。两者校验 URL 与 DNS 解析结果,阻止回环、私网、link-local 和本地域名,并禁用自动重定向,以降低 SSRF 风险。 ## calculator — 计算器