# Pi Bot 项目总览

## 1. 项目定位

`pi-bot` 是一个基于 `pi-mono` / `@mariozechner/pi-coding-agent` 的 Bot 项目。

这个目录直接承载三类能力：

- 初始化骨架
- 平台 channel 接入
- provider 扩展

项目里的关键运行时代码集中在这里，阅读、修改和扩展都会更直接。

## 2. 当前目录结构

```txt
.
├── bin
│   └── pi-bot.ts
├── pi-resources
│   ├── SYSTEM.md
│   ├── extensions
│   ├── prompts
│   └── skills
│       ├── coze-asr
│       ├── coze-image-gen
│       ├── coze-tts
│       └── coze-video-gen
├── scripts
│   ├── dev.sh
│   └── prepare.sh
├── docs
│   ├── project-overview.md
│   └── user
├── src
│   ├── agent.ts
│   ├── cli.ts
│   ├── pi-resources.ts
│   ├── config.ts
│   ├── core.ts
│   ├── session-store.ts
│   ├── tools
│   │   ├── common
│   │   │   └── format-coze-error.ts
│   │   ├── web-search
│   │   │   └── index.ts
│   │   ├── web-fetch
│   │   │   └── index.ts
│   │   └── index.ts
│   ├── dashboard
│   │   ├── config-store.ts
│   │   ├── api
│   │   │   ├── channels.ts
│   │   │   ├── models.ts
│   │   │   └── overview.ts
│   │   ├── web
│   │   │   ├── src
│   │   │   │   ├── components
│   │   │   │   │   ├── ui
│   │   │   │   │   ├── app-layout.tsx
│   │   │   │   │   └── page-title.tsx
│   │   │   │   ├── hooks
│   │   │   │   │   ├── use-fetch.ts
│   │   │   │   │   └── use-local-storage-state.ts
│   │   │   │   ├── pages
│   │   │   │   ├── services
│   │   │   │   │   └── chat-ws-service.ts
│   │   │   │   ├── utils
│   │   │   │   ├── main.tsx
│   │   │   │   └── styles.css
│   │   │   ├── index.html
│   │   │   ├── postcss.config.cjs
│   │   │   ├── tsconfig.json
│   │   │   └── vite.config.ts
│   │   ├── index.ts
│   │   ├── server.ts
│   │   └── types.ts
│   ├── index.ts
│   ├── channels
│   │   ├── feishu
│   │   │   ├── index.ts
│   │   │   └── streaming-card.ts
│   │   └── wechat
├── tests
│   ├── cli.test.ts
│   ├── config.test.ts
│   ├── dashboard-docs-api.test.ts
│   ├── dashboard-models-api.test.ts
│   ├── feishu-channel.test.ts
│   ├── feishu-streaming-card.test.ts
│   ├── pi-resources.test.ts
│   ├── session-store.test.ts
│   ├── web-fetch.test.ts
│   ├── web-search.test.ts
│   └── smoke
└── types
```

各部分职责：

- `bin/pi-bot.ts`
  - CLI 可执行入口，通过 `pnpm link --global` 注册为全局命令 `pi-bot`
- `pi-resources`
  - repo 内置的 SYSTEM / extensions / prompts / skills 源码目录
- `src/config.ts`
  - 项目唯一配置入口，读取 `config.json` 并装配 agent、channel、routing 等运行配置
- `src/index.ts`
  - 应用装配层，创建 channel 并把消息交给 runtime
- `src/agent.ts`
  - `pi-coding-agent` runtime 封装
- `src/cli.ts`
  - CLI 命令实现，支持 `config set/get/list/delete` 子命令读写 `workspace/config.json`
- `src/pi-resources.ts`
  - 统一装配 repo 内置资源（extensions / skills / prompts / system prompt），并显式关闭 `AGENTS.md` 自动发现
- `src/core.ts`
  - 通用消息协议、共享类型和基础 helper
- `src/session-store.ts`
  - session 持久化索引层
  - 负责维护 `sessionKey -> sessionId/sessionFile` 映射，并管理 transcript header、reset 归档等文件操作
- `src/tools/*`
  - 自定义工具目录，通过 `customTools` 注入到 `pi-coding-agent` session 中
  - `web-search/`：基于 `coze-coding-dev-sdk` 的网页/图片搜索工具（`coze_web_search`）
  - `web-fetch/`：基于 `coze-coding-dev-sdk` 的网页内容抓取工具（`coze_web_fetch`）
  - `common/`：工具间共享的辅助函数（如 `formatCozeError`）
  - `index.ts`：barrel export
- `src/channels/feishu`
  - 飞书消息接收、标准化、去重、过滤和回复发送
  - `streaming-card.ts`：飞书 CardKit 流式卡片的创建、增量更新和回退逻辑
- `src/channels/wechat`
  - 微信消息标准化和回复发送
- `src/dashboard/*`
  - 本地 Dashboard（HTTP + WebSocket），用于查看运行状态、编辑配置（默认模型、飞书渠道）、以及以 UI 方式调试聊天会话
  - 开发态通过 Vite middleware 提供前端资源；生产态直接托管 `src/dashboard/web/dist`
- `src/dashboard/config-store.ts`
  - Dashboard 配置存储抽象
  - 默认使用文件存储读写 `workspace/config.json`
  - 也支持内存存储，便于 smoke test 或宿主注入
- `tests/smoke`
  - 基础链路 smoke test
  - 当前会额外覆盖 dashboard 的 models/channels 配置读写，并使用内存 `ConfigStore` 避免依赖真实配置文件
- `tests/config.test.ts`
  - config 解析与模型构建测试
- `tests/cli.test.ts`
  - CLI config 命令单测：覆盖 set/get/list/delete 子命令、嵌套路径、数组索引、值类型推导、错误处理
- `tests/pi-resources.test.ts`
  - `pi-resources.ts` 资源装配与优先级逻辑测试
- `tests/session-store.test.ts`
  - session 持久化索引层测试
- `tests/web-fetch.test.ts`
  - `coze_web_fetch` 工具单测：覆盖 text/markdown/json 格式渲染、textOnly 模式、链接过滤、图片尺寸、并发抓取、异常处理
- `tests/web-search.test.ts`
  - `coze_web_search` 工具单测：覆盖基本搜索、摘要、内容包含、图片搜索、路由判断、默认参数、空结果、异常处理
- `tests/dashboard-models-api.test.ts`
  - Dashboard models API 测试
- `tests/feishu-channel.test.ts`
  - 飞书 channel 消息收发测试
- `tests/feishu-streaming-card.test.ts`
  - 飞书流式卡片测试
- `types`
  - 第三方 SDK 的类型补充

### 自定义工具说明

`src/tools/` 下的工具通过 `coze-coding-dev-sdk` 接入 Coze 平台能力，当前包含：

| 工具名 | 功能 |
|--------|------|
| `coze_web_search` | 网页/图片搜索，支持时间过滤、站点限制、摘要输出 |
| `coze_web_fetch` | 抓取网页内容，支持 text / markdown / json 三种输出格式 |

所需环境变量（通过 `.env` 或运行时注入）：

| 变量 | 用途 |
|------|------|
| `COZE_WORKLOAD_IDENTITY_API_KEY` | Coze 平台鉴权 |
| `COZE_INTEGRATION_BASE_URL` | Coze Integration 服务基地址 |

## 3. Dashboard（现状实现）

Dashboard 是一个「和 Bot 同进程」启动的本地 HTTP 服务，默认地址：

- `http://127.0.0.1:5000`
- 端口/Host 可通过环境变量覆盖：`PI_BOT_DASHBOARD_PORT`、`PI_BOT_DASHBOARD_HOST`

服务端入口：

- [`src/dashboard/server.ts`](../src/dashboard/server.ts) 负责 Express 路由、Vite 开发中间件、以及 WebSocket（聊天流式）
- [`src/dashboard/index.ts`](../src/dashboard/index.ts) 负责把 Bot runtime 信息注入 Dashboard server
- [`src/dashboard/config-store.ts`](../src/dashboard/config-store.ts) 负责把 Dashboard 的配置读写抽象成 `ConfigStore`

### 3.1 前端技术栈

- Vite + React + React Router
- Tailwind CSS v4（`styles.css` 里使用 `@import "tailwindcss"` / `@theme`）
- shadcn 风格组件（代码内置于 `src/dashboard/web/src/components/ui/*`）
- 暗黑模式：通过在 `documentElement` 上切换 `dark` class，并在 `styles.css` 提供 `:root` / `.dark` token

### 3.2 页面与路由

前端路由位于 `src/dashboard/web/src/main.tsx`，当前页面：

- `/overview`：系统摘要（运行状态、启用渠道）、默认模型切换、飞书渠道配置
- `/chat`：调试聊天会话（含历史、reset、WebSocket 流式）

导航栏仅包含「聊天」和「概览」两个入口，md 断点及以上直接展示带文字标签的侧边栏。

### 3.3 HTTP API（服务端）

当前主要接口（均在同一进程内，不做鉴权）：

- `GET /api/overview`：运行状态/启用渠道等
- `GET /api/models`：通过 `ConfigStore` 读取配置并返回默认模型与可选模型列表（用于下拉选择）
- `POST /api/models`：仅写回默认模型（请求体只包含 `defaultModel`；服务端会基于当前配置扫描列表做校验）
- `GET /api/channels`：通过 `ConfigStore` 读取配置并返回可编辑结构
- `POST /api/channels`：通过 `ConfigStore` 写回配置；默认文件存储会落回 `workspace/config.json`，保存后需要重启进程生效
- `GET /api/chat/history`：按 session identity 计算 sessionKey，并按需加载持久化 transcript 后返回消息
- `POST /api/chat/reset`：重置指定 sessionKey 的会话；会切换到新的 `sessionId/sessionFile`，旧 transcript 归档到 `archive/`
- `WS /api/chat/ws`：聊天流式（推荐的 UI 通道）

补充说明：

- 生产/日常开发默认使用文件型 `ConfigStore`
- `createBotApp(..., { dashboardConfigStore })` 可注入自定义存储实现
- `tests/smoke/run-smoke.ts` 当前使用内存型 `ConfigStore`，直接验证 dashboard 配置的读写 API 行为

### 3.4 模型配置能力边界

概览页面中的「默认模型」区域，以 UI 方式快速切换默认模型，降低直接手改 JSON 的成本。当前支持：

- 修改默认模型（写回 `agents.defaults.model.primary`）

当前不支持：

- 编辑 Provider 配置
- 编辑模型参数
- 新增/删除 Provider 或模型条目

因此，如果要接入新的 openai-compatible provider、修改模型定义或参数，仍然建议直接编辑默认文件存储对应的 `workspace/config.json`；补充完成后，Dashboard 会自动读取并展示这些新模型供选择。

### 3.5 Session 持久化

当前 `pi-bot` 已支持 session 持久化，逻辑参考 `openclaw` 的 transcript/sessionFile 模式：

- `src/core.ts` 里的 `getSessionKey()` 仍然负责根据 channel / conversation / thread 等信息生成稳定的业务会话键
- `src/session-store.ts` 负责把这个 `sessionKey` 映射到一个可落盘的 transcript 文件
- `src/agent.ts` 在创建真实 `pi-coding-agent` session 时，不再只依赖进程内 `Map`，而是通过 `SessionManager.open(sessionFile)` 绑定到固定 transcript

默认持久化目录位于：

```txt
<agentDir>/.pi-bot/sessions/
├── index.json
├── transcripts/
└── archive/
```

其中：

- `index.json`
  - 保存 `sessionKey -> { sessionId, sessionFile, updatedAt }` 的索引
- `transcripts/`
  - 保存当前活跃会话的 transcript（`.jsonl`）
- `archive/`
  - 保存 reset 后被归档的旧 transcript

补充说明：

- 持久化覆盖全部 channel：`dashboard` / `feishu` / `wechat`
- transcript 文件名不会直接使用原始 `sessionKey`，而是基于其 hash 生成，避免特殊字符和超长文件名问题
- Dashboard 重启后仍可通过 `GET /api/chat/history` 恢复已有会话消息
- Reset 不会直接覆盖旧 transcript，而是新建 session 并归档旧文件，便于排查和追溯

### 3.6 构建方式

仓库构建命令：

```bash
npm run build
```

其中会先运行类型检查，再构建 Dashboard 前端静态资源。

如果只需要单独构建前端静态资源，也可以运行：

```bash
npm run dashboard:build
```

该脚本对应 Vite 配置：

- [`src/dashboard/web/vite.config.ts`](../src/dashboard/web/vite.config.ts)

开发态（`NODE_ENV !== "production"`）：

- Dashboard server 通过 Vite middleware 直接服务前端（无需单独启动 Vite dev server）

生产态（`NODE_ENV=production`）：

- Dashboard server 托管 `src/dashboard/web/dist` 的静态文件
  - 如果你需要生产态可用，记得先运行 `npm run build`

## 4. 运行模型

当前项目的运行链路如下：

1. channel 接收平台事件
2. channel 把事件转换成 `BotMessage`
3. `src/index.ts` 调用 `runtime.run(message)`
4. `src/agent.ts` 根据配置选择 mock 或真实 `pi-coding-agent`，默认使用真实 `pi` runtime；只有显式设置 `PI_BOT_AGENT_MODE=mock` 时才走 mock
5. 真实 runtime 会先把 `pi-resources/` 里的 repo 内置资源接到 `ResourceLoader`，并显式关闭 `AGENTS.md` 自动加载；`workspace/.pi/` 仅保留本地覆盖作用
6. runtime 会再通过 `getSessionKey()` 确定业务会话，并通过 `src/session-store.ts` 找到或创建对应的持久化 transcript
7. runtime 通过 `createAgentSession()` 创建 session 时，将 `src/tools/` 中导出的工具以 `customTools` 参数注入，使 agent 在对话中可调用自定义工具
8. runtime 基于该 transcript 执行 prompt，新的 user / assistant 消息持续写入 sessionFile
9. runtime 返回文本
10. channel 把文本发回原平台

## 4.1 飞书卡片与流式输出

`pi-bot` 的飞书回复有两种渲染形态：

- 普通文本：`msg_type: "text"`
- Markdown 卡片：`msg_type: "interactive"`（卡片内使用 `tag: "markdown"`）

默认情况下会按内容自动选择是否使用卡片（与 `openclaw` 的判断一致）：

- 命中代码块（```...```，流式阶段也接受仅出现 opening fence）或 Markdown 表格时，使用卡片渲染
- 其他普通文本/普通 Markdown，使用文本消息

当启用流式回复（`onStreamMessage` + `runtime.stream()`）时，如果内容命中“使用卡片”的条件，会尝试使用 CardKit 的 streaming card API 做实时更新：

- 创建 streaming card（CardKit `cardkit/v1/cards`）
- reply 一条 `interactive` 卡片引用到原消息
- 按增量内容持续更新卡片中的 markdown 元素
- 最终关闭 streaming 模式并更新 summary

注意：流式卡片需要飞书开放平台开通 CardKit 相关权限（例如 `cardkit:card:write`）。如果权限未开通，streaming card 创建/更新会失败并自动回退为“最终一次性回复”。

如果 streaming card 创建失败（例如未开通 CardKit 权限、参数/租户不匹配等），会自动回退为“最终一次性回复”，不会影响正常出消息。

补充说明：

- 进程内仍保留 `Map<string, AgentSession>` 作为热缓存，避免同一 session 在单次运行期间重复创建
- 但真正的“可恢复会话”依赖的是磁盘上的 transcript，而不是这个内存 `Map`
- 因此进程重启后，只要 `sessionKey` 不变，就可以重新打开对应 transcript 继续对话
- 飞书 thinking reaction 默认 emoji 已调整为 `OneSecond`，也可通过 `channels.feishu.thinkingReaction.emojiType` 覆盖

## 5. 当前建议的开发顺序

如果后续继续扩展，建议按这个顺序理解项目：

1. 阅读根目录 `README.md`
2. 阅读 `docs/project-overview.md`
3. 阅读 `src/config.ts`
4. 阅读 `src/index.ts`
5. 阅读 `src/agent.ts`
6. 根据需要再进入 `src/channels/*`、`src/dashboard/*` 和相关测试

## 6. 关于 docs

`docs/` 目录中保留了两类材料：

- `docs/project-overview.md`
  - 面向维护者的架构介绍
- `docs/user/getting-started.md`
  - 面向项目使用者的快速开始文档

最贴近现状的介绍文档是这份 `docs/project-overview.md`。如果其他文档与当前目录结构冲突，应以当前源码、`README.md` 和本文件为准。
