diff --git "a/all-in-one-book/00-\345\211\215\350\250\200/01-\346\234\254\344\271\246\347\256\200\344\273\213\344\270\216\351\230\205\350\257\273\346\214\207\345\215\227.md" "b/all-in-one-book/00-\345\211\215\350\250\200/01-\346\234\254\344\271\246\347\256\200\344\273\213\344\270\216\351\230\205\350\257\273\346\214\207\345\215\227.md"
new file mode 100644
index 000000000000..5170d27beeb3
--- /dev/null
+++ "b/all-in-one-book/00-\345\211\215\350\250\200/01-\346\234\254\344\271\246\347\256\200\344\273\213\344\270\216\351\230\205\350\257\273\346\214\207\345\215\227.md"
@@ -0,0 +1,68 @@
+# 本书简介与阅读指南
+
+> 📌 一本从零到精通的 OpenCode 中文技术书籍,采用"地图→路线→细节"三级认知结构。
+
+## 本书的目标读者
+
+本书面向以下读者:
+
+| 角色 | 你会获得什么 |
+|------|-------------|
+| **AI 工具用户** | 学会高效使用 OpenCode 完成日常编码任务 |
+| **全栈开发者** | 理解 Coding Agent 的架构设计,学习 TypeScript/Bun/Effect-TS 实践 |
+| **开源贡献者** | 掌握源码结构,快速定位并修改代码 |
+| **架构师** | 理解 Agent 系统的设计模式和技术选型 |
+| **AI 产品经理** | 理解 AI Coding Agent 的能力边界和扩展方式 |
+
+## 本书的认知结构
+
+我们采用 **"地图→路线→细节"** 三级认知结构:
+
+```
+第一级(全景地图): 花 30 分钟建立完整心智模型
+ ↓ 知道"所有部件在哪、怎么连、怎么跑"
+第二级(路线导航): 沿着数据流/执行流走通关键路径
+ ↓ 知道"一个请求从输入到输出经历了什么"
+第三级(细节深入): 对每个模块进行源码级解析
+ ↓ 知道"每个零件内部怎么工作"
+```
+
+### 推荐阅读路线
+
+**路线 A:快速了解(1 小时)**
+1. 01-全景视野/01-一图看懂OpenCode
+2. 01-全景视野/04-一次对话的完整旅程
+3. 02-快速上手/02-五分钟体验OpenCode
+
+**路线 B:深入架构(半天)**
+1. 01-全景视野(全部 11 节)
+2. 03-关键路径详解(全部 5 节)
+
+**路线 C:扩展开发(按需)**
+1. 01-全景视野/09-扩展点全景图
+2. 06-扩展与集成(选择你需要的扩展类型)
+3. 10-实战案例(动手练习)
+
+**路线 D:完整精读(一周)**
+按目录顺序从头到尾读完。
+
+## 约定说明
+
+- 技术术语首次出现时使用 `中文(English)` 格式,例如:`会话(Session)`
+- 源码引用标注文件路径,如 `// 文件: packages/opencode/src/agent/agent.ts`
+- 所有架构图使用 Mermaid 语法
+- 💡 标记表示关键设计决策
+- 📍 标记表示"你在全景图中的位置"
+- ⚠️ 标记表示需要特别注意的地方
+
+## 前置知识
+
+阅读本书的不同章节需要不同程度的前置知识:
+
+| 章节 | 需要了解 |
+|------|---------|
+| 00-前言、01-全景视野 | 基本编程概念 |
+| 02-快速上手 | 命令行基础操作 |
+| 03-关键路径、04-核心引擎 | TypeScript 基础、async/await |
+| 05-用户界面 | 前端框架基本概念 |
+| 09-周边知识 | 无(本章就是帮你补前置知识的) |
diff --git "a/all-in-one-book/00-\345\211\215\350\250\200/02-OpenCode\346\230\257\344\273\200\344\271\210.md" "b/all-in-one-book/00-\345\211\215\350\250\200/02-OpenCode\346\230\257\344\273\200\344\271\210.md"
new file mode 100644
index 000000000000..6f32050364c4
--- /dev/null
+++ "b/all-in-one-book/00-\345\211\215\350\250\200/02-OpenCode\346\230\257\344\273\200\344\271\210.md"
@@ -0,0 +1,56 @@
+# OpenCode 是什么
+
+> 📌 OpenCode 是一个开源的 AI 编程代理(Coding Agent),让你在终端中与 AI 结对编程。
+
+## 一句话定义
+
+**OpenCode 是一个运行在终端里的 AI 编程助手**,它能理解你的代码库,帮你读代码、写代码、改代码、运行命令,就像一个坐在你旁边的资深程序员搭档。
+
+## 核心特性
+
+| 特性 | 说明 |
+|------|------|
+| 🤖 **多模型支持** | 支持 Claude、GPT、Gemini、Mistral 等 20+ 种 LLM |
+| 🔧 **内置工具** | 文件读写、代码搜索、Shell 执行、Git 操作等 |
+| 🔌 **MCP 协议** | 通过 Model Context Protocol 无限扩展工具能力 |
+| 🖥️ **多端体验** | TUI 终端界面、Web 应用、桌面客户端 |
+| 📂 **项目感知** | 自动理解项目结构、Git 状态、文件关系 |
+| 🔒 **权限控制** | 精细的权限模型,控制 AI 可以做什么 |
+| 🧩 **可扩展** | 自定义 Agent、Tool、Command、Plugin |
+| 💾 **会话持久化** | 对话历史保存在本地 SQLite 数据库 |
+
+## 与同类工具对比
+
+| 特性 | OpenCode | GitHub Copilot CLI | Cursor | Aider |
+|------|----------|-------------------|--------|-------|
+| 开源 | ✅ MIT | ❌ | ❌ | ✅ |
+| 多 LLM 支持 | ✅ 20+ | ❌ 仅 GPT | ❌ 有限 | ✅ |
+| MCP 协议 | ✅ | ❌ | ✅ | ❌ |
+| TUI 界面 | ✅ | ✅ | ❌ | ✅ |
+| Web 界面 | ✅ | ❌ | ✅ | ❌ |
+| 桌面客户端 | ✅ | ❌ | ✅ | ❌ |
+| 插件系统 | ✅ | ❌ | ❌ | ❌ |
+| 自定义 Agent | ✅ | ❌ | ❌ | ❌ |
+
+## 项目背景
+
+OpenCode 最初由 [anomalyco](https://github.com/anomalyco) 团队开发,本书基于 `propress/opencode` 仓库的 `dev` 分支编写。项目采用 MIT 许可证,欢迎社区贡献。
+
+- **官网**: https://opencode.ai
+- **GitHub**: https://github.com/anomalyco/opencode
+- **技术栈**: TypeScript + Bun + Effect-TS + Vercel AI SDK + SolidJS + Tauri + Drizzle ORM
+
+## OpenCode 能做什么?
+
+```
+你:帮我把 src/utils.ts 里的所有 forEach 改成 for...of 循环
+
+OpenCode:
+1. 🔍 搜索 src/utils.ts 中的 forEach 用法
+2. 📖 读取文件内容,理解上下文
+3. ✏️ 逐个替换为 for...of,保持语义一致
+4. ✅ 展示修改 diff,等你确认
+5. 💾 保存文件,创建 Git 快照
+```
+
+这就是 OpenCode 的核心工作方式:**理解意图 → 分析代码 → 选择工具 → 执行操作 → 确认结果**。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/01-\344\270\200\345\233\276\347\234\213\346\207\202OpenCode.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/01-\344\270\200\345\233\276\347\234\213\346\207\202OpenCode.md"
new file mode 100644
index 000000000000..69f71d8e21e1
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/01-\344\270\200\345\233\276\347\234\213\346\207\202OpenCode.md"
@@ -0,0 +1,291 @@
+# 一图看懂 OpenCode
+
+> 📌 一句话总结:OpenCode 是一个六层架构的 AI 编程助手——从用户界面到基础设施,每一层都可插拔、可替换。
+> 🗺️ 本节在全景中的位置:这是全书的**导航地图**,后续所有章节都会回到这张图上标注"你在这里"。
+
+---
+
+## 全景架构图
+
+我们先来看 OpenCode 的完整架构。这张图从上到下分为六层,每一层解决一类问题:
+
+```mermaid
+graph TB
+ subgraph User["👤 用户层(User)"]
+ CLI["命令行
CLI Entry"]
+ WebUI["网页界面
Web UI"]
+ Desktop["桌面应用
Desktop App"]
+ VSCode["VS Code 插件
Extension"]
+ Slack["Slack Bot"]
+ GHAction["GitHub Action"]
+ end
+
+ subgraph Interface["🖥️ 接口层(Interface)"]
+ TUI["终端界面
packages/opencode/src/tui/"]
+ Server["HTTP 服务
packages/opencode/src/server/server.ts"]
+ Hono["Hono 路由
REST API + OpenAPI"]
+ Event["事件流
SSE /event"]
+ end
+
+ subgraph Engine["⚙️ 引擎层(Engine)"]
+ Agent["智能体
packages/opencode/src/agent/agent.ts"]
+ Session["会话
packages/opencode/src/session/index.ts"]
+ Provider["模型提供者
packages/opencode/src/provider/provider.ts"]
+ Message["消息
packages/opencode/src/session/message-v2.ts"]
+ Permission["权限
packages/opencode/src/permission/"]
+ end
+
+ subgraph Capability["🔧 能力层(Capability)"]
+ Tool["工具
packages/opencode/src/tool/"]
+ Skill["技能
packages/opencode/src/skill/index.ts"]
+ MCP["MCP 协议
packages/opencode/src/mcp/index.ts"]
+ LSP["语言服务
packages/opencode/src/lsp/"]
+ Command["命令
packages/opencode/src/command/index.ts"]
+ Plugin["插件
packages/opencode/src/plugin/"]
+ end
+
+ subgraph Infra["🏗️ 基础设施层(Infrastructure)"]
+ Storage["存储
packages/opencode/src/storage/"]
+ Git["Git 操作
packages/opencode/src/git/"]
+ Snapshot["快照
packages/opencode/src/snapshot/"]
+ FileOps["文件操作
packages/opencode/src/file/"]
+ Auth["认证
packages/opencode/src/auth/"]
+ Config["配置
packages/opencode/src/config/config.ts"]
+ end
+
+ subgraph External["☁️ 外部服务层(External)"]
+ LLM["大语言模型
OpenAI / Anthropic / Google ..."]
+ MCPServer["MCP 服务器
外部工具服务"]
+ GitRemote["Git 远程仓库
GitHub / GitLab"]
+ end
+
+ CLI --> TUI
+ WebUI --> Server
+ Desktop --> Server
+ VSCode --> Server
+ Slack --> Server
+ GHAction --> Server
+
+ TUI --> Agent
+ Server --> Hono
+ Hono --> Agent
+ Hono --> Session
+ Hono --> Provider
+ Event --> Session
+
+ Agent --> Session
+ Agent --> Provider
+ Agent --> Permission
+ Session --> Message
+ Provider --> LLM
+
+ Agent --> Tool
+ Agent --> Skill
+ Agent --> Command
+ Tool --> MCP
+ Tool --> LSP
+ Tool --> Plugin
+
+ Tool --> FileOps
+ Tool --> Git
+ Tool --> Snapshot
+ Session --> Storage
+ Agent --> Config
+ Provider --> Auth
+
+ MCP --> MCPServer
+ Git --> GitRemote
+```
+
+---
+
+## 餐巾纸架构图
+
+如果只用 5 个概念解释 OpenCode,那就是这张"餐巾纸图(Napkin Diagram)":
+
+```mermaid
+graph LR
+ U["🧑 用户"] -->|"提问"| A["🤖 Agent
智能体"]
+ A -->|"选择模型"| P["🧠 Provider
模型提供者"]
+ A -->|"调用工具"| T["🔧 Tool
工具"]
+ A -->|"读写记录"| S["💬 Session
会话"]
+ T -->|"操作代码"| F["📁 文件系统
& Git"]
+
+ style U fill:#e1f5fe
+ style A fill:#fff3e0
+ style P fill:#f3e5f5
+ style T fill:#e8f5e9
+ style S fill:#fce4ec
+ style F fill:#f5f5f5
+```
+
+**用一句话串起来**:用户向**智能体(Agent)**提问,智能体通过**模型提供者(Provider)**调用大语言模型生成回答,过程中使用**工具(Tool)**来读写文件、执行命令,所有对话记录保存在**会话(Session)**中。
+
+---
+
+## 每个模块一句话
+
+下表按架构分层列出 OpenCode 的核心模块,用一句话 + 一个生活类比帮助理解:
+
+| 层级 | 模块 | 一句话描述 | 生活类比 |
+|------|------|-----------|---------|
+| 接口层 | **Server 服务器** | 基于 Hono 框架的 HTTP 服务,暴露 REST API 和 SSE 事件流 | 餐厅的前台接待 |
+| 接口层 | **TUI 终端界面** | 基于 Ink(React)的终端交互界面 | 餐厅的点餐屏幕 |
+| 引擎层 | **Agent 智能体** | 编排模型调用和工具使用的核心决策单元,内置 `build`、`plan`、`explore` 等角色 | 项目经理 |
+| 引擎层 | **Session 会话** | 管理对话历史、消息和上下文窗口 | 会议纪要 |
+| 引擎层 | **Provider 模型提供者** | 统一封装 18+ 家大模型服务商(OpenAI、Anthropic、Google 等) | 翻译中介 |
+| 引擎层 | **Message 消息** | 结构化的对话消息,包含文本(Text)、推理(Reasoning)、工具调用(ToolCall)等多种部件(Part) | 信封里的多页信件 |
+| 引擎层 | **Permission 权限** | 细粒度的操作权限控制:允许(allow)/ 询问(ask)/ 拒绝(deny) | 公司审批流 |
+| 能力层 | **Tool 工具** | 40+ 个内置工具:bash、read、edit、write、grep、glob 等 | 工人的工具箱 |
+| 能力层 | **Skill 技能** | 以 `SKILL.md` 文件定义的可复用提示词模板 | 操作手册 |
+| 能力层 | **MCP 协议** | 模型上下文协议(Model Context Protocol)客户端,连接外部工具服务 | USB 接口 |
+| 能力层 | **LSP 语言服务** | 集成语言服务器提供代码智能:悬停(hover)、跳转定义(definition)、引用(references) | 代码的 GPS 导航 |
+| 能力层 | **Command 命令** | 可绑定快捷键的预定义操作模板,来源于配置(Config)、技能(Skill)或 MCP | 快捷指令 |
+| 能力层 | **Plugin 插件** | 外部扩展包,可注册新工具和新功能 | 浏览器扩展 |
+| 基础设施 | **Storage 存储** | 基于 SQLite + Drizzle ORM 的持久化层,支持 Bun 和 Node.js 两种运行时 | 档案室 |
+| 基础设施 | **Config 配置** | 8 级优先级的配置系统:远程默认 → 全局 → 项目 → 环境变量 → 企业托管 | 公司规章制度 |
+| 基础设施 | **Git 版本控制** | 封装 Git 命令行操作:分支、状态、差异、合并基准 | 时间机器 |
+| 基础设施 | **Snapshot 快照** | 用独立的 Git 仓库追踪工作区文件变更,支持回滚 | 游戏存档 |
+| 基础设施 | **File 文件操作** | 文件读写、二进制检测、gitignore 过滤、模糊搜索 | 文件管理器 |
+| 基础设施 | **Auth 认证** | 管理 API 密钥(API Key)、OAuth 令牌(Token)和 Well-Known 凭证 | 钥匙串 |
+
+---
+
+## 关键设计决策
+
+### 1. Effect 架构:函数式依赖注入
+
+OpenCode 使用 [Effect](https://effect.website/) 库进行依赖注入和错误处理。每个模块都定义为 Effect 的 `Service`,通过 `Layer` 组合。
+
+```
+// packages/opencode/src/agent/agent.ts 中的典型模式
+Agent.Service = Effect.Tag()
+```
+
+**为什么这样设计?** Effect 让每个服务都可测试、可替换,且所有错误路径都在类型系统中显式表达——不会有"忘记 catch"的情况。
+
+### 2. 多智能体系统:专业分工
+
+OpenCode 不是单一 Agent,而是一个多智能体系统(Multi-Agent System):
+
+```mermaid
+graph TD
+ User["用户"] --> Build["build 智能体
默认执行者"]
+ User --> Plan["plan 智能体
只读规划者"]
+ Build --> General["general 子智能体
多步骤任务"]
+ Build --> Explore["explore 子智能体
快速探索"]
+ Build --> Compaction["compaction 智能体
上下文压缩"]
+ Build --> Title["title 智能体
标题生成"]
+ Build --> Summary["summary 智能体
摘要生成"]
+
+ style Build fill:#4CAF50,color:#fff
+ style Plan fill:#2196F3,color:#fff
+ style General fill:#FF9800,color:#fff
+ style Explore fill:#9C27B0,color:#fff
+```
+
+- **`build`**(构建者):默认智能体,拥有完整工具权限,可以读写文件、执行命令
+- **`plan`**(规划者):只读智能体,只能查看代码不能修改,用于分析和规划
+- **`general`**(通用者):子智能体,用于复杂多步骤任务的委派
+- **`explore`**(探索者):子智能体,擅长快速搜索代码库(grep、glob、bash)
+
+### 3. 消息的"零件化"设计
+
+消息(Message)不是一个单一文本,而是由多个**部件(Part)**组成的结构体:
+
+| 部件类型 | 用途 |
+|---------|------|
+| `TextPart` | 文本输出 |
+| `ReasoningPart` | LLM 的推理过程(如 Claude 的 thinking) |
+| `ToolPart` | 工具调用及其结果,含状态机(pending → running → completed/error) |
+| `FilePart` | 文件附件 |
+| `SnapshotPart` | 工作区快照 |
+| `PatchPart` | 代码差异补丁 |
+| `SubtaskPart` | 委派给子智能体的任务 |
+| `StepStartPart` / `StepFinishPart` | 多步骤执行的开始和结束 |
+
+这种设计让前端可以**流式渲染**每个部件,而不需要等整条消息完成。
+
+### 4. 工具的权限网关
+
+每个工具调用都经过权限系统(Permission)的网关:
+
+```mermaid
+sequenceDiagram
+ participant A as Agent 智能体
+ participant T as Tool 工具
+ participant P as Permission 权限
+ participant U as User 用户
+
+ A->>T: 调用 bash("rm -rf node_modules")
+ T->>P: 请求权限 {name: "bash", patterns: ["rm -rf *"]}
+ P->>P: 评估规则集
+ alt 规则为 allow
+ P-->>T: ✅ 通过
+ else 规则为 ask
+ P->>U: 🔔 请确认此操作
+ U-->>P: once / always / reject
+ P-->>T: 返回决定
+ else 规则为 deny
+ P-->>T: ❌ 拒绝
+ end
+ T-->>A: 返回结果
+```
+
+### 5. 配置的 8 级优先级
+
+配置系统(`packages/opencode/src/config/config.ts`)支持 8 级层叠覆盖,优先级从低到高:
+
+```mermaid
+graph BT
+ A["1️⃣ 远程 .well-known/opencode
组织默认配置"] --> B
+ B["2️⃣ 全局 ~/.config/opencode/opencode.json"] --> C
+ C["3️⃣ OPENCODE_CONFIG 环境变量指定的文件"] --> D
+ D["4️⃣ 项目 opencode.json / opencode.jsonc"] --> E
+ E["5️⃣ .opencode/ 目录"] --> F
+ F["6️⃣ OPENCODE_CONFIG_CONTENT 环境变量"] --> G
+ G["7️⃣ 远程账户配置(已登录时)"] --> H
+ H["8️⃣ 企业托管配置(最高优先级)"]
+
+ style H fill:#f44336,color:#fff
+ style A fill:#9E9E9E,color:#fff
+```
+
+### 6. 统一的提供者抽象
+
+`Provider`(`packages/opencode/src/provider/provider.ts`)为 18+ 家大模型服务商提供统一接口:
+
+| 提供者 | 说明 |
+|--------|------|
+| OpenAI | GPT 系列 |
+| Anthropic | Claude 系列 |
+| Google | Gemini / Vertex AI |
+| Azure | Azure OpenAI |
+| AWS Bedrock | Amazon 托管模型 |
+| xAI | Grok 系列 |
+| Groq / Mistral / Cohere / Cerebras | 其他云端模型 |
+| GitHub Copilot | GitHub 模型服务 |
+| OpenRouter / Together AI / DeepInfra | 模型聚合平台 |
+| Gateway | 自定义网关 |
+
+每个模型都声明自己的**能力(Capabilities)**——推理(reasoning)、结构化输出(structuredOutput)、视觉(vision)——Agent 据此选择合适的模型。
+
+---
+
+## 图解说明
+
+让我们回到全景图,理解数据是如何流动的:
+
+1. **用户请求进入**:通过 CLI/Web/Desktop 等界面到达接口层
+2. **路由分发**:Hono 服务器将请求路由到对应的引擎模块
+3. **智能体编排**:Agent 创建或恢复 Session,选择 Provider 和模型
+4. **模型调用**:Provider 将请求发送到外部 LLM 服务
+5. **工具执行**:LLM 返回的工具调用请求经权限检查后执行
+6. **结果回写**:工具执行结果作为新的消息部件追加到会话中
+7. **持久化**:所有状态通过 Storage 持久化到 SQLite 数据库
+
+---
+
+## 与下一节的衔接
+
+这张全景图告诉了我们"有什么",但还没回答"它们之间是什么关系"。在下一节 [02-核心概念关系图谱](./02-核心概念关系图谱.md) 中,我们将深入每个核心概念之间的实体关系(Entity Relationship),弄清楚一个 Session 里有多少个 Message,一个 Message 里有多少种 Part,以及 Agent、Tool、Skill、Command 这四个容易混淆的概念到底有什么区别。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/02-\346\240\270\345\277\203\346\246\202\345\277\265\345\205\263\347\263\273\345\233\276\350\260\261.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/02-\346\240\270\345\277\203\346\246\202\345\277\265\345\205\263\347\263\273\345\233\276\350\260\261.md"
new file mode 100644
index 000000000000..0babd58d10d0
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/02-\346\240\270\345\277\203\346\246\202\345\277\265\345\205\263\347\263\273\345\233\276\350\260\261.md"
@@ -0,0 +1,404 @@
+# 核心概念关系图谱
+
+> 📌 一句话总结:OpenCode 的核心概念之间存在精确的一对多、多对多关系——理解这些关系,就理解了整个系统的骨架。
+> 🗺️ 本节在全景中的位置:上一节展示了六层架构的"全景图",本节深入到**概念层**,弄清楚每个核心实体的内部结构和相互关系。
+
+---
+
+## 实体关系图
+
+我们先用 ER 图展示 OpenCode 中最重要的数据实体及其关系。这些类型定义在 `packages/opencode/src/session/message-v2.ts` 和 `packages/opencode/src/session/index.ts` 中。
+
+```mermaid
+erDiagram
+ Project ||--o{ Session : "拥有"
+ Session ||--o{ Message : "包含"
+ Session ||--o| Session : "fork 自"
+ Session }o--|| Workspace : "属于"
+ Message ||--o{ Part : "由多个部件组成"
+ Message }o--|| Agent : "由 Agent 生成"
+ Message }o--|| Model : "使用模型"
+
+ Part ||--o| ToolCall : "ToolPart 包含"
+ Part ||--o| Snapshot : "SnapshotPart 引用"
+ Part ||--o| Patch : "PatchPart 包含"
+ Part ||--o| Subtask : "SubtaskPart 委派"
+
+ ToolCall }o--|| Tool : "调用"
+ Agent ||--o{ Tool : "可使用"
+ Agent }o--|| Permission_Ruleset : "受权限约束"
+ Agent }o--o| Model : "绑定模型"
+
+ Provider ||--o{ Model : "提供"
+ Config ||--o{ Provider : "配置"
+ Config ||--o{ MCP_Server : "配置"
+ MCP_Server ||--o{ Tool : "暴露"
+
+ Project {
+ string id PK
+ string path
+ }
+
+ Session {
+ string id PK
+ string slug
+ string projectID FK
+ string title
+ string version
+ datetime created
+ datetime updated
+ }
+
+ Message {
+ string id PK
+ string sessionID FK
+ string parentID FK "助手消息指向用户消息"
+ string role "user | assistant"
+ string agent
+ number cost
+ }
+
+ Part {
+ string id PK
+ string messageID FK
+ string type "text|reasoning|tool|file|snapshot|patch|subtask|step_start|step_finish|compaction|retry|agent"
+ }
+
+ Agent {
+ string name PK
+ string mode "primary | subagent"
+ boolean hidden
+ number temperature
+ }
+
+ Provider {
+ string id PK
+ string name
+ }
+
+ Model {
+ string id PK
+ string providerID FK
+ boolean reasoning
+ boolean vision
+ }
+
+ Tool {
+ string id PK
+ string description
+ }
+```
+
+### 关键关系解读
+
+| 关系 | 基数 | 说明 |
+|------|------|------|
+| Project → Session | 1:N | 一个项目下可以有多个会话 |
+| Session → Message | 1:N | 一个会话包含多条消息(用户消息和助手消息交替) |
+| Message → Part | 1:N | 一条消息由多个部件组成——这是 OpenCode 的核心设计 |
+| Agent → Tool | 1:N | 每个智能体根据权限规则集可使用不同的工具子集 |
+| Provider → Model | 1:N | 一个提供者(如 Anthropic)包含多个模型(如 claude-sonnet-4、claude-opus-4) |
+| Session → Session | 0:1 | 会话可以 fork 自另一个会话(`parentID`) |
+| ToolPart → Tool | N:1 | 一个工具部件对应一次工具调用 |
+
+---
+
+## 消息部件(Part)类型体系
+
+`Part` 是 OpenCode 消息系统中最精巧的设计。每种 Part 都是一个带有 `type` 判别字段的联合类型(Discriminated Union):
+
+```mermaid
+graph TD
+ Msg["Message 消息"]
+
+ Msg --> Text["TextPart
📝 文本输出"]
+ Msg --> Reasoning["ReasoningPart
🧠 推理过程"]
+ Msg --> ToolP["ToolPart
🔧 工具调用"]
+ Msg --> FileP["FilePart
📎 文件附件"]
+ Msg --> SnapP["SnapshotPart
📸 工作区快照"]
+ Msg --> PatchP["PatchPart
🩹 代码补丁"]
+ Msg --> SubP["SubtaskPart
👥 子任务委派"]
+ Msg --> StepS["StepStartPart
▶️ 步骤开始"]
+ Msg --> StepF["StepFinishPart
⏹️ 步骤结束"]
+ Msg --> Compact["CompactionPart
📦 上下文压缩"]
+ Msg --> RetryP["RetryPart
🔄 重试"]
+ Msg --> AgentP["AgentPart
🤖 智能体引用"]
+
+ ToolP --> State["状态机"]
+ State --> Pending["⏳ pending
输入已收到"]
+ Pending --> Running["🔄 running
正在执行"]
+ Running --> Completed["✅ completed
执行成功"]
+ Running --> Error["❌ error
执行失败"]
+
+ style Msg fill:#1565C0,color:#fff
+ style ToolP fill:#E65100,color:#fff
+ style State fill:#FFF3E0
+```
+
+ToolPart 内部包含一个**状态机(State Machine)**,定义在 `packages/opencode/src/session/message-v2.ts` 中:
+
+| 状态 | 包含的数据 | 说明 |
+|------|-----------|------|
+| `pending` | `input`(工具参数) | LLM 发出工具调用请求,但还未执行 |
+| `running` | `title`、`metadata` | 工具正在执行中,可实时更新元数据 |
+| `completed` | `output`、`attachments` | 执行成功,返回文本输出和可选的文件附件 |
+| `error` | `error`(错误信息) | 执行失败,包含错误原因 |
+
+---
+
+## 消息错误类型体系
+
+助手消息可以携带错误信息,OpenCode 定义了丰富的错误类型层次结构:
+
+```mermaid
+graph TD
+ E["Message Error 消息错误"]
+ E --> OL["OutputLengthError
输出超长"]
+ E --> AB["AbortedError
用户中断"]
+ E --> SO["StructuredOutputError
结构化输出格式错误"]
+ E --> AU["AuthError
认证失败"]
+ E --> API["APIError
API 调用错误"]
+ E --> CO["ContextOverflowError
上下文窗口溢出"]
+
+ API --> R["isRetryable: boolean
statusCode: number"]
+
+ style E fill:#c62828,color:#fff
+ style API fill:#e53935,color:#fff
+```
+
+---
+
+## 配置层级图
+
+配置系统(`packages/opencode/src/config/config.ts`)采用多层叠覆盖策略。理解这个层级对于调试配置问题至关重要:
+
+```mermaid
+graph TB
+ subgraph Remote["☁️ 远程层"]
+ WK["远程 .well-known/opencode
组织默认配置"]
+ AC["远程账户配置
已登录用户"]
+ MG["企业托管配置
Managed Config"]
+ end
+
+ subgraph Local["💻 本地层"]
+ GL["全局配置
~/.config/opencode/opencode.json"]
+ ENV1["OPENCODE_CONFIG 环境变量
指向配置文件路径"]
+ PJ["项目配置
./opencode.json 或 ./opencode.jsonc"]
+ DIR[".opencode/ 目录
项目级配置目录"]
+ ENV2["OPENCODE_CONFIG_CONTENT
环境变量内联配置"]
+ end
+
+ WK -->|"优先级 1(最低)"| GL
+ GL -->|"优先级 2"| ENV1
+ ENV1 -->|"优先级 3"| PJ
+ PJ -->|"优先级 4"| DIR
+ DIR -->|"优先级 5"| ENV2
+ ENV2 -->|"优先级 6"| AC
+ AC -->|"优先级 7"| MG
+
+ MG -->|"优先级 8(最高)"| Final["最终合并配置
Config.get()"]
+
+ style MG fill:#f44336,color:#fff
+ style Final fill:#4CAF50,color:#fff
+ style WK fill:#9E9E9E,color:#fff
+```
+
+### 配置内容结构
+
+配置文件中可以配置的核心内容(`Config.Info` 类型):
+
+```mermaid
+graph TD
+ Config["Config 配置"]
+
+ Config --> AgentCfg["agent
自定义智能体配置"]
+ Config --> ProvCfg["provider
模型提供者配置"]
+ Config --> MCPCfg["mcp
MCP 服务器配置"]
+ Config --> CmdCfg["command
自定义命令"]
+ Config --> PermCfg["permission
权限规则"]
+ Config --> SkillCfg["skills
技能路径 & URL"]
+ Config --> ServerCfg["server
端口 & 主机名"]
+ Config --> CompCfg["compaction
自动压缩策略"]
+ Config --> ShareCfg["share
会话分享设置"]
+ Config --> KeyCfg["keybinds
快捷键绑定"]
+ Config --> PluginCfg["plugin
插件列表"]
+
+ MCPCfg --> Local["McpLocal
type: local
command + args"]
+ MCPCfg --> Remote["McpRemote
type: remote
url + oauth"]
+
+ style Config fill:#1565C0,color:#fff
+```
+
+---
+
+## Agent / Tool / Skill / Command 关系辨析
+
+这四个概念是初学者最容易混淆的。我们用一张图来厘清它们的边界:
+
+```mermaid
+graph TB
+ subgraph AgentBox["🤖 Agent(智能体)— 决策者"]
+ AgentDesc["定义在 packages/opencode/src/agent/agent.ts
拥有:名称、模型绑定、权限规则、温度参数
职责:编排模型调用,决定何时使用工具
内置:build / plan / general / explore"]
+ end
+
+ subgraph ToolBox["🔧 Tool(工具)— 执行者"]
+ ToolDesc["定义在 packages/opencode/src/tool/tool.ts
拥有:ID、参数 Schema(Zod)、execute 函数
职责:执行具体操作(读文件、写文件、运行命令)
40+ 内置工具 + MCP 暴露的外部工具"]
+ end
+
+ subgraph SkillBox["📋 Skill(技能)— 知识库"]
+ SkillDesc["定义在 packages/opencode/src/skill/index.ts
存在形式:SKILL.md 文件(Markdown)
职责:为 Agent 提供领域知识和最佳实践
来源:项目目录 / ~/.claude/skills / 配置路径"]
+ end
+
+ subgraph CmdBox["⌨️ Command(命令)— 快捷入口"]
+ CmdDesc["定义在 packages/opencode/src/command/index.ts
拥有:名称、模板(template)、参数占位符
职责:用户可直接触发的预定义操作
来源:配置文件 / MCP prompts / Skill 自动生成"]
+ end
+
+ AgentBox -->|"调用"| ToolBox
+ AgentBox -->|"参考"| SkillBox
+ CmdBox -->|"触发"| AgentBox
+ SkillBox -->|"自动生成"| CmdBox
+ ToolBox -.->|"MCP 工具也注册为"| ToolBox
+
+ style AgentBox fill:#fff3e0
+ style ToolBox fill:#e8f5e9
+ style SkillBox fill:#e3f2fd
+ style CmdBox fill:#fce4ec
+```
+
+### 四者对比表
+
+| 维度 | Agent 智能体 | Tool 工具 | Skill 技能 | Command 命令 |
+|------|-------------|----------|-----------|-------------|
+| **是什么** | 决策编排者 | 具体执行者 | 知识文档 | 用户快捷方式 |
+| **定义方式** | 代码 + 配置 | `Tool.define()` | `SKILL.md` 文件 | 配置 / MCP / Skill |
+| **包含什么** | 模型绑定、权限、提示词 | Zod 参数、execute 函数 | Markdown 正文 | 模板字符串(`$1`, `$ARGUMENTS`) |
+| **谁调用它** | 用户 / 命令 | Agent | Agent(注入 system prompt) | 用户(快捷键 / 斜杠命令) |
+| **运行时行为** | 多轮循环调用模型 | 单次执行返回结果 | 注入到提示词中 | 展开模板后交给 Agent |
+| **可扩展性** | 配置自定义 Agent | 插件注册新工具 | 添加 SKILL.md 文件 | 配置 `command` 字段 |
+| **源码位置** | `src/agent/agent.ts` | `src/tool/*.ts` | `src/skill/index.ts` | `src/command/index.ts` |
+
+### Command 的三种来源
+
+```mermaid
+graph LR
+ subgraph Sources["Command 来源"]
+ ConfigCmd["配置文件
config.command"]
+ MCPPrompt["MCP Prompts
MCP 服务器的 prompt"]
+ SkillAuto["Skill 自动生成
每个 Skill 自动成为 Command"]
+ end
+
+ ConfigCmd --> Cmd["Command 命令"]
+ MCPPrompt --> Cmd
+ SkillAuto --> Cmd
+
+ Cmd --> Template["模板展开
$1, $2, $ARGUMENTS"]
+ Template --> Agent["交给 Agent 执行"]
+
+ style Cmd fill:#E91E63,color:#fff
+```
+
+内置命令(`packages/opencode/src/command/index.ts`):
+- **`init`**:初始化或更新项目的 `AGENTS.md` 文件
+- **`review`**:审查代码变更(支持 commit / branch / PR)
+
+---
+
+## Permission 权限模型
+
+权限系统(`packages/opencode/src/permission/`)控制着工具能做什么、不能做什么:
+
+```mermaid
+graph TD
+ subgraph Rule["权限规则 Permission.Ruleset"]
+ Allow["✅ allow
自动允许"]
+ Ask["🔔 ask
需要用户确认"]
+ Deny["❌ deny
自动拒绝"]
+ end
+
+ subgraph Request["权限请求 Permission.Request"]
+ ReqInfo["name: 权限名称
patterns: glob 模式
tool: 工具上下文"]
+ end
+
+ subgraph Reply["用户回复 Permission.Reply"]
+ Once["once
仅本次允许"]
+ Always["always
永久允许"]
+ Reject["reject
拒绝"]
+ end
+
+ Request --> Evaluate["Permission.evaluate()"]
+ Rule --> Evaluate
+ Evaluate -->|"匹配到 allow"| Pass["通过 ✅"]
+ Evaluate -->|"匹配到 deny"| Block["阻止 ❌"]
+ Evaluate -->|"匹配到 ask"| Prompt["弹出确认"]
+ Prompt --> Reply
+ Reply -->|"once"| Pass
+ Reply -->|"always"| SaveRule["保存规则到数据库"]
+ Reply -->|"reject"| Block
+
+ style Allow fill:#4CAF50,color:#fff
+ style Ask fill:#FF9800,color:#fff
+ style Deny fill:#f44336,color:#fff
+```
+
+每个 Agent 都绑定一个权限规则集。例如 `plan` 智能体的规则集只允许只读操作,而 `build` 智能体允许读写但某些危险操作需要确认。
+
+---
+
+## Provider-Model 关系
+
+```mermaid
+graph LR
+ subgraph Providers["Provider 提供者"]
+ Anthropic["Anthropic"]
+ OpenAI["OpenAI"]
+ Google["Google"]
+ Others["...18+ 家"]
+ end
+
+ subgraph Models["Model 模型"]
+ Sonnet["claude-sonnet-4
reasoning ✅
vision ✅"]
+ Opus["claude-opus-4
reasoning ✅
vision ✅"]
+ GPT4["gpt-4.1
vision ✅"]
+ Gemini["gemini-2.5-pro
reasoning ✅"]
+ end
+
+ subgraph Caps["Capabilities 能力"]
+ R["🧠 reasoning
推理"]
+ S["📊 structuredOutput
结构化输出"]
+ V["👁️ vision
视觉"]
+ end
+
+ Anthropic --> Sonnet
+ Anthropic --> Opus
+ OpenAI --> GPT4
+ Google --> Gemini
+
+ Sonnet --> R
+ Sonnet --> V
+ GPT4 --> V
+ Gemini --> R
+
+ style Anthropic fill:#1a1a2e,color:#fff
+ style OpenAI fill:#412991,color:#fff
+ style Google fill:#4285F4,color:#fff
+```
+
+每个模型都声明自己支持的能力(`capabilities`),Agent 可以据此选择合适的模型。例如需要"推理"能力时会优先选择支持 `reasoning` 的模型。
+
+---
+
+## 图解说明
+
+回顾本节的核心要点:
+
+1. **Session → Message → Part** 是三级嵌套结构,Part 的多态设计是 OpenCode 消息系统的灵魂
+2. **ToolPart** 内部是一个状态机(pending → running → completed/error),支持流式更新
+3. **Agent / Tool / Skill / Command** 四个概念职责分明:Agent 决策、Tool 执行、Skill 提供知识、Command 提供快捷入口
+4. **Permission** 是横切关注点,贯穿 Agent 和 Tool 之间的每一次交互
+5. **Config** 的 8 级优先级确保了从组织到个人的灵活配置
+
+---
+
+## 与下一节的衔接
+
+理解了核心概念之间的关系后,我们需要"退后一步"看看这些代码是如何组织在一起的。OpenCode 是一个包含 19 个包的 Monorepo(单体仓库),每个包承担不同的职责。在下一节 [03-Monorepo 全景:19 个包的关系](./03-Monorepo全景-19个包的关系.md) 中,我们将展开整个仓库的包依赖拓扑图。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/03-Monorepo\345\205\250\346\231\257-19\344\270\252\345\214\205\347\232\204\345\205\263\347\263\273.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/03-Monorepo\345\205\250\346\231\257-19\344\270\252\345\214\205\347\232\204\345\205\263\347\263\273.md"
new file mode 100644
index 000000000000..44f90b603dcc
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/03-Monorepo\345\205\250\346\231\257-19\344\270\252\345\214\205\347\232\204\345\205\263\347\263\273.md"
@@ -0,0 +1,441 @@
+# Monorepo 全景:19 个包的关系
+
+> 📌 一句话总结:OpenCode 采用 Monorepo(单体仓库)架构,19 个包按"基础层 → 核心层 → 界面层 → 集成层"自下而上依赖,使用 Turborepo 编排构建。
+> 🗺️ 本节在全景中的位置:前两节分别展示了架构分层和概念关系,本节聚焦于**代码组织**——这些模块是如何被拆分为独立的包并通过依赖关系连接在一起的。
+
+---
+
+## 全景图:包依赖拓扑
+
+下图展示了 OpenCode 全部 19 个包之间的 `workspace:*` 依赖关系。箭头方向为"依赖于":
+
+```mermaid
+graph BT
+ subgraph Foundation["🧱 基础层(Foundation)"]
+ SDK["@opencode-ai/sdk
packages/sdk/js/"]
+ Util["@opencode-ai/util
packages/util/"]
+ Script["@opencode-ai/script
packages/script/"]
+ Function["@opencode-ai/function
packages/function/"]
+ ConsoleMail["@opencode-ai/console-mail
packages/console/mail/"]
+ ConsoleRes["@opencode-ai/console-resource
packages/console/resource/"]
+ end
+
+ subgraph Core["⚙️ 核心层(Core)"]
+ Plugin["@opencode-ai/plugin
packages/plugin/"]
+ OpenCode["opencode
packages/opencode/"]
+ UI["@opencode-ai/ui
packages/ui/"]
+ ConsoleCore["@opencode-ai/console-core
packages/console/core/"]
+ end
+
+ subgraph Interface["🖥️ 界面层(Interface)"]
+ App["@opencode-ai/app
packages/app/"]
+ Web["@opencode-ai/web
packages/web/"]
+ Enterprise["@opencode-ai/enterprise
packages/enterprise/"]
+ Storybook["@opencode-ai/storybook
packages/storybook/"]
+ ConsoleApp["@opencode-ai/console-app
packages/console/app/"]
+ ConsoleFn["@opencode-ai/console-function
packages/console/function/"]
+ end
+
+ subgraph Endpoint["🚀 终端层(Endpoint)"]
+ Desktop["@opencode-ai/desktop
packages/desktop/"]
+ Electron["@opencode-ai/desktop-electron
packages/desktop-electron/"]
+ Slack["@opencode-ai/slack
packages/slack/"]
+ end
+
+ %% 基础层 → 核心层
+ Plugin -->|"workspace:*"| SDK
+ OpenCode -->|"workspace:*"| SDK
+ OpenCode -->|"workspace:*"| Util
+ OpenCode -->|"workspace:*"| Plugin
+ OpenCode -->|"workspace:*"| Script
+ UI -->|"workspace:*"| SDK
+ UI -->|"workspace:*"| Util
+ ConsoleCore -->|"workspace:*"| ConsoleMail
+ ConsoleCore -->|"workspace:*"| ConsoleRes
+
+ %% 核心层 → 界面层
+ App -->|"workspace:*"| SDK
+ App -->|"workspace:*"| UI
+ App -->|"workspace:*"| Util
+ Web -->|"workspace:*"| OpenCode
+ Enterprise -->|"workspace:*"| UI
+ Enterprise -->|"workspace:*"| Util
+ Storybook -->|"workspace:*"| UI
+ ConsoleApp -->|"workspace:*"| UI
+ ConsoleApp -->|"workspace:*"| ConsoleCore
+ ConsoleApp -->|"workspace:*"| ConsoleMail
+ ConsoleApp -->|"workspace:*"| ConsoleRes
+ ConsoleFn -->|"workspace:*"| ConsoleCore
+ ConsoleFn -->|"workspace:*"| ConsoleRes
+
+ %% 界面层 → 终端层
+ Desktop -->|"workspace:*"| App
+ Desktop -->|"workspace:*"| UI
+ Electron -->|"workspace:*"| App
+ Electron -->|"workspace:*"| UI
+ Slack -->|"workspace:*"| SDK
+
+ style SDK fill:#1565C0,color:#fff
+ style OpenCode fill:#E65100,color:#fff
+ style UI fill:#2E7D32,color:#fff
+ style App fill:#6A1B9A,color:#fff
+```
+
+> 💡 **阅读技巧**:图中越靠下的包越"基础",被依赖的次数越多。`@opencode-ai/sdk` 是被依赖最多的包——它是整个生态的"通用语言"。
+
+---
+
+## 仓库构建工具
+
+OpenCode 使用 [Turborepo](https://turbo.build/)(`turbo.json` 配置)来编排多包构建:
+
+```mermaid
+graph LR
+ subgraph Pipeline["Turbo 构建管线"]
+ TC["typecheck
类型检查"]
+ Build["build
构建"]
+ Dev["dev
开发模式"]
+ Test["test
测试"]
+ end
+
+ TC -->|"dependsOn: [^typecheck]"| Build
+ Build -->|"dependsOn: [^build]"| Dev
+
+ style TC fill:#00897B,color:#fff
+ style Build fill:#F57C00,color:#fff
+```
+
+- **包管理器(Package Manager)**:Bun 1.3.11
+- **工作区协议(Workspace Protocol)**:`workspace:*` 链接内部依赖
+- **构建编排**:Turborepo 自动推导构建顺序,只重建变更的包
+
+---
+
+## 包分类矩阵
+
+我们按**角色**和**面向的用户**对 19 个包进行分类:
+
+| 分类 | 包名 | 路径 | 一句话描述 | 面向 |
+|------|------|------|-----------|------|
+| **基础/SDK** | `@opencode-ai/sdk` | `packages/sdk/js/` | TypeScript SDK,定义与 OpenCode 服务器交互的客户端类型和方法 | 开发者 |
+| **基础/工具** | `@opencode-ai/util` | `packages/util/` | 共享工具函数库 | 内部 |
+| **基础/脚本** | `@opencode-ai/script` | `packages/script/` | 构建和发布脚本 | 维护者 |
+| **基础/插件** | `@opencode-ai/plugin` | `packages/plugin/` | 插件系统的公共接口,定义插件如何注册工具和扩展功能 | 插件开发者 |
+| **基础/函数** | `@opencode-ai/function` | `packages/function/` | 云函数(Serverless Function)定义 | 内部 |
+| **核心引擎** | `opencode` | `packages/opencode/` | **核心包**——包含 Agent、Session、Provider、Tool、MCP 等全部核心逻辑 | 终端用户 |
+| **界面/组件库** | `@opencode-ai/ui` | `packages/ui/` | 基于 React 的 UI 组件库,使用 Tailwind CSS | 前端开发者 |
+| **界面/Web 应用** | `@opencode-ai/app` | `packages/app/` | Web 前端应用,对接 SDK 提供图形化交互界面 | 终端用户 |
+| **界面/官网** | `@opencode-ai/web` | `packages/web/` | 基于 Astro 的官方网站 | 访客 |
+| **界面/企业版** | `@opencode-ai/enterprise` | `packages/enterprise/` | 企业版管理界面 | 企业管理员 |
+| **界面/Storybook** | `@opencode-ai/storybook` | `packages/storybook/` | UI 组件的可视化文档和测试环境 | 前端开发者 |
+| **终端/桌面 Tauri** | `@opencode-ai/desktop` | `packages/desktop/` | 基于 Tauri 的桌面应用(轻量级) | 终端用户 |
+| **终端/桌面 Electron** | `@opencode-ai/desktop-electron` | `packages/desktop-electron/` | 基于 Electron 的桌面应用(跨平台兼容) | 终端用户 |
+| **集成/Slack** | `@opencode-ai/slack` | `packages/slack/` | Slack 机器人集成,在 Slack 中使用 OpenCode | 团队 |
+| **控制台/应用** | `@opencode-ai/console-app` | `packages/console/app/` | 管理控制台前端 | 运维 |
+| **控制台/核心** | `@opencode-ai/console-core` | `packages/console/core/` | 管理控制台后端逻辑、数据库 | 内部 |
+| **控制台/函数** | `@opencode-ai/console-function` | `packages/console/function/` | 管理控制台的无服务器函数 | 内部 |
+| **控制台/邮件** | `@opencode-ai/console-mail` | `packages/console/mail/` | 邮件发送服务 | 内部 |
+| **控制台/资源** | `@opencode-ai/console-resource` | `packages/console/resource/` | 基础设施资源定义(SST / IaC) | 内部 |
+
+> 📝 另外还有两个仓库内的独立项目(不在 `packages/` 工作区内):
+> - `github/` — GitHub Actions 集成,依赖 `@opencode-ai/sdk`
+> - `sdks/vscode/` — VS Code 扩展
+
+---
+
+## 每个包的详细说明
+
+### 🧱 基础层
+
+#### `@opencode-ai/sdk`(SDK)
+
+```
+packages/sdk/js/
+```
+
+这是整个生态的**通用语言**。它定义了客户端与 OpenCode 服务器之间的类型契约:Session、Message、Part、Agent、Provider 等。所有需要与 OpenCode 交互的包都依赖它。
+
+**被依赖次数**:6 次(最高)
+
+#### `@opencode-ai/util`(工具库)
+
+```
+packages/util/
+```
+
+共享的工具函数集合,提供跨包复用的通用逻辑。被 `opencode` 核心包、`ui` 组件库和其他界面包使用。
+
+#### `@opencode-ai/plugin`(插件接口)
+
+```
+packages/plugin/
+```
+
+定义插件的公共 API。插件开发者通过这个包的接口来注册自定义工具、扩展 Agent 能力。依赖 `@opencode-ai/sdk` 获取类型定义。
+
+#### `@opencode-ai/script`(构建脚本)
+
+```
+packages/script/
+```
+
+内部构建和发布脚本,不对外发布。
+
+#### `@opencode-ai/function`(云函数)
+
+```
+packages/function/
+```
+
+定义 Serverless 函数的入口和逻辑,无内部依赖。
+
+---
+
+### ⚙️ 核心层
+
+#### `opencode`(核心引擎)
+
+```
+packages/opencode/
+```
+
+这是整个项目的**心脏**,包含所有核心逻辑:
+
+| 子模块 | 路径 | 职责 |
+|--------|------|------|
+| Agent | `src/agent/` | 智能体定义与编排 |
+| Session | `src/session/` | 会话和消息管理 |
+| Provider | `src/provider/` | 模型提供者抽象 |
+| Tool | `src/tool/` | 40+ 内置工具 |
+| Skill | `src/skill/` | SKILL.md 技能系统 |
+| MCP | `src/mcp/` | MCP 协议客户端 |
+| LSP | `src/lsp/` | 语言服务器集成 |
+| Permission | `src/permission/` | 权限控制 |
+| Config | `src/config/` | 配置管理 |
+| Storage | `src/storage/` | SQLite 持久化 |
+| Server | `src/server/` | Hono HTTP 服务 |
+| Git | `src/git/` | Git 操作 |
+| Snapshot | `src/snapshot/` | 文件快照 |
+| File | `src/file/` | 文件操作 |
+| Auth | `src/auth/` | 认证管理 |
+| Command | `src/command/` | 命令系统 |
+| Plugin | `src/plugin/` | 插件加载 |
+
+依赖:`@opencode-ai/sdk`、`@opencode-ai/util`、`@opencode-ai/plugin`、`@opencode-ai/script`
+
+#### `@opencode-ai/ui`(UI 组件库)
+
+```
+packages/ui/
+```
+
+基于 React + Tailwind CSS 的 UI 组件库。提供主题、布局、表单、对话框等通用组件,被所有前端应用共享。
+
+依赖:`@opencode-ai/sdk`、`@opencode-ai/util`
+
+---
+
+### 🖥️ 界面层
+
+#### `@opencode-ai/app`(Web 前端应用)
+
+```
+packages/app/
+```
+
+主要的 Web 前端应用,提供图形化的聊天界面、会话管理、设置等功能。
+
+依赖:`@opencode-ai/sdk`、`@opencode-ai/ui`、`@opencode-ai/util`
+
+#### `@opencode-ai/web`(官方网站)
+
+```
+packages/web/
+```
+
+基于 [Astro](https://astro.build/) 构建的官方网站和文档。直接依赖 `opencode` 核心包。
+
+#### `@opencode-ai/enterprise`(企业版)
+
+```
+packages/enterprise/
+```
+
+企业版管理界面,提供团队管理、使用统计等功能。
+
+依赖:`@opencode-ai/ui`、`@opencode-ai/util`
+
+#### `@opencode-ai/storybook`(组件文档)
+
+```
+packages/storybook/
+```
+
+UI 组件库的可视化展示与测试环境,帮助前端开发者查看和调试组件。
+
+依赖:`@opencode-ai/ui`
+
+---
+
+### 🚀 终端层
+
+#### `@opencode-ai/desktop`(Tauri 桌面应用)
+
+```
+packages/desktop/
+```
+
+基于 [Tauri](https://tauri.app/) 的轻量级桌面应用,利用系统 WebView 渲染,安装包体积小。
+
+依赖:`@opencode-ai/app`、`@opencode-ai/ui`
+
+#### `@opencode-ai/desktop-electron`(Electron 桌面应用)
+
+```
+packages/desktop-electron/
+```
+
+基于 [Electron](https://www.electronjs.org/) 的桌面应用,跨平台兼容性更好。
+
+依赖:`@opencode-ai/app`、`@opencode-ai/ui`
+
+#### `@opencode-ai/slack`(Slack 集成)
+
+```
+packages/slack/
+```
+
+Slack 机器人,让团队可以在 Slack 频道中直接使用 OpenCode。
+
+依赖:`@opencode-ai/sdk`
+
+---
+
+### 🎛️ 控制台子系统
+
+控制台(Console)是 OpenCode 的后台管理平台,自身也是一个小型分层架构:
+
+```mermaid
+graph BT
+ ConsoleMail["console-mail
邮件服务"] --> ConsoleCore
+ ConsoleRes["console-resource
基础设施资源"] --> ConsoleCore
+ ConsoleCore["console-core
核心逻辑 & 数据库"]
+ ConsoleCore --> ConsoleApp["console-app
管理界面"]
+ ConsoleCore --> ConsoleFn["console-function
云函数"]
+ UI2["@opencode-ai/ui"] --> ConsoleApp
+
+ style ConsoleCore fill:#37474F,color:#fff
+ style ConsoleApp fill:#546E7A,color:#fff
+```
+
+---
+
+## 依赖被引用排行
+
+哪个包被依赖最多?这反映了它在架构中的**基础程度**:
+
+| 排名 | 包名 | 被依赖次数 | 角色 |
+|------|------|-----------|------|
+| 1 | `@opencode-ai/sdk` | 6 | 类型契约,生态通用语言 |
+| 2 | `@opencode-ai/ui` | 5 | UI 组件库,所有前端共享 |
+| 3 | `@opencode-ai/util` | 4 | 工具函数,内部共享 |
+| 4 | `@opencode-ai/console-resource` | 3 | 基础设施资源定义 |
+| 5 | `@opencode-ai/app` | 2 | Web 前端,桌面应用复用 |
+| 5 | `@opencode-ai/console-core` | 2 | 控制台核心逻辑 |
+| 5 | `@opencode-ai/console-mail` | 2 | 邮件服务 |
+| 8 | `@opencode-ai/plugin` | 1 | 插件接口 |
+
+---
+
+## 图解说明
+
+让我们用一个更直观的视角来理解这 19 个包的关系——把它们想象成一座建筑:
+
+```mermaid
+graph TD
+ subgraph Roof["🏠 屋顶:终端产品"]
+ Desktop
+ Electron
+ Slack
+ GH["GitHub Action"]
+ end
+
+ subgraph Floor2["🏢 二楼:用户界面"]
+ App
+ Web
+ Enterprise
+ ConsoleApp["Console App"]
+ end
+
+ subgraph Floor1["🏗️ 一楼:核心引擎"]
+ OpenCode["opencode 核心包"]
+ UI["UI 组件库"]
+ ConsoleCore["Console Core"]
+ end
+
+ subgraph Base["🧱 地基:基础设施"]
+ SDK
+ Util
+ Plugin
+ Script
+ end
+
+ Roof --> Floor2
+ Floor2 --> Floor1
+ Floor1 --> Base
+
+ style Base fill:#795548,color:#fff
+ style Floor1 fill:#FF9800,color:#fff
+ style Floor2 fill:#2196F3,color:#fff
+ style Roof fill:#4CAF50,color:#fff
+```
+
+**读图规则**:
+- **地基层**的包不依赖其他内部包,它们是"纯基础设施"
+- **一楼**是核心逻辑,依赖地基但不关心谁在使用自己
+- **二楼**是面向用户的界面,组合核心逻辑和 UI 组件
+- **屋顶**是最终交付给用户的产品形态
+
+---
+
+## 关键设计决策
+
+### 1. 为什么用 Monorepo?
+
+- **代码共享**:`sdk`、`util`、`ui` 等包被多个应用复用,Monorepo 确保版本一致性
+- **原子提交**:跨包的功能变更可以在一个 PR 中完成
+- **统一工具链**:Turborepo + Bun 一套构建工具管理所有包
+
+### 2. 为什么 SDK 是独立包?
+
+`@opencode-ai/sdk` 独立出来而不是放在 `opencode` 核心包中,因为:
+- 外部集成(Slack Bot、GitHub Action、VS Code 插件)只需要类型定义和 API 客户端,不需要整个核心引擎
+- SDK 可以独立发布到 npm,方便第三方开发者使用
+
+### 3. 为什么有两个桌面应用框架?
+
+- **Tauri**(`packages/desktop/`):体积小、性能好,但依赖系统 WebView
+- **Electron**(`packages/desktop-electron/`):体积大,但自带 Chromium,跨平台兼容性更稳定
+
+两者共享同一个 `@opencode-ai/app` 前端代码,只是"壳"不同。
+
+### 4. Console 子系统为什么独立分包?
+
+管理控制台(Console)是 SaaS 平台的后台,与核心编程助手功能正交:
+- `console-resource`:使用 [SST](https://sst.dev/) 定义云基础设施
+- `console-core`:后台业务逻辑和数据库
+- `console-app`:管理界面前端
+- `console-function`:事件处理的无服务器函数
+- `console-mail`:邮件通知服务
+
+---
+
+## 与下一节的衔接
+
+到这里,我们已经从三个维度理解了 OpenCode:
+
+1. **架构分层**(01-一图看懂 OpenCode):六层架构,从用户到基础设施
+2. **概念关系**(02-核心概念关系图谱):Session、Message、Part 等实体的关系
+3. **代码组织**(本节):19 个包的依赖拓扑和职责划分
+
+接下来,我们将进入 [02-快速上手](../02-快速上手/) 章节,动手安装和运行 OpenCode——亲身体验这张全景图中的每一个部分。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/04-\344\270\200\346\254\241\345\257\271\350\257\235\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/04-\344\270\200\346\254\241\345\257\271\350\257\235\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md"
new file mode 100644
index 000000000000..bb1082b75582
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/04-\344\270\200\346\254\241\345\257\271\350\257\235\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md"
@@ -0,0 +1,335 @@
+# 一次对话的完整旅程
+
+> 📌 一句话总结:从用户在终端输入一句话,到 AI 读取文件、调用工具、修改代码、返回结果——我们将跟踪每一个字节穿越系统的完整路径。
+> 🗺️ 本节在全景中的位置:前三节分别从宏观架构、概念关系、包组织三个维度描绘了 OpenCode 的全貌。本节开始**进入运行时**——用一个真实场景串联所有模块,让你看到代码是如何"活起来"的。
+
+---
+
+## 场景设定
+
+小明打开终端,输入 `opencode`,然后说:
+
+> "帮我把 utils.ts 里的 forEach 改成 for...of"
+
+让我们追踪这句话从键盘到文件系统的完整旅程。
+
+---
+
+## 全景图:完整对话时序
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant User as 👤 小明
+ participant TUI as 🖥️ TUI 终端
cli/cmd/tui/
+ participant Server as 🌐 HTTP Server
server/server.ts
+ participant Route as 📡 Session Route
server/routes/session.ts
+ participant Prompt as 🔄 Chat Loop
session/prompt.ts
+ participant Proc as ⚙️ Processor
session/processor.ts
+ participant LLM as 🤖 LLM Stream
session/llm.ts
+ participant Provider as 🏭 Provider
provider/provider.ts
+ participant AI as ☁️ LLM API
(Claude/GPT/Gemini)
+ participant Tool as 🔧 Tool System
tool/
+ participant FS as 💾 文件系统
+
+ User->>TUI: 输入 "帮我把 utils.ts 里的 forEach 改成 for...of"
+ Note over TUI: prompt/index.tsx
构建 PromptInput
+
+ TUI->>Server: POST /session/:id/message
{parts: [{type:"text", text:"..."}]}
+ Note over Server: server.ts 中间件链
Auth → CORS → WorkspaceContext
+
+ Server->>Route: 路由匹配 → session.prompt
+ Note over Route: routes/session.ts:783-822
validator 校验参数
+
+ Route->>Prompt: SessionPrompt.prompt({sessionID, parts, agent, model})
+ Note over Prompt: prompt.ts:162
创建 UserMessage
+
+ Prompt->>Prompt: loop({sessionID}) 进入主循环
+ Note over Prompt: prompt.ts:278
while(true) 外层编排循环
+
+ Prompt->>Proc: processor.process(streamInput)
+ Note over Proc: processor.ts:46
while(true) 内层重试循环
+
+ Proc->>LLM: LLM.stream({user, agent, system, messages, tools})
+ Note over LLM: llm.ts:48
构建 system prompt + 解析 tools
+
+ LLM->>Provider: Provider.getLanguage(model)
+ Note over Provider: provider.ts
缓存 SDK → 创建 LanguageModelV2
+
+ Provider->>LLM: 返回 languageModel 实例
+
+ LLM->>AI: streamText({model, system, messages, tools})
+ Note over AI: AI SDK → 流式响应
+
+ AI-->>Proc: stream.fullStream 事件流
text-delta / tool-call / finish
+
+ Note over Proc: 💡 关键点:Agent 决定调用 read 工具
+
+ Proc->>Tool: execute read({filePath: "utils.ts"})
+ Note over Tool: tool/read.ts
权限检查 → 读取文件
+
+ Tool->>FS: Filesystem.readText("utils.ts")
+ FS-->>Tool: 文件内容
+ Tool-->>Proc: {output: "文件内容...", title: "utils.ts"}
+
+ Note over Proc: tool-result 事件 → 更新 ToolPart
+
+ Proc-->>LLM: 工具结果回传(下一轮流式)
+
+ LLM->>AI: 再次调用 LLM(包含工具结果)
+ AI-->>Proc: tool-call: edit({filePath, oldString, newString})
+
+ Note over Proc: 💡 关键点:Agent 决定调用 edit 工具
+
+ Proc->>Tool: execute edit({filePath: "utils.ts", oldString: "forEach", newString: "for...of"})
+ Note over Tool: tool/edit.ts
权限检查 → 匹配替换 → 写入文件
+
+ Tool->>FS: Filesystem.write("utils.ts", newContent)
+ FS-->>Tool: 写入成功
+ Tool-->>Proc: {output: "Edit applied.", diff: "...", title: "utils.ts"}
+
+ Proc-->>LLM: 工具结果回传
+
+ LLM->>AI: 最终调用 LLM
+ AI-->>Proc: text-delta: "已完成修改..."
finish: stop
+
+ Note over Proc: finish-step → 计算 token/cost
创建 snapshot patch
+
+ Proc-->>Prompt: return "continue"(无更多工具调用则结束)
+
+ Prompt->>Prompt: 检查 finish reason → 非 tool-calls → break
+
+ Prompt-->>Route: 返回最终 AssistantMessage
+ Route-->>TUI: stream.write(JSON.stringify(msg))
+ TUI-->>User: 显示 "已完成修改..." + diff 预览
+```
+
+---
+
+## 数据流转图:每个阶段的数据格式
+
+```mermaid
+flowchart LR
+ subgraph Input["📝 用户输入"]
+ A["纯文本字符串
'帮我把 utils.ts 里的
forEach 改成 for...of'"]
+ end
+
+ subgraph SDK["📦 SDK 封装"]
+ B["PromptInput
{sessionID, agent,
model, variant,
parts: [{type:'text',
text:'...'}]}"]
+ end
+
+ subgraph HTTP["🌐 HTTP 请求"]
+ C["POST JSON Body
{parts, agent,
model:{providerID,
modelID}}"]
+ end
+
+ subgraph UserMsg["💬 UserMessage"]
+ D["MessageV2.User
{id, sessionID,
role:'user',
system:[], tools:{}}"]
+ end
+
+ subgraph ModelMsg["🔄 模型消息"]
+ E["ModelMessage[]
[{role:'system',
content:'...'},
{role:'user',
content:'...'}]"]
+ end
+
+ subgraph Stream["⚡ 流式事件"]
+ F["StreamEvent
text-delta |
tool-call |
tool-result |
finish"]
+ end
+
+ subgraph Parts["🧩 消息部件"]
+ G["MessageV2.Part
TextPart |
ToolPart |
ReasoningPart"]
+ end
+
+ subgraph Result["✅ 最终结果"]
+ H["AssistantMessage
{parts, finish,
cost, tokens,
time}"]
+ end
+
+ A --> B --> C --> D --> E --> F --> G --> H
+
+ style Input fill:#e1f5fe
+ style SDK fill:#f3e5f5
+ style HTTP fill:#fff3e0
+ style UserMsg fill:#e8f5e9
+ style ModelMsg fill:#fce4ec
+ style Stream fill:#fff9c4
+ style Parts fill:#f1f8e9
+ style Result fill:#e0f2f1
+```
+
+### 💡 关键点:数据格式在每一步都在变化
+
+| 阶段 | 数据格式 | 对应文件 |
+|------|---------|---------|
+| 用户输入 | 纯文本字符串 | `cli/cmd/tui/component/prompt/index.tsx` |
+| SDK 封装 | `PromptInput`(含 parts 数组、model 信息) | `server/routes/session.ts` |
+| 创建用户消息 | `MessageV2.User`(含 system、tools 覆盖) | `session/prompt.ts` |
+| 转换为模型消息 | `ModelMessage[]`(system + user + assistant 历史) | `session/prompt.ts` → `MessageV2.toModelMessages()` |
+| Provider 归一化 | 经 `ProviderTransform.message()` 处理的消息 | `provider/transform.ts` |
+| 流式事件 | `StreamEvent`(text-delta、tool-call 等) | `session/processor.ts` |
+| 持久化部件 | `MessageV2.Part`(TextPart / ToolPart / ReasoningPart) | `session/index.ts` |
+| 最终响应 | `AssistantMessage`(含 cost、tokens、finish) | `session/prompt.ts` |
+
+---
+
+## Agent 决策循环:ReAct 模式状态机
+
+OpenCode 的 Agent 采用经典的 ReAct(Reasoning + Acting)模式。整个过程由**两层循环**驱动:
+
+```mermaid
+stateDiagram-v2
+ [*] --> Init: loop() 启动
+
+ state "外层循环 (prompt.ts)" as Outer {
+ Init --> Resolve: 解析 model/agent/tools
+ Resolve --> BuildSystem: 构建 system prompt
+ BuildSystem --> Process: processor.process()
+
+ state "内层循环 (processor.ts)" as Inner {
+ state "🧠 Think" as Think
+ state "🔧 Act" as Act
+ state "👁️ Observe" as Observe
+ state "✍️ Respond" as Respond
+ state "🔁 Retry" as Retry
+
+ Think: LLM 推理
分析用户需求
决定下一步行动
+ Act: 调用工具
read/edit/bash/grep...
+ Observe: 接收工具结果
更新 ToolPart 状态
+ Respond: 生成文本回复
text-delta 事件流
+
+ [*] --> Think: LLM.stream()
+ Think --> Act: tool-call 事件
+ Think --> Respond: text-delta 事件(无工具调用)
+ Act --> Observe: tool-result / tool-error
+ Observe --> Think: 结果回传 LLM
+ Respond --> [*]: finish 事件
+ Think --> Retry: 可重试错误
+ Retry --> Think: 延迟后重试
+ }
+
+ Process --> CheckResult: 返回结果
+ }
+
+ state CheckResult <>
+ CheckResult --> Outer: "continue"
还有工具要调用
+ CheckResult --> Compact: "compact"
上下文溢出
+ CheckResult --> Done: "stop"
完成或被阻止
+
+ Compact --> Outer: 压缩后继续
+ Done --> [*]: 对话结束
+```
+
+### 💡 关键点:双层循环的设计智慧
+
+**外层循环**(`prompt.ts:278` — `while(true)`)负责**编排**:
+- 每轮重新解析可用工具(因为权限可能变化)
+- 重新构建 system prompt(因为环境可能变化)
+- 处理上下文压缩(`SessionCompaction`)
+- 当 Agent 完成推理(finish reason 非 `tool-calls`)时退出
+
+**内层循环**(`processor.ts:46` — `while(true)`)负责**执行**:
+- 调用 `LLM.stream()` 获取流式事件
+- 处理每个事件(文本、工具调用、推理)
+- 处理重试逻辑(`SessionRetry.retryable(error)`)
+- 检测死循环(Doom Loop Detection)——连续 3 次相同工具+相同参数
+
+---
+
+## 图解说明:关键步骤详解
+
+### 第 1 步:TUI 捕获输入
+
+```
+文件:cli/cmd/tui/component/prompt/index.tsx
+```
+
+TUI 使用 Ink(React for CLI)渲染终端界面。当小明按下 Enter,`prompt/index.tsx` 将输入文本封装为 `PromptInput`,通过 SDK client 发送 HTTP 请求到本地 Server:
+
+```typescript
+sdk.client.session.prompt({
+ sessionID,
+ agent: local.agent.current().name, // "build"
+ model: selectedModel, // {providerID, modelID}
+ parts: [{ id, type: "text", text: inputText }],
+})
+```
+
+### 第 2 步:Server 中间件链
+
+```
+文件:server/server.ts
+```
+
+HTTP 请求经过中间件链处理:
+1. **错误处理** — 捕获 `NamedError` 映射到 HTTP 状态码
+2. **Basic Auth** — 如果设置了 `OPENCODE_SERVER_PASSWORD`
+3. **请求日志** — 记录所有请求(跳过 `/log` 端点避免递归)
+4. **CORS** — 允许 `localhost:*`、`tauri://`、`*.opencode.ai`
+5. **WorkspaceContext** — 从 header/query 提取 `workspaceID`
+6. **Instance Bootstrap** — 初始化项目上下文
+
+### 第 3 步:Session Route 分发
+
+```
+文件:server/routes/session.ts:783-822
+```
+
+路由 `POST /:sessionID/message` 使用 Zod 校验参数后,调用 `SessionPrompt.prompt()`。该函数创建 `MessageV2.User`,然后启动主循环。
+
+### 第 4-5 步:模型解析与 Provider 初始化
+
+```
+文件:provider/provider.ts → provider/transform.ts
+```
+
+`Provider.getLanguage(model)` 的解析过程:
+1. 检查缓存(key = `providerID/modelID`)
+2. 获取 Provider SDK(如 `@ai-sdk/anthropic` → `createAnthropic()`)
+3. 通过 Custom Loader 或默认方式加载模型实例
+4. `ProviderTransform.message()` 处理 Provider 特异性(Anthropic 空内容过滤、Mistral 工具 ID 标准化等)
+
+### 第 6-9 步:ReAct 循环
+
+这是整个系统的核心。Agent 通常会:
+1. **Think**:分析用户需求,决定先 `read` 文件
+2. **Act**:调用 `read` 工具获取 `utils.ts` 内容
+3. **Observe**:接收文件内容,理解需要修改的位置
+4. **Think**:决定使用 `edit` 工具进行精确替换
+5. **Act**:调用 `edit({filePath, oldString, newString})`
+6. **Observe**:收到 "Edit applied successfully" + LSP 诊断
+7. **Respond**:生成总结文本回复用户
+
+---
+
+## 关键设计决策
+
+### 1. 为什么用 HTTP Server 而不是直接调用?
+
+```
+TUI ←HTTP→ Server ←→ 核心逻辑
+```
+
+💡 **解耦前端与后端**。这使得 Web UI、Desktop App、IDE 插件都能复用同一套核心逻辑。Server 是唯一的入口点,所有客户端通过相同的 API 交互。
+
+### 2. 为什么有两层循环?
+
+💡 **外层管编排,内层管执行**。外层循环在每轮重新解析工具和提示词,使系统能适应动态变化(如用户中途修改权限)。内层循环专注于一次 LLM 调用的流式处理和重试逻辑。
+
+### 3. 为什么 Agent 不一次性完成所有修改?
+
+💡 **ReAct 模式的渐进式推理**。Agent 先读取文件理解上下文,再决定如何修改。每一步都有完整的观察 → 推理 → 行动闭环,这比"一次生成所有修改"更可靠。
+
+### 4. 流式事件的价值
+
+💡 **实时反馈**。`text-delta` 让用户在 Agent 思考时就能看到部分输出,`tool-call` 状态更新让用户知道 Agent 正在执行什么工具。这一切通过 `Bus.publish()` 和 `SyncEvent` 实现。
+
+### 5. 死循环检测(Doom Loop Detection)
+
+```
+文件:session/processor.ts — tool-call 事件处理
+```
+
+💡 如果最近 3 个工具调用使用了**相同工具 + 相同参数**,Processor 会触发 `Permission.ask("doom_loop")`,让用户决定是否继续。这防止了 Agent 陷入无限重试。
+
+---
+
+## 与下一节的衔接
+
+我们已经看到对话旅程中 Agent 决定调用工具的那一刻(时序图中的 `tool-call` 事件)。但工具调用本身还有一个完整的生命周期——从权限检查、到工具发现、到参数校验、到执行、到结果格式化。下一节 **"一次 Tool 调用的完整旅程"** 将深入这个过程。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/05-\344\270\200\346\254\241Tool\350\260\203\347\224\250\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/05-\344\270\200\346\254\241Tool\350\260\203\347\224\250\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md"
new file mode 100644
index 000000000000..447037dfb97c
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/05-\344\270\200\346\254\241Tool\350\260\203\347\224\250\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md"
@@ -0,0 +1,324 @@
+# 一次 Tool 调用的完整旅程
+
+> 📌 一句话总结:从 Agent 说"我要调用 bash 工具"到结果返回,中间经历了注册发现、权限评估、参数校验、执行隔离、输出截断六道关卡。
+> 🗺️ 本节在全景中的位置:上一节追踪了完整对话流程,在 Agent 决定"调用工具"的那一刻停下。本节接过接力棒,放大工具调用的完整生命周期。
+
+---
+
+## 全景图:工具调用时序
+
+当 Processor 收到 LLM 返回的 `tool-call` 事件时,以下流程启动:
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant LLM as 🤖 LLM 响应
session/processor.ts
+ participant Proc as ⚙️ Processor
tool-call 事件处理
+ participant Registry as 📋 ToolRegistry
tool/registry.ts
+ participant Define as 🏗️ Tool.define
tool/tool.ts
+ participant Perm as 🔐 Permission
permission/index.ts
+ participant Eval as 📏 Evaluate
permission/evaluate.ts
+ participant User as 👤 用户
(TUI 弹窗)
+ participant Exec as 🔧 Tool Execute
tool/bash.ts 等
+ participant FS as 💾 外部系统
文件/Shell/网络
+
+ LLM->>Proc: tool-call 事件
{toolName:"bash", args:{command:"cat utils.ts"}}
+ Note over Proc: processor.ts
创建 ToolPart
status: "pending" → "running"
+
+ Proc->>Registry: 查找工具定义
+ Note over Registry: registry.ts
内置工具 + 自定义工具 + MCP 工具
+
+ Registry->>Define: tool.init({agent})
+ Note over Define: tool.ts:49-89
返回 {description, parameters, execute}
+
+ Define->>Define: Zod 参数校验
parameters.safeParse(args)
+ Note over Define: 💡 关键点:校验失败会
调用 formatValidationError()
+
+ Define->>Exec: execute(validatedArgs, ctx)
+
+ Note over Exec: === 进入具体工具逻辑 ===
+
+ Exec->>Perm: ctx.ask({permission:"bash", patterns:[command]})
+ Note over Perm: permission/index.ts:166-201
遍历所有 pattern
+
+ Perm->>Eval: evaluate(permission, pattern, ...rulesets)
+ Note over Eval: evaluate.ts:9-15
findLast 匹配规则
Wildcard.match()
+
+ alt action = "allow"
+ Eval-->>Perm: 直接通过
+ Perm-->>Exec: resolve(无阻塞)
+ else action = "deny"
+ Eval-->>Perm: 抛出 DeniedError
+ Perm-->>Proc: PermissionDeniedError
+ Note over Proc: ToolPart status → "error"
+ else action = "ask"
+ Perm->>User: Bus.publish(Permission.Event.Asked)
+ Note over User: TUI 显示权限弹窗
用户看到工具名+参数
+
+ alt 用户批准 "once"
+ User->>Perm: reply("once")
+ Perm-->>Exec: resolve
+ else 用户批准 "always"
+ User->>Perm: reply("always")
+ Note over Perm: 💡 添加到 approved 规则集
后续同类调用自动通过
+ Perm-->>Exec: resolve
+ else 用户拒绝
+ User->>Perm: reply("reject")
+ Perm-->>Proc: RejectedError
+ Note over Proc: blocked = true
+ end
+ end
+
+ Exec->>FS: 执行实际操作
spawn / readFile / write
+ FS-->>Exec: 执行结果
+
+ Exec-->>Define: {title, metadata, output}
+
+ Define->>Define: Truncate.output(output)
截断超长输出
+ Note over Define: 💡 关键点:超限写入文件
metadata.truncated = true
+
+ Define-->>Proc: 完整工具结果
+
+ Proc->>Proc: ToolPart status → "completed"
更新 output/title/metadata
+
+ Note over Proc: tool-result 事件
Session.updatePart()
+
+ Proc-->>LLM: 结果回传(下一轮流式调用)
+```
+
+---
+
+## 工具注册与发现
+
+OpenCode 的工具来自三个来源,最终汇入统一注册表(Unified Registry):
+
+```mermaid
+flowchart TB
+ subgraph Builtin["🏗️ 内置工具(Built-in)"]
+ direction TB
+ bash["bash
tool/bash.ts"]
+ read["read
tool/read.ts"]
+ edit["edit
tool/edit.ts"]
+ write["write
tool/write.ts"]
+ grep["grep
tool/grep.ts"]
+ glob["glob
tool/glob.ts"]
+ task["task
tool/task.ts"]
+ fetch["webfetch
tool/webfetch.ts"]
+ lsp["lsp
tool/lsp.ts"]
+ patch["apply_patch
tool/apply_patch.ts"]
+ todo["todowrite
tool/todowrite.ts"]
+ batch["batch
tool/batch.ts"]
+ question["question
(条件注册)"]
+ websearch["websearch / codesearch
(条件注册)"]
+ end
+
+ subgraph Custom["📁 自定义工具(Custom)"]
+ direction TB
+ tooldir["{tool,tools}/*.{js,ts}
项目目录下的工具文件"]
+ plugin["Plugin.list()
插件提供的工具"]
+ end
+
+ subgraph MCP["🌐 MCP 工具(Model Context Protocol)"]
+ direction TB
+ mcpserver["MCP Server 连接
mcp/index.ts"]
+ mcptools["远程工具列表
.opencode/mcp.json 配置"]
+ end
+
+ subgraph Registry["📋 统一注册表
tool/registry.ts"]
+ direction TB
+ register["register(tool)
注册/更新"]
+ ids["ids()
列出所有工具 ID"]
+ tools["tools(model, agent)
根据模型+Agent 过滤"]
+ end
+
+ subgraph Filter["🔍 运行时过滤"]
+ direction TB
+ model_filter["模型能力过滤
是否支持 toolcall"]
+ agent_filter["Agent 权限过滤
Permission.disabled()"]
+ user_filter["用户覆盖
user.tools[name] === false"]
+ plugin_filter["Plugin hook
tool.definition 扩展"]
+ end
+
+ subgraph Final["✅ 最终工具集"]
+ available["Record<string, Tool>
传入 LLM.stream()"]
+ end
+
+ Builtin --> Registry
+ Custom --> Registry
+ MCP --> Registry
+
+ Registry --> Filter
+ Filter --> Final
+
+ style Builtin fill:#e8f5e9
+ style Custom fill:#fff3e0
+ style MCP fill:#e3f2fd
+ style Registry fill:#f3e5f5
+ style Filter fill:#fce4ec
+ style Final fill:#e0f2f1
+```
+
+### 💡 关键点:条件注册
+
+并非所有内置工具都会注册。`registry.ts` 中的条件逻辑:
+
+| 工具 | 条件 | 原因 |
+|------|------|------|
+| `question` | 仅当 `OPENCODE_CLIENT` 或 `OPENCODE_ENABLE_QUESTION_TOOL` | 需要 TUI 交互能力 |
+| `websearch` / `codesearch` | 仅当 `ProviderID.opencode` 或 `OPENCODE_ENABLE_EXA` | 需要 Exa 搜索服务 |
+| `apply_patch` vs `edit`/`write` | 取决于模型变体(GPT-4/OSS 选择) | 不同模型擅长不同的编辑格式 |
+| `batch` | 需要 `experimental.batch` 配置 | 实验性功能 |
+
+### Tool.define() 模式
+
+每个工具通过统一的 `Tool.define(id, init)` 注册(`tool/tool.ts:49-89`):
+
+```
+Tool.define("bash", async (initCtx) => ({
+ description: "执行 shell 命令",
+ parameters: z.object({ command: z.string(), ... }),
+ execute: async (args, ctx) => ({ title, output, metadata }),
+}))
+```
+
+`init` 函数在工具被请求时**惰性调用**,传入 `{agent}` 上下文。这意味着同一个工具面对不同 Agent 可能返回不同的 `description` 或 `parameters`。
+
+---
+
+## 工具权限模型:Auto / Ask / Deny 决策流
+
+权限系统是工具调用的守门人。每次工具调用都会触发 `ctx.ask()`,启动以下决策流:
+
+```mermaid
+flowchart TD
+ Start["ctx.ask({permission, patterns, always})"] --> ForEach
+
+ ForEach["遍历每个 pattern"] --> Evaluate
+
+ Evaluate["evaluate(permission, pattern, ...rulesets)
permission/evaluate.ts:9-15"] --> FindLast
+
+ FindLast["findLast: 从后向前
匹配 permission + pattern
使用 Wildcard.match()"] --> Found
+
+ Found{找到匹配规则?}
+
+ Found -->|否| DefaultAsk["默认:action = 'ask'"]
+ Found -->|是| CheckAction
+
+ CheckAction{规则的 action?}
+
+ CheckAction -->|"allow"| Allow["✅ 直接通过
无阻塞"]
+ CheckAction -->|"deny"| Deny["❌ 抛出 DeniedError
用户不可见"]
+ CheckAction -->|"ask"| NeedsAsk["标记 needsAsk = true"]
+ DefaultAsk --> NeedsAsk
+
+ Allow --> NextPattern{更多 pattern?}
+ NextPattern -->|是| ForEach
+ NextPattern -->|否| AllDone["所有 pattern 通过"]
+ NeedsAsk --> NextPattern2{更多 pattern?}
+ NextPattern2 -->|是| ForEach
+ NextPattern2 -->|否| ShowPrompt
+
+ ShowPrompt["📢 Bus.publish(Permission.Event.Asked)
创建 PendingEntry + Deferred"]
+ ShowPrompt --> WaitUser["⏳ await Deferred.await()
阻塞直到用户回复"]
+
+ WaitUser --> UserReply{用户回复}
+
+ UserReply -->|"once"| Once["✅ 仅此次通过
不修改规则集"]
+ UserReply -->|"always"| Always["✅ 永久通过
添加 allow 规则
自动批准同类请求"]
+ UserReply -->|"reject"| Reject["❌ RejectedError
取消本会话所有
pending 请求"]
+
+ Deny --> ToolError["ToolPart.status = 'error'"]
+ Reject --> Blocked["blocked = true
processor 返回 'stop'"]
+
+ style Allow fill:#c8e6c9
+ style Deny fill:#ffcdd2
+ style Reject fill:#ffcdd2
+ style Once fill:#c8e6c9
+ style Always fill:#a5d6a7
+ style NeedsAsk fill:#fff9c4
+ style DefaultAsk fill:#fff9c4
+```
+
+### 💡 关键点:规则叠加与优先级
+
+权限规则来自多个层级,**最后匹配的规则胜出**(`findLast` 语义):
+
+```
+1. 全局默认规则(allow all, doom_loop ask, question deny)
+2. 白名单目录(Truncate.GLOB + skill 目录)
+3. 用户配置权限(config.permission)
+4. Agent 配置权限(agent.permission)
+5. Session 级权限(session.permission — 可覆盖一切)
+```
+
+例如 `explore` Agent 的权限配置:
+
+```
+{ "*": "deny", grep: "allow", glob: "allow", list: "allow",
+ bash: "allow", read: "allow" }
+```
+
+这意味着 `explore` Agent 只能使用只读工具,无法调用 `edit` 或 `write`。
+
+---
+
+## 图解说明:工具执行细节
+
+### Bash 工具执行流(`tool/bash.ts`)
+
+Bash 工具的执行比想象中复杂——它包含**两阶段权限检查**:
+
+1. **外部目录检查**:使用 tree-sitter 解析 bash 命令 AST,提取 `cd`/`rm`/`cp`/`mv` 等文件系统操作的目标路径,判断是否超出项目目录
+2. **命令权限检查**:提取命令名,以完整命令文本(含重定向)作为 pattern 请求权限
+
+执行本身通过 `spawn()` 创建子进程,**流式更新 metadata**——用户可以实时在 TUI 中看到命令输出。超时和 abort 信号都会触发进程终止。
+
+### Edit 工具执行流(`tool/edit.ts`)
+
+Edit 工具使用**文件锁 + 时间戳断言**保护并发安全:
+
+```
+FileTime.withLock(filePath, async () => {
+ FileTime.assert(sessionID, filePath) // 确认文件未被外部修改
+ // ... 执行替换 ...
+})
+```
+
+替换本身通过**9 种 Replacer 策略**依次尝试(下一节详述),确保即使 LLM 给出的 `oldString` 有微小差异也能匹配成功。
+
+### MCP 工具集成(`mcp/index.ts`)
+
+MCP(Model Context Protocol)工具通过标准协议连接外部工具服务器。配置在 `.opencode/mcp.json` 中定义,OpenCode 作为 MCP client 连接到这些 server,将远程工具注册到统一注册表中。MCP 工具的权限检查与内置工具完全一致。
+
+---
+
+## 关键设计决策
+
+### 1. 为什么工具定义是惰性初始化(Lazy Init)?
+
+💡 `Tool.define(id, init)` 中 `init` 是一个异步函数,只在工具被实际请求时才执行。这允许:
+- 不同 Agent 获得不同的工具描述(`init({agent})`)
+- 运行时根据环境决定工具能力
+- 避免启动时加载所有工具的开销
+
+### 2. 为什么使用 Wildcard 匹配而非精确匹配?
+
+💡 `Wildcard.match()` 让权限规则可以使用 `*` 通配。例如 `bash` 权限的 `always` 模式 `"git *"` 可以一次批准所有 git 命令,避免用户为每个 `git status`、`git diff` 单独确认。
+
+### 3. 为什么 "always" 回复会自动批准同会话的其他请求?
+
+💡 当用户回复 `"always"` 时,系统不仅通过当前请求,还会扫描该 Session 中所有 pending 请求,自动批准匹配的。这避免了"批准了 edit 权限但还要单独批准每个文件"的烦人体验。
+
+### 4. 为什么输出需要截断?
+
+💡 `Tool.define()` 的包装层(`tool.ts:71-84`)会调用 `Truncate.output()` 截断超长输出。过长的工具输出会消耗 LLM 的上下文窗口(Context Window),导致重要信息被挤出。截断后的内容写入临时文件,Agent 可按需读取。
+
+### 5. 死循环检测为什么设在 Processor 层?
+
+💡 Doom Loop 检测在 `processor.ts` 的 `tool-call` 事件处理中——检查最近 3 个 ToolPart 是否使用相同工具+相同参数。这比在工具层面检测更全面,因为它能跨工具类型发现重复模式。
+
+---
+
+## 与下一节的衔接
+
+我们已经看到工具调用的完整生命周期——从发现、权限、执行到结果返回。但其中最复杂的工具是 `edit`——它如何将 LLM 给出的"旧文本 → 新文本"转变为实际的文件修改?下一节 **"一次文件编辑的完整旅程"** 将深入 9 种 Replacer 策略、Patch 系统和 Snapshot 机制。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/06-\344\270\200\346\254\241\346\226\207\344\273\266\347\274\226\350\276\221\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/06-\344\270\200\346\254\241\346\226\207\344\273\266\347\274\226\350\276\221\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md"
new file mode 100644
index 000000000000..d6c474317df9
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/06-\344\270\200\346\254\241\346\226\207\344\273\266\347\274\226\350\276\221\347\232\204\345\256\214\346\225\264\346\227\205\347\250\213.md"
@@ -0,0 +1,407 @@
+# 一次文件编辑的完整旅程
+
+> 📌 一句话总结:从 Agent 说"我要改这个文件"到文件落盘,中间经历了路径解析、文件锁、时间戳断言、9 种模糊匹配策略、格式化、LSP 诊断、Snapshot 快照——每一步都是一道安全网。
+> 🗺️ 本节在全景中的位置:上一节描述了工具调用的通用流程,本节聚焦最复杂、最关键的一类工具——文件编辑。理解这条链路,就理解了 OpenCode 如何**安全、可靠地修改你的代码**。
+
+---
+
+## 场景回顾
+
+还是小明的故事。Agent 已经读取了 `utils.ts` 的内容,现在决定执行编辑:
+
+```json
+{
+ "toolName": "edit",
+ "args": {
+ "filePath": "/project/src/utils.ts",
+ "oldString": "items.forEach((item) => {",
+ "newString": "for (const item of items) {"
+ }
+}
+```
+
+让我们追踪这个 JSON 如何变成磁盘上的真实修改。
+
+---
+
+## 全景图:文件编辑完整链路
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant Agent as 🤖 Agent 决策
processor.ts
+ participant Define as 🏗️ Tool.define
tool/tool.ts
+ participant Edit as ✏️ EditTool
tool/edit.ts
+ participant FT as ⏱️ FileTime
file/time.ts
+ participant FS as 💾 Filesystem
util/filesystem.ts
+ participant Perm as 🔐 Permission
permission/index.ts
+ participant Format as 💅 Formatter
Format.file()
+ participant LSP as 🔍 LSP Server
lsp/
+ participant Snap as 📸 Snapshot
snapshot/index.ts
+ participant Git as 🗂️ Snapshot Git
~/.opencode/data/snapshot/
+ participant Bus as 📢 Event Bus
+
+ Agent->>Define: execute({filePath, oldString, newString})
+ Note over Define: tool.ts:49-89
Zod 参数校验
+
+ Define->>Edit: 转入 EditTool.execute()
+
+ Edit->>Edit: 路径解析
相对路径 → 绝对路径
+ Note over Edit: path.isAbsolute() ?
: path.join(Instance.directory, filePath)
+
+ Edit->>Edit: assertExternalDirectory(ctx, filePath)
+ Note over Edit: 💡 关键点:检查路径是否
在项目目录之内
+
+ Edit->>FT: FileTime.withLock(filePath, fn)
+ Note over FT: 获取信号量锁
同一文件串行操作
+
+ FT->>FT: FileTime.assert(sessionID, filePath)
+ Note over FT: 💡 关键点:比较 mtime/ctime/size
确认文件未被外部修改
+
+ FT->>FS: Filesystem.stat(filePath)
+ FS-->>FT: {mtime, ctime, size}
+
+ Edit->>FS: Filesystem.readText(filePath)
+ FS-->>Edit: contentOld(原始文件内容)
+
+ Edit->>Edit: detectLineEnding(contentOld)
识别换行符: "\n" 或 "\r\n"
+
+ Edit->>Edit: replace(contentOld, oldString, newString)
+ Note over Edit: 💡 核心:9 种 Replacer 策略
依次尝试直到匹配成功
+
+ Edit->>Edit: createTwoFilesPatch()
生成 Unified Diff
+
+ Edit->>Perm: ctx.ask({permission:"edit", patterns:[相对路径]})
+ Note over Perm: 权限评估 → allow/ask/deny
metadata 包含完整 diff
+
+ Perm-->>Edit: 权限通过
+
+ Edit->>FS: Filesystem.write(filePath, contentNew)
+ Note over FS: 💡 ENOENT → mkdir -r → retry
原子写入
+
+ Edit->>Format: Format.file(filePath)
+ Note over Format: 应用项目格式化规则
(Prettier/Biome/etc.)
+
+ Edit->>FT: FileTime.read(sessionID, filePath)
+ Note over FT: 记录新的 mtime/ctime/size
供下次编辑断言使用
+
+ Edit->>Bus: Bus.publish(File.Event.Edited)
+ Edit->>Bus: Bus.publish(FileWatcher.Event.Updated)
+
+ Edit->>LSP: LSP.touchFile(filePath, true)
+ LSP->>LSP: 重新分析文件
+ LSP-->>Edit: diagnostics(诊断信息)
+
+ Edit->>Edit: 过滤 errors (severity === 1)
最多 20 条/文件
+
+ Note over Edit: 构建 FileDiff metadata
+
+ Edit-->>Define: {title, output, metadata:{diff, filediff, diagnostics}}
+
+ Define->>Define: Truncate.output(output)
+ Define-->>Agent: 完整工具结果
+
+ Note over Agent: finish-step 事件触发
+
+ Agent->>Snap: Snapshot.track()
+ Note over Snap: 💡 关键点:捕获修改后快照
+
+ Snap->>Git: git add --sparse .
+ Snap->>Git: git write-tree
+ Git-->>Snap: tree hash(快照哈希)
+
+ Snap->>Snap: Snapshot.patch(beforeHash)
+ Note over Snap: 对比 before/after 树
生成变更列表
+
+ Agent->>Agent: SessionSummary.summarize()
+ Note over Agent: 更新 session.summary
{additions, deletions, files, diffs}
+```
+
+---
+
+## Patch 系统:LLM 输出如何变成文件修改
+
+OpenCode 支持两种编辑模式,由不同的工具实现。我们来看它们的完整对比:
+
+```mermaid
+flowchart TB
+ subgraph LLM["🤖 LLM 输出"]
+ direction TB
+ edit_output["Edit 模式
{oldString, newString}
精确替换指令"]
+ patch_output["Apply Patch 模式
Unified Diff 格式
多文件批量修改"]
+ end
+
+ subgraph EditPath["✏️ Edit Tool 路径
tool/edit.ts"]
+ direction TB
+ E1["读取原文件内容"]
+ E2["9 种 Replacer 策略匹配"]
+ E3["字符串替换生成新内容"]
+ E4["createTwoFilesPatch() 生成 diff"]
+ end
+
+ subgraph PatchPath["🩹 Apply Patch 路径
tool/apply_patch.ts + patch/index.ts"]
+ direction TB
+ P1["Patch.parsePatch()
解析 Begin/End 块"]
+ P2["提取 Hunk 列表
add / update / delete"]
+ P3["deriveNewContentsFromChunks()
计算行替换"]
+ P4["applyReplacements()
倒序 splice 应用"]
+ end
+
+ subgraph Common["🔄 共同后续流程"]
+ direction TB
+ C1["权限检查 ctx.ask()"]
+ C2["Filesystem.write() 写入"]
+ C3["Format.file() 格式化"]
+ C4["LSP.touchFile() 诊断"]
+ C5["Snapshot.track() 快照"]
+ end
+
+ edit_output --> EditPath
+ patch_output --> PatchPath
+ EditPath --> Common
+ PatchPath --> Common
+
+ style LLM fill:#e3f2fd
+ style EditPath fill:#e8f5e9
+ style PatchPath fill:#fff3e0
+ style Common fill:#f3e5f5
+```
+
+### Edit Tool 的 9 种 Replacer 策略
+
+当 LLM 给出的 `oldString` 与文件实际内容有微小差异时(这在实践中非常常见),`replace()` 函数(`edit.ts:630-667`)会依次尝试 9 种匹配策略:
+
+```mermaid
+flowchart TD
+ Start["replace(content, oldString, newString)"] --> R1
+
+ R1["① SimpleReplacer
精确字符串匹配"]
+ R1 -->|匹配成功| Done["✅ 应用替换"]
+ R1 -->|失败| R2
+
+ R2["② LineTrimmedReplacer
逐行 trim 后匹配"]
+ R2 -->|匹配成功| Done
+ R2 -->|失败| R3
+
+ R3["③ BlockAnchorReplacer
首尾行锚定 + Levenshtein 距离"]
+ R3 -->|匹配成功| Done
+ R3 -->|失败| R4
+
+ R4["④ WhitespaceNormalizedReplacer
所有空白归一化为单空格"]
+ R4 -->|匹配成功| Done
+ R4 -->|失败| R5
+
+ R5["⑤ IndentationFlexibleReplacer
忽略缩进差异"]
+ R5 -->|匹配成功| Done
+ R5 -->|失败| R6
+
+ R6["⑥ EscapeNormalizedReplacer
处理转义序列差异
\\n \\t \\' \\\" \\`"]
+ R6 -->|匹配成功| Done
+ R6 -->|失败| R7
+
+ R7["⑦ MultiOccurrenceReplacer
所有精确匹配(replaceAll 模式)"]
+ R7 -->|匹配成功| Done
+ R7 -->|失败| R8
+
+ R8["⑧ TrimmedBoundaryReplacer
多行块边界 trim"]
+ R8 -->|匹配成功| Done
+ R8 -->|失败| R9
+
+ R9["⑨ ContextAwareReplacer
上下文行锚定 + 50% 相似度"]
+ R9 -->|匹配成功| Done
+ R9 -->|失败| Error["❌ 未找到匹配
返回错误信息"]
+
+ style R1 fill:#c8e6c9
+ style R3 fill:#fff9c4
+ style R6 fill:#ffe0b2
+ style R9 fill:#ffcdd2
+ style Done fill:#a5d6a7
+ style Error fill:#ef9a9a
+```
+
+### 💡 关键点:为什么需要这么多策略?
+
+LLM 生成的 `oldString` 常见问题:
+- **缩进不一致**:LLM 可能用 2 空格,而文件用 4 空格 → `IndentationFlexibleReplacer`
+- **尾部空白**:LLM 可能丢失行尾空格 → `LineTrimmedReplacer`
+- **转义字符**:LLM 可能输出 `\n` 而文件中是实际换行 → `EscapeNormalizedReplacer`
+- **上下文偏移**:文件被其他编辑修改后行号偏移 → `BlockAnchorReplacer`(使用 Levenshtein 距离容错)
+
+### Apply Patch 的 Hunk 格式
+
+`apply_patch` 工具解析的 Patch 格式(`patch/index.ts`):
+
+```
+*** Begin Patch
+*** Update File: src/utils.ts
+@@ items.forEach((item) => { @@
+- items.forEach((item) => {
++ for (const item of items) {
+ console.log(item)
+*** End Patch
+```
+
+解析后生成 `Hunk` 结构,通过 `deriveNewContentsFromChunks()` 应用:
+1. `seekSequence()` — 4 轮匹配(精确 → rstrip → trim → Unicode 归一化)
+2. `computeReplacements()` — 计算 `[startIndex, oldLength, newLines]` 数组
+3. `applyReplacements()` — **倒序 splice** 避免索引偏移
+
+---
+
+## Snapshot 系统:编辑的时光机
+
+每次编辑前后,Snapshot 系统都会用**独立的 Git 仓库**记录文件系统状态:
+
+```mermaid
+flowchart LR
+ subgraph Before["📸 编辑前"]
+ B1["Snapshot.track()
processor.ts start-step"]
+ B2["git add --sparse ."]
+ B3["git write-tree"]
+ B4["hash: abc123"]
+ end
+
+ subgraph Edit["✏️ 编辑过程"]
+ E1["Filesystem.write()"]
+ E2["文件已修改"]
+ end
+
+ subgraph After["📸 编辑后"]
+ A1["Snapshot.track()
processor.ts finish-step"]
+ A2["git add --sparse ."]
+ A3["git write-tree"]
+ A4["hash: def456"]
+ end
+
+ subgraph Ops["🔧 可用操作"]
+ O1["Snapshot.diff(abc123)
获取 Unified Diff"]
+ O2["Snapshot.diffFull(abc123, def456)
获取 FileDiff[] 详情"]
+ O3["Snapshot.restore(abc123)
完全回滚到编辑前"]
+ O4["Snapshot.revert(patches)
按文件选择性回滚"]
+ end
+
+ Before --> Edit --> After
+ After --> Ops
+
+ style Before fill:#e3f2fd
+ style Edit fill:#fff3e0
+ style After fill:#e8f5e9
+ style Ops fill:#f3e5f5
+```
+
+### Snapshot 存储位置
+
+```
+~/.opencode/data/snapshot/{projectID}/{worktreeHash}/.git
+├── objects/ ← 文件内容快照
+├── refs/ ← 无分支,纯 tree 对象
+├── HEAD
+├── config ← autocrlf=false, fsmonitor=false
+└── info/exclude ← 排除 .gitignore + >2MB 文件
+```
+
+💡 **关键点**:Snapshot 使用**独立于项目的 Git 仓库**。它不会干扰项目的 `.git`,也不会出现在 `git status` 中。大于 2MB 的文件自动排除,后台每小时运行 `git gc --prune=7.days` 清理过期快照。
+
+---
+
+## 图解说明:并发安全三重保护
+
+文件编辑面临的核心挑战:多个工具可能同时修改同一文件。OpenCode 通过三重机制保护:
+
+```mermaid
+flowchart TD
+ subgraph Lock["🔒 第一重:文件信号量锁"]
+ L1["FileTime.withLock(filePath, fn)
file/time.ts"]
+ L2["每个文件路径一个 Semaphore
同一文件的编辑串行化"]
+ end
+
+ subgraph Assert["⏱️ 第二重:时间戳断言"]
+ A1["FileTime.assert(sessionID, filePath)
file/time.ts"]
+ A2["比较当前 mtime/ctime/size
与上次 read 时记录的值"]
+ A3["不一致 → 抛出错误
'文件已被外部修改'"]
+ end
+
+ subgraph Detect["🔍 第三重:行尾检测"]
+ D1["detectLineEnding(content)"]
+ D2["保持原文件的换行风格
\\n 或 \\r\\n"]
+ end
+
+ Lock --> Assert --> Detect --> Safe["✅ 安全执行替换"]
+
+ style Lock fill:#e3f2fd
+ style Assert fill:#fff3e0
+ style Detect fill:#e8f5e9
+ style Safe fill:#c8e6c9
+```
+
+---
+
+## 关键设计决策
+
+### 1. 为什么 Edit 而非 Write?
+
+💡 `edit` 工具做**精确替换**(oldString → newString),`write` 工具做**全量覆盖**。精确替换的好处:
+- diff 更小、更可读
+- 不会意外丢失文件其他部分的内容
+- 权限弹窗中展示的 diff 更有意义
+- LLM 不需要重复输出整个文件(节省 token)
+
+### 2. 为什么 9 种 Replacer 而非 1 种?
+
+💡 LLM 不是精确的文本复制机。实测中,LLM 输出的 `oldString` 经常有空白差异、缩进偏移、转义不一致。9 种策略从严格到宽松依次尝试,**优先使用最精确的匹配**——这避免了过度模糊导致的错误替换。
+
+### 3. 为什么用独立 Git 做 Snapshot?
+
+💡 三个原因:
+- **不污染项目**:Snapshot 的 Git 操作不影响项目的 `.git`
+- **原子性**:`git write-tree` 是原子操作,不会出现半写状态
+- **高效 diff**:`git diff --cached` 比文件系统级 diff 快得多
+
+### 4. 为什么编辑后要跑 Formatter?
+
+💡 `Format.file(filePath)` 在写入后自动运行项目配置的格式化器(Prettier、Biome 等)。这确保 AI 生成的代码**遵循项目的代码风格**,不会因为 LLM 的格式偏好引入不一致。
+
+### 5. 为什么编辑后要收集 LSP 诊断?
+
+💡 `LSP.touchFile()` + `LSP.diagnostics()` 让 Agent 立即知道编辑是否引入了类型错误或语法错误。如果检测到错误,诊断信息会附在工具输出中——Agent 可以在下一轮 ReAct 循环中自动修复。
+
+```
+output: "Edit applied successfully.
+
+LSP errors detected:
+
+src/utils.ts:42 - error TS2304: Cannot find name 'item'.
+"
+```
+
+### 6. Apply Patch 的倒序 splice
+
+💡 `applyReplacements()` 将替换操作按行号**从大到小排序**后执行。这是一个经典技巧——倒序处理避免了前面的 splice 操作导致后面的行号偏移。
+
+---
+
+## Write 工具与 MultiEdit 工具
+
+除了 `edit`,文件修改还有两个变体:
+
+**`write` 工具**(`tool/write.ts`)— 全量写入:
+- 适合创建新文件或大规模重写
+- 同样经过权限检查 + 格式化 + LSP 诊断
+- diff 展示完整的 before/after 差异
+
+**`multiedit` 工具**(`tool/multiedit.ts`)— 批量编辑:
+- 接受一个 `edits` 数组,对同一文件执行多次替换
+- 内部循环调用 `EditTool.execute()`
+- 避免多次文件读写的开销
+
+**`apply_patch` 工具**(`tool/apply_patch.ts`)— 多文件补丁:
+- 解析 `*** Begin Patch ... *** End Patch` 格式
+- 支持 Add / Update / Delete / Move 四种操作
+- 单次权限请求覆盖所有文件变更
+- 适合大规模重构场景
+
+---
+
+## 与下一节的衔接
+
+至此,我们完成了"全景视野"章节的三次旅程——对话、工具调用、文件编辑。你已经看到了 OpenCode 从用户输入到文件落盘的完整数据流。接下来,**"快速上手"** 章节将带你从阅读者变为使用者,亲手体验这些流程。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/07-\346\225\260\346\215\256\346\265\201\345\205\250\346\231\257\345\233\276.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/07-\346\225\260\346\215\256\346\265\201\345\205\250\346\231\257\345\233\276.md"
new file mode 100644
index 000000000000..ce8d75bf6f47
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/07-\346\225\260\346\215\256\346\265\201\345\205\250\346\231\257\345\233\276.md"
@@ -0,0 +1,443 @@
+# 数据流全景图
+
+> 📌 一句话总结:从用户敲下回车到 LLM 回复落盘,数据经历 7 次格式变换——字符串 → 内部消息 → AI SDK 格式 → HTTP 请求 → 流式响应 → 结构化 Part → SQLite 行,每一步都有明确的类型守卫。
+> 🗺️ 本节在全景中的位置:前三节以"旅程"视角讲述了对话、工具调用、文件编辑的流程。本节切换到**数据(Data)**视角,用 DFD(数据流图)把系统中所有数据的来源、变换、去向画成一张全景图。
+
+---
+
+## 全景图:系统级数据流
+
+```mermaid
+flowchart LR
+ subgraph External["外部实体"]
+ User["👤 用户
终端/桌面/VSCode"]
+ LLM["🤖 LLM API
Anthropic · OpenAI · Google …"]
+ FS["📁 文件系统
项目源码 · .opencode/"]
+ Git["🔀 Git
版本控制"]
+ MCP["🔌 MCP Server
外部工具"]
+ end
+
+ subgraph Processing["处理层"]
+ Server["🌐 Hono Server
server/index.ts"]
+ Prompt["📨 SessionPrompt
session/prompt.ts"]
+ Processor["⚙️ SessionProcessor
session/processor.ts"]
+ LLMModule["🧠 LLM.stream
provider/llm.ts"]
+ ToolExec["🔧 ToolRegistry
tool/registry.ts"]
+ Compaction["📦 Compaction
session/compaction.ts"]
+ end
+
+ subgraph DataStores["数据存储"]
+ SQLite["🗃️ SQLite
SessionTable
MessageTable
PartTable"]
+ Config["⚙️ Config Files
opencode.json
.opencode/"]
+ Snapshot["📸 Snapshot
VCS 快照"]
+ end
+
+ subgraph Bus["事件总线"]
+ EventBus["📡 Bus
BusEvent · SyncEvent"]
+ end
+
+ User -->|"PromptInput (JSON)"| Server
+ Server -->|"验证后的 PromptInput"| Prompt
+ Prompt -->|"MessageV2.User + Parts"| SQLite
+ Prompt -->|"ModelMessage[]"| LLMModule
+ LLMModule -->|"streamText() 调用"| LLM
+ LLM -->|"StreamTextResult (SSE)"| Processor
+ Processor -->|"ToolPart{status:running}"| ToolExec
+ ToolExec -->|"ToolPart{status:completed}"| Processor
+ ToolExec -->|"读/写文件"| FS
+ ToolExec -->|"MCP 调用"| MCP
+ Processor -->|"Part 增量写入"| SQLite
+ Processor -->|"PartDelta 事件"| EventBus
+ EventBus -->|"SSE /event 推送"| Server
+ Server -->|"实时流"| User
+ Compaction -->|"压缩后消息"| SQLite
+ Config -->|"agent/tool/mcp 配置"| Prompt
+ Git -->|"diff · branch"| FS
+ Processor -->|"PatchPart · SnapshotPart"| Snapshot
+```
+
+---
+
+## 消息格式变换链
+
+这是数据流的核心——一条用户输入从 `string` 到 SQLite 行,经历了 **7 次格式变换**:
+
+```mermaid
+flowchart TB
+ subgraph S1["① 用户输入"]
+ Raw["纯文本字符串
'帮我重构这个函数'"]
+ end
+
+ subgraph S2["② PromptInput"]
+ PI["PromptInput {
sessionID,
parts: [{type:'text', text:'…'}],
model?, agent?
}"]
+ end
+
+ subgraph S3["③ MessageV2.User"]
+ MU["MessageV2.User {
id: MessageID,
role: 'user',
agent: 'coder',
model: {providerID, modelID},
time: {created}
}"]
+ UP["Part[] — TextPart / FilePart / AgentPart"]
+ end
+
+ subgraph S4["④ ModelMessage (AI SDK)"]
+ MM["CoreMessage[] {
role: 'user' | 'assistant',
content: ContentPart[]
}"]
+ end
+
+ subgraph S5["⑤ HTTP 请求"]
+ HTTP["POST https://api.anthropic.com/v1/messages
{model, messages, tools, system, stream:true}"]
+ end
+
+ subgraph S6["⑥ 流式响应"]
+ Stream["StreamTextResult
事件序列:
text-delta · tool-call · step-finish …"]
+ end
+
+ subgraph S7["⑦ 持久化格式"]
+ DB["MessageTable.data (JSON)
PartTable.data (JSON)
每个 Part 独立一行"]
+ end
+
+ S1 -->|"TUI/Server 封装"| S2
+ S2 -->|"SessionPrompt.prompt()"| S3
+ S3 -->|"MessageV2.toModelMessages()"| S4
+ S4 -->|"Vercel AI SDK streamText()"| S5
+ S5 -->|"LLM 返回 SSE 流"| S6
+ S6 -->|"SessionProcessor.process()"| S7
+```
+
+---
+
+## 图解说明:每一步的细节
+
+### ① → ② 用户输入 → PromptInput
+
+用户在 TUI 中输入文本后,客户端将其封装为 `PromptInput`:
+
+```typescript
+// session/prompt.ts — PromptInput schema
+const PromptInput = z.object({
+ sessionID: SessionID.zod,
+ parts: z.array(z.discriminatedUnion("type", [
+ TextPartInput, // { type: "text", text: string }
+ FilePartInput, // { type: "file", url, mime }
+ AgentPartInput, // { type: "agent", name }
+ SubtaskPartInput, // { type: "subtask", prompt, description, agent }
+ ])),
+ model: z.object({ providerID, modelID }).optional(),
+ agent: z.string().optional(),
+ format: MessageV2.Format.optional(),
+ noReply: z.boolean().optional(),
+})
+```
+
+注意 `parts` 数组——用户输入不仅仅是文字,还可以附带文件、指定 Agent、甚至发起子任务。
+
+### ② → ③ PromptInput → MessageV2.User
+
+`SessionPrompt.prompt()` 把 `PromptInput` 转化为持久化的 `MessageV2.User`:
+
+```typescript
+// 关键字段映射
+MessageV2.User = {
+ id: MessageID.ascending(), // 时间有序 ID
+ sessionID: input.sessionID,
+ role: "user",
+ agent: resolvedAgent.name, // 从 config 解析
+ model: { providerID, modelID }, // 从 config / input 解析
+ time: { created: Date.now() },
+ format: input.format, // text 或 json_schema
+ tools: input.tools, // 工具白名单
+}
+```
+
+同时,每个 `part` 被写入 `PartTable`,与 `MessageTable` 通过 `message_id` 关联。
+
+### ③ → ④ MessageV2 → ModelMessage(AI SDK 格式)
+
+这是最复杂的变换。`MessageV2.toModelMessages()` 负责把内部 Part 类型映射到 Vercel AI SDK 的 `CoreMessage` 格式:
+
+| 内部 Part 类型 | AI SDK ContentPart | 说明 |
+|---|---|---|
+| `TextPart` | `{ type: "text", text }` | 直接映射 |
+| `FilePart` | `{ type: "file", url, mediaType }` | URL 或 base64 |
+| `ReasoningPart` | `{ type: "reasoning", text }` | 思考过程 |
+| `ToolPart(completed)` | `{ type: "tool-result", toolCallId, result }` | 工具结果 |
+| `ToolPart(error)` | `{ type: "tool-result", isError: true }` | 工具错误 |
+| `StepFinishPart` | *(不映射)* | 仅内部使用 |
+| `PatchPart` | *(不映射)* | 仅内部使用 |
+
+> 💡 **关键设计**:并非所有 Part 都需要发给 LLM。`StepFinishPart`、`PatchPart`、`SnapshotPart` 等仅用于内部记录和 UI 展示。
+
+### ④ → ⑤ ModelMessage → HTTP 请求
+
+Vercel AI SDK 的 `streamText()` 接管剩余工作:
+
+```typescript
+// provider/llm.ts — 简化后的调用
+const result = streamText({
+ model: provider(modelID), // AI SDK provider 实例
+ messages: modelMessages, // CoreMessage[]
+ tools: builtTools, // 工具定义
+ system: systemPrompts.join("\n"), // 合并系统提示词
+ abortSignal: abort,
+ maxSteps: agent.steps ?? 100, // 最大迭代步数
+ toolChoice: agent.toolChoice,
+})
+```
+
+### ⑤ → ⑥ HTTP 响应 → StreamTextResult
+
+LLM 以 Server-Sent Events 返回流式数据。Vercel AI SDK 将其解析为标准化事件序列:
+
+```
+start → reasoning-start → reasoning-delta* → reasoning-end
+ → text-start → text-delta* → text-end
+ → tool-input-start → tool-call → tool-result
+ → step-finish → finish
+```
+
+### ⑥ → ⑦ StreamTextResult → 持久化
+
+`SessionProcessor.process()` 消费这些事件,**实时**写入数据库:
+
+```mermaid
+flowchart LR
+ subgraph Processor["SessionProcessor"]
+ direction TB
+ Listen["监听 stream 事件"]
+ Create["创建/更新 Part"]
+ Persist["写入 PartTable"]
+ Emit["发射 Bus 事件"]
+ end
+
+ Stream["StreamTextResult"] --> Listen
+ Listen -->|"text-delta"| Create
+ Listen -->|"tool-call"| Create
+ Listen -->|"step-finish"| Create
+ Create --> Persist
+ Persist --> Emit
+ Emit -->|"PartDelta
PartUpdated"| Bus["EventBus"]
+```
+
+每种流事件对应的 Part 创建逻辑:
+
+| Stream 事件 | 创建的 Part 类型 | Part 状态 |
+|---|---|---|
+| `reasoning-start` | `ReasoningPart` | `text: ""` |
+| `reasoning-delta` | *(更新)* | 追加 `text` |
+| `text-start` | `TextPart` | `text: ""` |
+| `text-delta` | *(更新)* | 追加 `text` |
+| `tool-input-start` | `ToolPart` | `status: "pending"` |
+| `tool-call` | *(更新)* | `status: "running"` |
+| `tool-result` | *(更新)* | `status: "completed"` |
+| `tool-error` | *(更新)* | `status: "error"` |
+| `step-finish` | `StepFinishPart` | `cost`, `tokens` |
+
+---
+
+## 数据存储层详解
+
+### SQLite 表结构
+
+```mermaid
+erDiagram
+ ProjectTable ||--o{ SessionTable : "project_id"
+ SessionTable ||--o{ MessageTable : "session_id"
+ MessageTable ||--o{ PartTable : "message_id"
+ SessionTable ||--o{ TodoTable : "session_id"
+ ProjectTable ||--o| PermissionTable : "project_id"
+
+ ProjectTable {
+ text id PK
+ text directory
+ integer time_created
+ integer time_updated
+ }
+
+ SessionTable {
+ text id PK
+ text project_id FK
+ text workspace_id
+ text parent_id FK
+ text slug
+ text directory
+ text title
+ text version
+ text share_url
+ text summary_diffs "JSON"
+ text revert "JSON"
+ text permission "JSON"
+ integer time_created
+ integer time_updated
+ integer time_compacting
+ integer time_archived
+ }
+
+ MessageTable {
+ text id PK
+ text session_id FK
+ integer time_created
+ integer time_updated
+ text data "JSON — MessageV2.Info"
+ }
+
+ PartTable {
+ text id PK
+ text message_id FK
+ text session_id FK
+ integer time_created
+ integer time_updated
+ text data "JSON — MessageV2.Part"
+ }
+
+ TodoTable {
+ text session_id FK
+ text content
+ text status
+ text priority
+ integer position
+ integer time_created
+ integer time_updated
+ }
+
+ PermissionTable {
+ text project_id PK
+ integer time_created
+ integer time_updated
+ text data "JSON — Permission.Ruleset"
+ }
+```
+
+### 存储设计决策
+
+**为什么 `data` 列存 JSON?**
+
+`MessageTable.data` 和 `PartTable.data` 都用 JSON 列存储完整对象。这是**事件溯源(Event Sourcing)**的简化实现:
+
+1. **写入快**——消息结构复杂且频繁变化,不值得把每个字段拆成列
+2. **读取灵活**——整条 JSON 反序列化后即可得到完整类型
+3. **迁移简单**——新增字段只需在应用层处理默认值,不需要 `ALTER TABLE`
+
+但核心索引字段(`session_id`、`time_created`、`id`)仍然是独立列,确保查询性能。
+
+---
+
+## 事件总线:实时数据分发
+
+```mermaid
+flowchart TB
+ subgraph Publishers["发布者"]
+ SP["SessionPrompt"]
+ PROC["SessionProcessor"]
+ SESS["Session CRUD"]
+ end
+
+ subgraph Bus["Bus (Effect PubSub)"]
+ direction LR
+ Typed["typed Map
按事件类型路由"]
+ Wild["wildcard PubSub
所有事件"]
+ end
+
+ subgraph Subscribers["订阅者"]
+ SSE["SSE /event 端点
→ TUI / 桌面客户端"]
+ Plugin["Plugin Hooks
→ 插件系统"]
+ Internal["内部服务
→ Status · Compaction"]
+ end
+
+ SP -->|"MessageV2.Event.Updated"| Typed
+ SP -->|"MessageV2.Event.PartUpdated"| Typed
+ PROC -->|"MessageV2.Event.PartDelta"| Typed
+ PROC -->|"Session.Event.Error"| Typed
+ SESS -->|"Session.Event.Created"| Typed
+ SESS -->|"Session.Event.Updated"| Typed
+
+ Typed --> Wild
+ Typed --> SSE
+ Typed --> Plugin
+ Wild --> Internal
+```
+
+**事件类型分两类**:
+
+| 类型 | 定义方式 | 用途 | 示例 |
+|---|---|---|---|
+| `SyncEvent` | `SyncEvent.define()` | 需要持久化/同步的状态变更 | `Session.Event.Created`、`MessageV2.Event.Updated` |
+| `BusEvent` | `BusEvent.define()` | 瞬时通知,不持久化 | `MessageV2.Event.PartDelta`、`SessionStatus.Event.Status` |
+
+---
+
+## 完整数据流时序
+
+把上面所有部分串起来,一次完整的对话数据流如下:
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant U as 👤 User
+ participant S as 🌐 Hono Server
+ participant P as 📨 SessionPrompt
+ participant DB as 🗃️ SQLite
+ participant L as 🧠 LLM.stream
+ participant API as 🤖 LLM API
+ participant Proc as ⚙️ Processor
+ participant T as 🔧 ToolExec
+ participant Bus as 📡 EventBus
+
+ U->>S: POST /session/:id/message
{parts:[{type:"text",text:"…"}]}
+ S->>P: 验证 PromptInput
+ P->>DB: INSERT MessageTable (User)
+ P->>DB: INSERT PartTable (TextPart)
+ P->>Bus: MessageV2.Event.Updated
+ P->>P: MessageV2.toModelMessages()
+ P->>L: StreamInput {messages, tools, system}
+ L->>API: streamText() → HTTP POST
+ API-->>Proc: SSE: text-delta
+ Proc->>DB: INSERT PartTable (TextPart)
+ Proc->>Bus: PartDelta {delta}
+ Bus-->>S: SSE 推送
+ S-->>U: 实时文本流
+ API-->>Proc: SSE: tool-call
+ Proc->>DB: UPDATE PartTable (ToolPart → running)
+ Proc->>T: 执行工具
+ T->>Proc: 工具结果
+ Proc->>DB: UPDATE PartTable (ToolPart → completed)
+ Proc->>Bus: PartUpdated
+ API-->>Proc: SSE: step-finish
+ Proc->>DB: INSERT PartTable (StepFinishPart)
+ Proc->>DB: UPDATE MessageTable (tokens, cost)
+ Proc->>Bus: MessageV2.Event.Updated
+ Bus-->>S: SSE 推送
+ S-->>U: 完成通知
+```
+
+---
+
+## 关键设计决策
+
+### 1. 为什么 Part 独立存储?
+
+每个 `Part` 在 `PartTable` 中是独立的一行,而不是嵌入在 `MessageTable.data` 中。这让我们能:
+
+- **增量更新**:流式过程中只需 `UPDATE` 一个 Part 行,而不是重写整条 Message
+- **独立删除**:compaction(上下文压缩)时可以只删除工具输出的 Part,保留其他
+- **细粒度事件**:`PartDelta` 事件只携带变化部分,减少 SSE 传输量
+
+### 2. 为什么消息 ID 是时间有序的?
+
+`MessageID.ascending()` 生成时间有序 ID(类似 ULID),这意味着:
+
+- 按 `id` 排序 = 按时间排序,无需额外 `ORDER BY time_created`
+- `message_session_time_created_id_idx` 索引可以高效支持分页查询
+- `SessionID` 使用 `descending()`——最新会话排在前面
+
+### 3. 为什么用 Effect PubSub 而不是 Node.js EventEmitter?
+
+Effect PubSub 提供:
+
+- **背压(Backpressure)**:订阅者消费慢时不会丢失事件
+- **类型安全**:每个事件有 Zod schema 验证
+- **Effect 集成**:可以无缝嵌入 Effect 的错误处理和资源管理
+
+---
+
+## 与下一节的衔接
+
+我们已经看到数据在系统中如何**流动**。但数据的"形态"在时间轴上是如何变化的?一个 Session 从创建到归档经历了哪些状态?一个 ToolPart 从 `pending` 到 `completed` 的状态机是什么样的?
+
+下一节 [08-状态生命周期全景图](./08-状态生命周期全景图.md) 将从**状态(State)**视角,为每种核心实体画出完整的状态机图。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/08-\347\212\266\346\200\201\347\224\237\345\221\275\345\221\250\346\234\237\345\205\250\346\231\257\345\233\276.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/08-\347\212\266\346\200\201\347\224\237\345\221\275\345\221\250\346\234\237\345\205\250\346\231\257\345\233\276.md"
new file mode 100644
index 000000000000..10060b3de907
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/08-\347\212\266\346\200\201\347\224\237\345\221\275\345\221\250\346\234\237\345\205\250\346\231\257\345\233\276.md"
@@ -0,0 +1,467 @@
+# 状态生命周期全景图
+
+> 📌 一句话总结:OpenCode 中有 4 条核心状态机——Session、SessionStatus、Message(含 Assistant 与 Part)、ToolPart,它们互相联动但各自独立,理解这些状态流转就能理解系统在任意时刻"正在做什么"。
+> 🗺️ 本节在全景中的位置:上一节从数据流视角画出了信息的变换链路。本节切换到**状态(State)**视角,用状态机图展示每种核心实体的完整生命周期,以及状态之间的触发关系。
+
+---
+
+## 全景图:四大状态机联动
+
+```mermaid
+flowchart TB
+ subgraph Overview["四大状态机总览"]
+ direction LR
+ A["🗂️ Session 生命周期
创建 → 活跃 → 归档"]
+ B["🚦 SessionStatus
idle ↔ busy ↔ retry"]
+ C["💬 Message 生命周期
创建 → 处理中 → 完成"]
+ D["🔧 ToolPart 状态
pending → running → completed"]
+ end
+
+ A ---|"Session 活跃时
触发消息处理"| C
+ C ---|"消息处理驱动
Status 切换"| B
+ C ---|"消息包含
工具调用"| D
+ D ---|"工具完成/失败
影响消息状态"| C
+```
+
+---
+
+## 一、Session 生命周期
+
+```mermaid
+stateDiagram-v2
+ [*] --> Created: Session.create()
+
+ Created --> Active: 首次消息 / touch()
+
+ Active --> Active: setTitle() / setPermission()\nsetSummary() / touch()
+ Active --> Compacting: 上下文溢出\ntime.compacting = now
+ Active --> Forked: Session.fork()
+ Active --> Shared: Session.share()
+ Active --> Reverting: setRevert()
+
+ Compacting --> Active: 压缩完成\ntime.compacting = undefined
+
+ Shared --> Active: Session.unshare()
+
+ Reverting --> Active: clearRevert()
+
+ Active --> Archived: setArchived()\ntime.archived = now
+ Archived --> Active: setArchived(undefined)
+
+ Active --> Deleted: Session.delete()
+ Archived --> Deleted: Session.delete()
+ Deleted --> [*]
+
+ note right of Created
+ Session.Info {
+ id: SessionID (descending)
+ slug: string
+ projectID: ProjectID
+ title: string
+ time: { created, updated }
+ }
+ end note
+
+ note right of Compacting
+ time.compacting 非空
+ 表示正在压缩上下文
+ 期间不接受新消息
+ end note
+
+ note right of Archived
+ time.archived 非空
+ UI 中不再默认显示
+ 但数据完整保留
+ end note
+```
+
+### Session 状态说明
+
+| 状态 | 判断依据 | 触发事件 | 可执行操作 |
+|---|---|---|---|
+| **Created** | `time.created` 存在,无消息 | `Session.Event.Created` | 发消息、修改标题、设权限 |
+| **Active** | `time.updated > time.created` | `Session.Event.Updated` | 全部操作 |
+| **Compacting** | `time.compacting` 非空 | 内部状态 | 等待压缩完成 |
+| **Shared** | `share.url` 非空 | `Session.Event.Updated` | 取消分享 |
+| **Reverting** | `revert` 非空 | `Session.Event.Updated` | 确认/取消回滚 |
+| **Archived** | `time.archived` 非空 | `Session.Event.Updated` | 恢复、删除 |
+| **Deleted** | 数据已移除 | `Session.Event.Deleted` | *(终态)* |
+
+### 关键字段
+
+```typescript
+// session/index.ts — Session.Info 中的时间字段
+time: {
+ created: number, // 创建时间
+ updated: number, // 最后更新时间
+ compacting?: number, // 压缩中时间戳(存在即为压缩中)
+ archived?: number, // 归档时间戳(存在即为已归档)
+}
+```
+
+---
+
+## 二、SessionStatus 状态机
+
+`SessionStatus` 是独立于 `Session` 的**运行时状态**——它不存在数据库中,只在内存 `Map` 中维护。
+
+```mermaid
+stateDiagram-v2
+ [*] --> idle: 默认状态
+
+ idle --> busy: stream 开始\nProcessor 收到 "start" 事件
+
+ busy --> idle: 正常完成\nProcessor 收到 "finish"
+ busy --> retry: 可重试错误\n(429 / 5xx / 网络错误)
+ busy --> idle: 不可重试错误\n(401 / 403 / 404)
+ busy --> idle: 用户取消\nAbortSignal triggered
+
+ retry --> busy: 延迟后重试\nsleep(delay) → 重新 streamText()
+ retry --> idle: 超过最大重试\n或用户取消
+
+ note right of idle
+ SessionStatus.Info =
+ { type: "idle" }
+ end note
+
+ note right of busy
+ SessionStatus.Info =
+ { type: "busy" }
+ end note
+
+ note right of retry
+ SessionStatus.Info = {
+ type: "retry",
+ attempt: number,
+ message: string,
+ next: number // 下次重试时间戳
+ }
+ end note
+```
+
+### 重试策略
+
+```mermaid
+flowchart LR
+ Error["捕获错误"] --> Check{"可重试?"}
+ Check -->|"429 / 5xx"| Retry["retry 状态"]
+ Check -->|"401 / 403 / 404"| Fail["→ idle + error"]
+ Check -->|"ContextOverflow"| Compact["→ compact"]
+
+ Retry --> Delay["sleep(delay)"]
+ Delay --> Again["attempt++\n重新 streamText()"]
+ Again --> Check
+```
+
+重试延迟采用指数退避(Exponential Backoff),由 `SessionRetry.delay(attempt, error)` 计算。对于 429 错误,会尊重 LLM 返回的 `Retry-After` 头。
+
+### 状态事件
+
+```typescript
+// session/status.ts — 事件定义
+Event.Status = BusEvent.define("session.status", z.object({
+ sessionID: SessionID.zod,
+ status: Info, // { type: "idle" } | { type: "busy" } | { type: "retry", ... }
+}))
+```
+
+---
+
+## 三、Message 生命周期
+
+### User Message 生命周期
+
+User Message 是**不可变的**——创建后不再修改。
+
+```mermaid
+stateDiagram-v2
+ [*] --> Created: SessionPrompt.prompt()
+
+ Created --> Persisted: INSERT MessageTable\nINSERT PartTable[]
+ Persisted --> Published: Bus: MessageV2.Event.Updated
+ Published --> [*]: ✅ 不可变,生命周期结束
+
+ note right of Created
+ MessageV2.User {
+ role: "user",
+ id: MessageID.ascending(),
+ agent: string,
+ model: { providerID, modelID },
+ time: { created }
+ }
+ end note
+```
+
+### Assistant Message 生命周期
+
+Assistant Message 是**可变的**——在 LLM 流式返回过程中不断更新。
+
+```mermaid
+stateDiagram-v2
+ [*] --> Created: Processor 创建空 Assistant
+
+ Created --> Streaming: LLM 开始返回
+
+ state Streaming {
+ [*] --> Reasoning: reasoning-start
+ Reasoning --> TextGen: reasoning-end → text-start
+ [*] --> TextGen: text-start (无 reasoning)
+ TextGen --> ToolCalling: tool-input-start
+ ToolCalling --> TextGen: tool-result → text-start
+ ToolCalling --> StepDone: step-finish
+ TextGen --> StepDone: step-finish
+ StepDone --> [*]: finish
+ StepDone --> TextGen: 新的 step (多轮迭代)
+ }
+
+ Streaming --> Completed: finish 事件\ntime.completed = now
+ Streaming --> Error: 不可重试错误\nerror = DiscriminatedError
+ Streaming --> Retrying: 可重试错误\nRetryPart 插入
+
+ Retrying --> Streaming: 延迟后重新开始
+
+ Completed --> [*]: ✅ 最终状态
+ Error --> [*]: ❌ 错误终态
+
+ note right of Completed
+ Assistant {
+ time: { created, completed },
+ cost: number,
+ tokens: { input, output, reasoning,
+ cache: { read, write } },
+ finish: "stop" | "tool-calls" | ...
+ }
+ end note
+
+ note right of Error
+ error 字段可能是:
+ · APIError (含 statusCode)
+ · AuthError (认证失败)
+ · AbortedError (用户取消)
+ · ContextOverflowError
+ · StructuredOutputError
+ end note
+```
+
+### Assistant Message 累积更新
+
+在 `Streaming` 状态内,`cost` 和 `tokens` 在每个 `step-finish` 时累加:
+
+```typescript
+// session/processor.ts — step-finish 处理
+case "step-finish": {
+ msg.cost += event.cost
+ msg.tokens.input += event.tokens.input
+ msg.tokens.output += event.tokens.output
+ msg.tokens.reasoning += event.tokens.reasoning
+ msg.tokens.cache.read += event.tokens.cache.read
+ msg.tokens.cache.write += event.tokens.cache.write
+}
+```
+
+---
+
+## 四、ToolPart 状态机
+
+每个工具调用在 `PartTable` 中是一个 `ToolPart`,拥有独立的 4 态状态机。
+
+```mermaid
+stateDiagram-v2
+ [*] --> pending: tool-input-start\n开始接收工具输入
+
+ pending --> running: tool-call\n输入完整,开始执行
+
+ running --> completed: tool-result\n执行成功
+ running --> error: tool-error\n执行失败
+ running --> error: Permission.RejectedError\n用户拒绝权限
+ running --> error: Question.RejectedError\n用户拒绝确认
+
+ completed --> [*]: ✅
+ error --> [*]: ❌
+
+ note right of pending
+ ToolStatePending {
+ status: "pending",
+ input: {},
+ raw: "" // 原始 JSON 输入
+ }
+ end note
+
+ note right of running
+ ToolStateRunning {
+ status: "running",
+ input: Record<string, any>,
+ title?: string,
+ time: { start: number }
+ }
+ end note
+
+ note right of completed
+ ToolStateCompleted {
+ status: "completed",
+ input, output: string,
+ title: string,
+ time: { start, end, compacted? },
+ attachments?: FilePart[]
+ }
+ end note
+
+ note right of error
+ ToolStateError {
+ status: "error",
+ input, error: string,
+ time: { start, end }
+ }
+ end note
+```
+
+### Doom Loop 检测
+
+Processor 会检测**工具循环调用**——如果最近 3 次工具调用的名称和输入完全相同,则触发 `doom_loop` 权限请求:
+
+```mermaid
+flowchart LR
+ TC1["tool-call #N-2
name: edit, input: {...}"] --> Check
+ TC2["tool-call #N-1
name: edit, input: {...}"] --> Check
+ TC3["tool-call #N
name: edit, input: {...}"] --> Check{"三次
完全相同?"}
+ Check -->|"是"| Ask["请求 doom_loop 权限"]
+ Ask -->|"允许"| Continue["继续执行"]
+ Ask -->|"拒绝"| Block["blocked = true\n→ stop"]
+ Check -->|"否"| Continue
+```
+
+---
+
+## 五、Provider 连接状态
+
+```mermaid
+stateDiagram-v2
+ [*] --> Unconfigured: 未设置 API Key
+
+ Unconfigured --> Methods: ProviderAuth.methods()
+ Methods --> Authorizing: authorize(providerID, method)
+
+ state Authorizing {
+ [*] --> OAuthFlow: type: "oauth"
+ [*] --> APIKeyFlow: type: "api"
+ OAuthFlow --> Callback: 用户完成 OAuth
+ APIKeyFlow --> [*]: key 直接验证
+ Callback --> [*]: access_token 获取成功
+ }
+
+ Authorizing --> Ready: 认证成功
+ Authorizing --> AuthError: 认证失败
+
+ Ready --> Streaming: streamText() 调用
+ Streaming --> Ready: 成功完成
+ Streaming --> RateLimited: 429 Too Many Requests
+ Streaming --> ServerError: 500-599
+ Streaming --> AuthError: 401 / 403
+
+ RateLimited --> Streaming: Retry-After 后重试
+ ServerError --> Streaming: 指数退避后重试
+
+ AuthError --> Unconfigured: 重新认证
+
+ note right of Ready
+ Provider 已认证
+ 可以发起 API 调用
+ end note
+
+ note right of RateLimited
+ 尊重 Retry-After 头
+ 由 SessionRetry 控制延迟
+ end note
+```
+
+### 上下文溢出特殊处理
+
+`ContextOverflowError` 不走 retry 路径,而是触发**消息压缩**:
+
+```mermaid
+flowchart LR
+ Overflow["ContextOverflowError
检测 26+ 种错误模式"] --> Flag["needsCompaction = true"]
+ Flag --> Compact["SessionCompaction.create()"]
+ Compact --> Resume["压缩后重新发送
减少的消息列表"]
+```
+
+支持的溢出错误模式覆盖所有主要 Provider(Anthropic / OpenAI / Google / Bedrock / xAI / Groq 等),共识别 **26+ 种**不同的错误消息格式。
+
+---
+
+## 六、状态联动总览
+
+所有状态机并非孤立运行。下面的时序图展示了一次包含工具调用的完整状态流转:
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant Session as 🗂️ Session
+ participant Status as 🚦 Status
+ participant UserMsg as 💬 UserMsg
+ participant AssistMsg as 🤖 AssistMsg
+ participant Tool as 🔧 ToolPart
+
+ Note over Session: Active
+
+ UserMsg->>UserMsg: Created → Persisted
+
+ Status->>Status: idle → busy
+ AssistMsg->>AssistMsg: Created (time.completed = undefined)
+
+ AssistMsg->>AssistMsg: Streaming: TextPart 增长中
+
+ AssistMsg->>Tool: tool-input-start
+ Tool->>Tool: → pending
+
+ AssistMsg->>Tool: tool-call
+ Tool->>Tool: → running
+
+ Tool->>Tool: 执行完成 → completed
+ Tool->>AssistMsg: 结果返回
+
+ AssistMsg->>AssistMsg: step-finish (tokens 累加)
+
+ AssistMsg->>AssistMsg: finish → Completed
+ Status->>Status: busy → idle
+
+ Note over Session: touch() → time.updated
+```
+
+---
+
+## 关键设计决策
+
+### 1. 为什么 SessionStatus 不存数据库?
+
+`SessionStatus`(idle/busy/retry)是纯运行时状态:
+
+- **重启恢复**:进程重启后所有 Session 自然回到 `idle`,不需要数据库恢复
+- **性能**:每次 `text-delta` 都可能更新状态,走数据库太重
+- **内存高效**:用 `Map` 存储,`idle` 状态从 Map 中移除
+
+### 2. 为什么 ToolPart 有 `pending` 状态?
+
+LLM 流式返回工具调用时,先发送 `tool-input-start`(开始接收 JSON 输入),然后逐步传输输入参数,最后发送 `tool-call`(输入完整)。`pending` 状态存在于这个"输入还在传输中"的窗口期,让 UI 可以显示"正在组装工具参数…"。
+
+### 3. 为什么错误类型是区分联合(Discriminated Union)?
+
+```typescript
+type Error = APIError | AuthError | AbortedError | ContextOverflowError | StructuredOutputError
+```
+
+不同错误类型需要不同的处理策略:
+- `APIError(isRetryable: true)` → 重试
+- `ContextOverflowError` → 压缩上下文
+- `AuthError` → 提示用户重新认证
+- `AbortedError` → 静默处理
+
+使用区分联合让 TypeScript 编译器强制我们处理每种情况。
+
+---
+
+## 与下一节的衔接
+
+我们已经从**数据流**和**状态机**两个角度理解了 OpenCode 的运行机制。但一个更实际的问题是:如果我想扩展 OpenCode——加一个自定义 Agent、写一个工具、接入一个 MCP Server——从哪里下手?
+
+下一节 [09-扩展点全景图](./09-扩展点全景图.md) 将列出 OpenCode 所有可定制的扩展点,每个扩展点附带配置方式、文件位置和上手难度。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/09-\346\211\251\345\261\225\347\202\271\345\205\250\346\231\257\345\233\276.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/09-\346\211\251\345\261\225\347\202\271\345\205\250\346\231\257\345\233\276.md"
new file mode 100644
index 000000000000..1c35ce68b2fd
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/09-\346\211\251\345\261\225\347\202\271\345\205\250\346\231\257\345\233\276.md"
@@ -0,0 +1,364 @@
+# 扩展点全景图
+
+> 📌 一句话总结:OpenCode 提供 9 大扩展点——从零代码的 Markdown Agent 到完整的 Plugin Hook 系统,覆盖"配置即扩展"到"代码级深度定制"的全部场景。
+> 🗺️ 本节在全景中的位置:前两节从数据流和状态机角度理解了系统内部。本节转向**外部视角**——作为使用者或贡献者,你能在哪些地方"插入"自己的逻辑?
+
+---
+
+## 全景图:扩展点辐射图
+
+```mermaid
+flowchart TB
+ Core["🏠 OpenCode Core
packages/opencode/"]
+
+ Agent["🤖 Custom Agent
.opencode/agent/*.md"]
+ Tool["🔧 Custom Tool
.opencode/tool/*.ts"]
+ Command["📝 Custom Command
.opencode/command/*.md"]
+ MCP["🔌 MCP Server
config: mcp"]
+ Plugin["🧩 Plugin
config: plugin"]
+ Provider["☁️ Provider
config: provider"]
+ Theme["🎨 Theme
.opencode/themes/*.json"]
+ VSCode["💻 VSCode Extension
sdks/vscode/"]
+ Skill["📚 Skill
.opencode/skill/SKILL.md"]
+
+ Core --- Agent
+ Core --- Tool
+ Core --- Command
+ Core --- MCP
+ Core --- Plugin
+ Core --- Provider
+ Core --- Theme
+ Core --- VSCode
+ Core --- Skill
+
+ style Agent fill:#d4edda
+ style Command fill:#d4edda
+ style Skill fill:#d4edda
+ style Theme fill:#d4edda
+ style Tool fill:#fff3cd
+ style MCP fill:#fff3cd
+ style Provider fill:#fff3cd
+ style Plugin fill:#f8d7da
+ style VSCode fill:#f8d7da
+```
+
+> 🟢 绿色 = 零代码(Markdown/JSON) · 🟡 黄色 = 少量代码/配置 · 🔴 红色 = 需要开发
+
+---
+
+## 各扩展点详解
+
+### 1. 🤖 Custom Agent(自定义 Agent)🟢
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | Markdown 文件 + YAML frontmatter |
+| **文件位置** | `.opencode/agent/*.md` 或 `.opencode/agents/*.md` |
+| **源码入口** | `config/config.ts` → `loadAgent()` 函数 |
+| **难度** | 🟢 零代码 |
+
+```markdown
+---
+description: "代码审查专家"
+mode: "primary"
+model: "anthropic/claude-sonnet-4-20250514"
+temperature: 0.3
+color: "#38A3EE"
+permission:
+ edit: "deny"
+ bash: "deny"
+steps: 50
+---
+
+你是一位资深代码审查专家。请仔细审查用户提交的代码...
+```
+
+**frontmatter 字段**:`description`、`mode`(`"subagent"` | `"primary"` | `"all"`)、`model`、`temperature`、`top_p`、`color`、`permission`、`hidden`、`steps`、`options`。
+
+文件名即为 Agent 名称:`docs.md` → Agent `docs`。
+
+---
+
+### 2. 📝 Custom Command(自定义命令)🟢
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | Markdown 文件 + YAML frontmatter |
+| **文件位置** | `.opencode/command/*.md` 或 `.opencode/commands/*.md` |
+| **源码入口** | `config/config.ts` → `loadCommand()` 函数 |
+| **难度** | 🟢 零代码 |
+
+```markdown
+---
+description: "查找 GitHub issue"
+model: "anthropic/claude-haiku-4-5-20250501"
+agent: "coder"
+subtask: false
+---
+
+在 GitHub 上搜索以下关键词相关的 issue:$ARGUMENTS
+```
+
+**模板变量**:`$1`、`$2`…(位置参数)、`$ARGUMENTS`(全部参数)。
+
+内置命令包括 `init` 和 `review`。
+
+---
+
+### 3. 📚 Skill(技能文件)🟢
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | Markdown 文件,文件名含 `SKILL` |
+| **文件位置** | `.opencode/skill/SKILL.md`、`.opencode/skills/` |
+| **源码入口** | `skill/index.ts` → `loadSkills()` |
+| **难度** | 🟢 零代码 |
+
+```json
+// opencode.json
+{
+ "skills": {
+ "paths": ["./skills", "~/shared-skills"],
+ "urls": ["https://example.com/.well-known/skills/"]
+ }
+}
+```
+
+Skill 文件会被注入到 Agent 的系统提示词(System Prompt)中,扫描模式为 `**/SKILL.md`。支持本地路径和远程 URL。
+
+---
+
+### 4. 🎨 Theme(主题)🟢
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | JSON 文件 |
+| **文件位置** | `.opencode/themes/*.json` |
+| **源码入口** | `cli/cmd/tui/context/theme.tsx` |
+| **难度** | 🟢 零代码 |
+
+```json
+{
+ "$schema": "https://opencode.ai/theme.json",
+ "defs": {
+ "blue": "#38A3EE",
+ "dark": "#1a1a2e"
+ },
+ "theme": {
+ "primary": { "dark": "blue", "light": "blue" },
+ "background": { "dark": "dark", "light": "#ffffff" },
+ "text": { "dark": "#e0e0e0", "light": "#1a1a1a" },
+ "border": { "dark": "#333333", "light": "#cccccc" },
+ "diffAdded": { "dark": "#2ea043", "light": "#1a7f37" },
+ "diffRemoved": { "dark": "#f85149", "light": "#cf222e" }
+ }
+}
+```
+
+在 TUI 中通过 `t` 打开主题选择器。
+
+---
+
+### 5. 🔧 Custom Tool(自定义工具)🟡
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | TypeScript/JavaScript 文件,使用 `@opencode-ai/plugin` API |
+| **文件位置** | `.opencode/tool/*.ts` 或 `.opencode/tools/*.ts` |
+| **源码入口** | `tool/registry.ts` → 扫描 `{tool,tools}/*.{js,ts}` |
+| **难度** | 🟡 需少量 TypeScript |
+
+```typescript
+import { tool } from "@opencode-ai/plugin"
+
+export default tool({
+ description: "搜索 GitHub PR",
+ args: {
+ query: tool.schema.string().describe("搜索关键词"),
+ limit: tool.schema.number().optional().describe("结果数量"),
+ },
+ execute: async (args, ctx) => {
+ const result = await fetch(`https://api.github.com/search/issues?q=${args.query}+is:pr`)
+ return JSON.stringify(await result.json())
+ },
+})
+```
+
+支持默认导出(单工具)和命名导出(多工具)。
+
+---
+
+### 6. 🔌 MCP Server(模型上下文协议)🟡
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | `opencode.json` 中的 `mcp` 字段 |
+| **文件位置** | 配置级,无需项目内文件 |
+| **源码入口** | `mcp/index.ts` → `create()` 函数 |
+| **难度** | 🟡 需配置 + 外部 MCP Server |
+
+```json
+{
+ "mcp": {
+ "my-local-server": {
+ "type": "local",
+ "command": ["node", "mcp-server.js"],
+ "environment": { "API_KEY": "..." },
+ "enabled": true,
+ "timeout": 5000
+ },
+ "remote-api": {
+ "type": "remote",
+ "url": "https://api.example.com/mcp",
+ "headers": { "Authorization": "Bearer ..." },
+ "oauth": { "clientId": "..." }
+ }
+ }
+}
+```
+
+支持两种传输:**Local**(Stdio 子进程)和 **Remote**(HTTP/SSE,支持 OAuth)。MCP Server 暴露的工具自动注册到 Agent 可用工具列表中。
+
+---
+
+### 7. ☁️ Provider(AI 提供商)🟡
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | `opencode.json` 中的 `provider` 字段 |
+| **文件位置** | 配置级 |
+| **源码入口** | `provider/provider.ts` |
+| **难度** | 🟡 需 API Key |
+
+```json
+{
+ "provider": {
+ "anthropic": { "apiKey": "sk-ant-..." },
+ "openai": { "apiKey": "sk-...", "timeout": 300000 }
+ },
+ "model": "anthropic/claude-sonnet-4-20250514",
+ "small_model": "anthropic/claude-haiku-4-5-20250501",
+ "enabled_providers": ["anthropic", "openai"],
+ "disabled_providers": ["openrouter"]
+}
+```
+
+内置 Provider 覆盖 **18+** 个:Anthropic、OpenAI、Azure、Google、Vertex、Bedrock、Groq、Mistral、xAI、Cohere、Cerebras、DeepInfra、TogetherAI、Perplexity、Vercel、OpenRouter、GitLab、Gateway。通过 `models.dev` 注册表获取模型元数据。
+
+---
+
+### 8. 🧩 Plugin(插件系统)🔴
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | `opencode.json` 中的 `plugin` 数组 |
+| **文件位置** | NPM 包 或 `file://` 本地路径 |
+| **源码入口** | `plugin/index.ts` → Hook 系统 |
+| **难度** | 🔴 需完整开发 |
+
+```json
+{
+ "plugin": [
+ "opencode-github",
+ "oh-my-opencode@1.2.3",
+ "file:///path/to/local/plugin.ts"
+ ]
+}
+```
+
+**可用 Hook 列表**:
+
+| Hook | 时机 | 用途 |
+|---|---|---|
+| `config` | 配置加载后 | 修改运行时配置 |
+| `event` | 任何事件触发时 | 监听系统事件 |
+| `tool` | 工具注册 | 注册自定义工具 |
+| `auth` | 认证流程 | 自定义认证方式 |
+| `chat.message` | 消息发送前 | 修改消息内容 |
+| `chat.params` | API 参数构建时 | 修改 API 参数 |
+| `chat.headers` | API 请求发送前 | 添加自定义 HTTP 头 |
+| `permission.ask` | 权限请求时 | 自定义权限逻辑 |
+| `tool.execute.before` | 工具执行前 | 拦截工具调用 |
+| `tool.execute.after` | 工具执行后 | 后处理工具结果 |
+| `tool.definition` | 工具定义时 | 修改工具描述 |
+| `shell.env` | Shell 环境构建时 | 注入环境变量 |
+| `experimental.chat.messages.transform` | 消息转换时 | 实验性消息变换 |
+| `experimental.chat.system.transform` | 系统提示时 | 实验性提示词变换 |
+| `experimental.text.complete` | 文本生成完成后 | 后处理文本输出 |
+
+内置插件包括:`CodexAuthPlugin`、`CopilotAuthPlugin`、`GitlabAuthPlugin`、`PoeAuthPlugin`。
+
+---
+
+### 9. 💻 VSCode Extension 🔴
+
+| 属性 | 说明 |
+|---|---|
+| **配置方式** | VSCode 市场安装 |
+| **文件位置** | `sdks/vscode/` |
+| **源码入口** | `sdks/vscode/src/extension.ts` |
+| **难度** | 🔴 需 VSCode 扩展开发 |
+
+提供 3 个命令:
+- `opencode.openTerminal` — 在 VSCode 终端中打开 OpenCode(`Cmd+Escape`)
+- `opencode.openNewTerminal` — 新标签页打开(`Cmd+Shift+Escape`)
+- `opencode.addFilepathToTerminal` — 将当前文件路径发送到 OpenCode(`Cmd+Alt+K`)
+
+通过 HTTP 与 OpenCode Server 的 `/tui/append-prompt` 端点通信。
+
+---
+
+## 配置文件优先级
+
+从高到低,后者被前者覆盖:
+
+```mermaid
+flowchart TB
+ M["🏢 Managed Config
/etc/opencode/opencode.json
(企业管控)"] --> I
+ I["🔧 Inline Config
OPENCODE_CONFIG_CONTENT 环境变量"] --> C
+ C["☁️ Console Config
从已连接账户获取"] --> D
+ D["📁 .opencode 目录
.opencode/opencode.json"] --> P
+ P["📄 Project Config
opencode.json(项目根目录)"] --> E
+ E["🌍 ENV Config
OPENCODE_CONFIG 环境变量"] --> G
+ G["🏠 Global Config
~/.config/opencode/opencode.json"]
+```
+
+---
+
+## 扩展点速查矩阵
+
+| 扩展点 | 零代码? | 热重载? | 对 Agent 可见? | 需发布? |
+|---|---|---|---|---|
+| Custom Agent | ✅ | ✅ | — (本身即 Agent) | ❌ |
+| Custom Command | ✅ | ✅ | ❌ | ❌ |
+| Skill | ✅ | ✅ | ✅ (系统提示词) | ❌ |
+| Theme | ✅ | ✅ | ❌ | ❌ |
+| Custom Tool | ❌ (TS) | ✅ | ✅ (工具列表) | ❌ |
+| MCP Server | ❌ (配置) | ✅ | ✅ (工具列表) | ❌ |
+| Provider | ❌ (API Key) | ✅ | ❌ | ❌ |
+| Plugin | ❌ (TS/NPM) | ❌ | ✅ (视 Hook) | 可选 |
+| VSCode Ext | ❌ (TS) | ❌ | ❌ | ✅ |
+
+---
+
+## 关键设计决策
+
+### 1. 为什么 Agent 和 Command 用 Markdown?
+
+降低创建门槛——任何人都能用文本编辑器创建自定义 Agent,不需要理解 TypeScript 或构建系统。YAML frontmatter 提供结构化配置,Markdown body 就是 prompt。
+
+### 2. 为什么 Plugin 用 Hook 而不是继承/接口?
+
+Hook 模式(`before/after`)比 OOP 继承更灵活:多个插件可以叠加同一个 Hook,且不会互相干扰。这也是 Webpack/Vite 等工具链验证过的模式。
+
+### 3. 为什么同时支持 MCP 和 Custom Tool?
+
+MCP(Model Context Protocol)是跨应用的标准协议——一个 MCP Server 可以同时被 OpenCode、Claude Desktop 等多个客户端使用。Custom Tool 则是 OpenCode 专属的、更轻量的方案。两者互补。
+
+---
+
+## 与下一节的衔接
+
+了解了"能扩展什么"之后,下一个问题是"底层用了什么技术"。为什么选 Bun 而不是 Node.js?为什么用 Effect-TS 而不是普通 async/await?
+
+下一节 [10-技术栈全景与选型理由](./10-技术栈全景与选型理由.md) 将从技术选型的角度,解释 OpenCode 每一层技术栈的选择理由。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/10-\346\212\200\346\234\257\346\240\210\345\205\250\346\231\257\344\270\216\351\200\211\345\236\213\347\220\206\347\224\261.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/10-\346\212\200\346\234\257\346\240\210\345\205\250\346\231\257\344\270\216\351\200\211\345\236\213\347\220\206\347\224\261.md"
new file mode 100644
index 000000000000..cf22ec0b41ca
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/10-\346\212\200\346\234\257\346\240\210\345\205\250\346\231\257\344\270\216\351\200\211\345\236\213\347\220\206\347\224\261.md"
@@ -0,0 +1,340 @@
+# 技术栈全景与选型理由
+
+> 📌 一句话总结:OpenCode 的技术栈从上到下都围绕两个原则——**开发者体验优先**和**类型安全到底**,每一层选型都有明确的"为什么不用 X"。
+> 🗺️ 本节在全景中的位置:前一节列出了 OpenCode 的所有扩展点。本节退后一步,从**技术选型**角度解释整个系统为什么长成这个样子。
+
+---
+
+## 全景图:技术栈分层
+
+```mermaid
+flowchart TB
+ subgraph Infra["基础设施层"]
+ SST["☁️ SST v3.18
Infrastructure as Code"]
+ CF["⚡ Cloudflare
Workers · R2"]
+ Nix["❄️ Nix Flake
可复现构建"]
+ end
+
+ subgraph Monorepo["工程管理层"]
+ Turbo["🔄 Turborepo v2.8
Monorepo 任务编排"]
+ Bun["🥟 Bun v1.3
运行时 + 包管理"]
+ TS["📘 TypeScript v5.8
类型系统"]
+ end
+
+ subgraph Core["核心引擎层"]
+ Effect["⚡ Effect-TS v4.0-beta
函数式效果管理"]
+ AI["🤖 Vercel AI SDK v5.0
多 Provider 统一接口"]
+ MCP2["🔌 MCP SDK v1.27
模型上下文协议"]
+ Drizzle["🗃️ Drizzle ORM v1.0-beta
+ SQLite"]
+ Hono["🌐 Hono v4.10
HTTP Server"]
+ Zod["🛡️ Zod v4.1
运行时类型验证"]
+ end
+
+ subgraph UI["用户界面层"]
+ Solid["💎 SolidJS v1.9
响应式 UI"]
+ OpenTUI["📺 OpenTUI v0.1.90
终端 UI 框架"]
+ Kobalte["🧱 Kobalte v0.13
无头 UI 组件"]
+ TW["🎨 Tailwind CSS v4.1
原子化样式"]
+ Vite2["⚡ Vite v7.1
前端构建"]
+ end
+
+ subgraph Desktop["桌面应用层"]
+ Tauri["🦀 Tauri v2
Rust 桌面框架"]
+ Electron["⚛️ Electron v40
Node.js 桌面框架"]
+ end
+
+ Infra --> Monorepo --> Core --> UI --> Desktop
+```
+
+---
+
+## 逐层选型分析
+
+### 🥟 Runtime: Bun v1.3.11
+
+**为什么不用 Node.js?**
+
+| 维度 | Bun | Node.js |
+|---|---|---|
+| 启动速度 | ~50ms | ~200-500ms |
+| 包管理 | 内置(极快) | 需要 npm/yarn/pnpm |
+| TypeScript | 原生运行 `.ts` | 需要 `tsx` / `ts-node` |
+| SQLite | 内置 `bun:sqlite` | 需要 `better-sqlite3` |
+| 文件 API | `Bun.file()` / `Bun.write()` | `fs.readFile()` / `fs.writeFile()` |
+| 测试 | 内置 `bun test` | 需要 Jest / Vitest |
+
+OpenCode 是一个 CLI 工具——**启动速度至关重要**。Bun 的内置 SQLite 驱动(`bun:sqlite`)消除了原生模块编译问题,`Bun.file()` API 更符合现代 TypeScript 风格。
+
+```toml
+# bunfig.toml
+[install]
+exact = true # 锁定精确版本
+
+[test]
+root = "./do-not-run-tests-from-root" # 防止从根目录运行测试
+```
+
+---
+
+### 📘 Language: TypeScript v5.8.2
+
+**为什么不用 Go / Rust?**
+
+| 维度 | TypeScript | Go | Rust |
+|---|---|---|---|
+| AI SDK 生态 | ✅ 最丰富 | ⚠️ 有限 | ⚠️ 有限 |
+| JSON 处理 | 原生 | 需要 struct tag | 需要 serde |
+| Plugin 生态 | NPM (最大) | 需要 CGo 或 gRPC | 需要 FFI |
+| 前后端统一 | ✅ SolidJS + 后端 | ❌ 前端另选 | ❌ 前端另选 |
+| 类型安全 | ✅ (with Effect + Zod) | ✅ (编译期) | ✅✅ (编译期) |
+
+关键因素是 **AI SDK 生态**——Vercel AI SDK、LangChain、OpenAI SDK 等都以 TypeScript 为第一等公民。选择 TypeScript 意味着可以直接使用 18+ 个 AI Provider 的官方 SDK。
+
+---
+
+### ⚡ Effect Management: Effect-TS v4.0.0-beta.35
+
+**为什么不用普通 async/await?**
+
+```typescript
+// 普通 async/await — 错误类型丢失
+async function getSession(id: string): Promise {
+ // throw 什么?谁知道呢…
+}
+
+// Effect-TS — 错误类型是签名的一部分
+function getSession(id: string): Effect {
+ // 调用方被强制处理两种错误
+}
+```
+
+Effect-TS 在 OpenCode 中主要用于:
+
+1. **依赖注入**(Service Layer)——`Bus.Service`、`Database.Service` 等
+2. **错误追踪**——`NamedError` 体系让错误类型可追踪
+3. **资源管理**——`Effect.acquireRelease` 确保数据库连接、子进程等资源正确释放
+4. **PubSub**——`Effect.PubSub` 提供带背压的事件总线
+
+---
+
+### 🤖 AI Integration: Vercel AI SDK v5.0.124
+
+**为什么不用 LangChain?**
+
+| 维度 | Vercel AI SDK | LangChain |
+|---|---|---|
+| 抽象层级 | 薄——直接映射 API | 厚——Chain/Agent/Memory 抽象 |
+| Provider 支持 | 官方维护 18+ Provider | 社区维护,质量不一 |
+| 流式支持 | `streamText()` 一等公民 | 需要额外配置 |
+| 体积 | 轻量 | 依赖链长 |
+| TypeScript | 原生 TS | JS + 类型声明 |
+
+OpenCode 需要**底层控制权**——自己管理 Agent 循环、工具调用、重试逻辑、上下文压缩。LangChain 的高级抽象反而会碍手碍脚。Vercel AI SDK 提供刚好够用的 Provider 统一层,其余由 OpenCode 自己实现。
+
+当前集成的 AI Provider SDK:
+
+```
+@ai-sdk/anthropic @ai-sdk/openai @ai-sdk/azure
+@ai-sdk/google @ai-sdk/google-vertex @ai-sdk/amazon-bedrock
+@ai-sdk/groq @ai-sdk/mistral @ai-sdk/xai
+@ai-sdk/cohere @ai-sdk/cerebras @ai-sdk/deepinfra
+@ai-sdk/togetherai @ai-sdk/perplexity @ai-sdk/vercel
+@ai-sdk/gateway @openrouter/ai-sdk-provider
+gitlab-ai-provider ai-gateway-provider
+```
+
+---
+
+### 🔌 Protocol: MCP SDK v1.27.1
+
+**为什么选 MCP?**
+
+MCP(Model Context Protocol)是 Anthropic 提出的开放协议,已被多个 AI 产品采用(Claude Desktop、Cursor 等)。选择 MCP 意味着:
+
+- 用户已有的 MCP Server 可直接复用
+- OpenCode 的工具也可以暴露给其他 MCP 客户端
+- 标准化的工具发现、权限协商机制
+
+同时 OpenCode 还集成了 `@agentclientprotocol/sdk v0.14.1`(Agent Client Protocol),为未来的 Agent 间通信做准备。
+
+---
+
+### 💎 TUI Framework: SolidJS v1.9.10 + OpenTUI v0.1.90
+
+**为什么不用 React + Ink?**
+
+| 维度 | SolidJS + OpenTUI | React + Ink |
+|---|---|---|
+| 响应式模型 | 细粒度 Signal | Virtual DOM + diff |
+| 终端渲染 | 直接操作终端 | Yoga 布局引擎 |
+| 性能 | O(1) 更新 | O(n) diff |
+| 与桌面共享 | ✅ 同一 UI 组件库 | ⚠️ 需要适配 |
+
+SolidJS 的细粒度响应式非常适合终端 UI——当一个 token 到达时,只需更新对应的文本节点,而不是重新 diff 整个组件树。OpenTUI 是 OpenCode 团队自建的终端 UI 框架(`@opentui/core` + `@opentui/solid`),专为这个场景设计。
+
+UI 组件库使用 **Kobalte v0.13.11**(SolidJS 的无头 UI 组件),配合 **Tailwind CSS v4.1** 处理样式。
+
+---
+
+### 🗃️ ORM: Drizzle v1.0.0-beta + SQLite
+
+**为什么不用 PostgreSQL?**
+
+```
+OpenCode 是本地 CLI 工具 → 不能要求用户安装 PostgreSQL
+ → SQLite 零配置、单文件、嵌入式
+ → Bun 内置 bun:sqlite 驱动
+```
+
+**为什么 Drizzle 不用 Prisma?**
+
+| 维度 | Drizzle | Prisma |
+|---|---|---|
+| 代码生成 | ❌ 不需要 | ✅ 需要 `prisma generate` |
+| 查询风格 | SQL-like(贴近 SQL) | 自定义 API |
+| Bundle 大小 | 轻量 | 需要 Prisma Engine |
+| TypeScript | Schema as Code | Schema DSL → 生成 |
+
+Drizzle 的"Schema as Code"哲学与项目风格契合——表定义就是 TypeScript 代码,类型推导自动完成,不需要额外的代码生成步骤。
+
+---
+
+### 🔄 Monorepo: Turborepo v2.8.13
+
+**为什么不用 Nx / Lerna?**
+
+| 维度 | Turborepo | Nx | Lerna |
+|---|---|---|---|
+| 配置复杂度 | 低(单 `turbo.json`) | 高 | 中 |
+| 缓存 | ✅ 远程+本地 | ✅ 远程+本地 | ❌ |
+| Bun 支持 | ✅ 原生 | ⚠️ 需配置 | ⚠️ 需配置 |
+| 学习曲线 | 低 | 高 | 中 |
+
+Turborepo 配置极简,与 Bun workspace 天然集成。`turbo.json` 仅定义任务依赖关系:
+
+```json
+{
+ "$schema": "https://turbo.build/schema.json",
+ "globalEnv": ["CI", "OPENCODE_DISABLE_SHARE"],
+ "tasks": {
+ "typecheck": {},
+ "build": {},
+ "opencode#test": {},
+ "@opencode-ai/app#test": {}
+ }
+}
+```
+
+---
+
+### 🦀 Desktop: Tauri v2 + Electron v40
+
+**为什么两个都有?**
+
+```mermaid
+flowchart LR
+ subgraph Tauri["🦀 Tauri v2 (packages/desktop/)"]
+ T1["✅ 体积小 (~10MB)"]
+ T2["✅ 内存占用低"]
+ T3["⚠️ 需要系统 WebView"]
+ end
+
+ subgraph Electron["⚛️ Electron v40 (packages/desktop-electron/)"]
+ E1["✅ 跨平台一致性好"]
+ E2["✅ 自带 Chromium"]
+ E3["⚠️ 体积大 (~150MB)"]
+ end
+
+ User["用户选择"] --> Tauri
+ User --> Electron
+```
+
+Tauri 是主力方案(体积小、性能好),Electron 作为兼容性后备——在某些 Linux 发行版上系统 WebView 可能有问题,Electron 自带 Chromium 规避了这个问题。两者共享同一套 SolidJS 前端代码。
+
+---
+
+### ☁️ Infrastructure: SST v3.18.10
+
+**为什么不用 CDK / Terraform?**
+
+SST(Serverless Stack)是专为 TypeScript 全栈应用设计的 IaC 框架:
+
+```typescript
+// sst.config.ts
+export default $config({
+ app(input) {
+ return {
+ name: "opencode",
+ home: "cloudflare", // 部署目标
+ providers: {
+ stripe: true,
+ planetscale: "0.4.1",
+ },
+ }
+ },
+ async run() {
+ // TypeScript 编写基础设施
+ await import("./infra/app.js")
+ await import("./infra/console.js")
+ if ($app.stage === "production")
+ await import("./infra/enterprise.js")
+ },
+})
+```
+
+- **TypeScript 原生**:与项目技术栈一致,一个语言贯穿前后端和基础设施
+- **Cloudflare 优先**:OpenCode 的 Web 服务部署在 Cloudflare Workers
+- **开发体验**:`sst dev` 提供实时 Lambda/Worker 调试
+
+---
+
+## 版本总览表
+
+| 层级 | 技术 | 版本 | 用途 |
+|---|---|---|---|
+| **Runtime** | Bun | 1.3.11 | 运行时 + 包管理 + 测试 |
+| **Language** | TypeScript | 5.8.2 | 类型系统 |
+| **Effect** | Effect-TS | 4.0.0-beta.35 | 函数式效果管理 |
+| **AI SDK** | Vercel AI SDK | 5.0.124 | 多 Provider 统一 |
+| **Protocol** | MCP SDK | 1.27.1 | 工具协议 |
+| **ORM** | Drizzle ORM | 1.0.0-beta.19 | 数据库访问 |
+| **DB** | SQLite | (Bun 内置) | 嵌入式数据库 |
+| **HTTP** | Hono | 4.10.7 | Web 框架 |
+| **Validation** | Zod | 4.1.8 | 运行时校验 |
+| **UI** | SolidJS | 1.9.10 | 响应式 UI |
+| **TUI** | OpenTUI | 0.1.90 | 终端 UI |
+| **Components** | Kobalte | 0.13.11 | 无头 UI |
+| **CSS** | Tailwind CSS | 4.1.11 | 原子化样式 |
+| **Build** | Vite | 7.1.4 | 前端构建 |
+| **Desktop** | Tauri | v2 | 桌面(主力) |
+| **Desktop** | Electron | 40.4.1 | 桌面(兼容) |
+| **Monorepo** | Turborepo | 2.8.13 | 任务编排 |
+| **IaC** | SST | 3.18.10 | 基础设施 |
+| **Website** | Astro | 5.7.13 | 文档站点 |
+
+---
+
+## 关键设计决策
+
+### 1. 为什么大量使用 Beta 版本?
+
+Effect-TS v4.0-beta、Drizzle v1.0-beta——这些库虽然版本号带 beta,但在各自社区已经广泛使用。OpenCode 团队选择"跟进最新"而非"等待稳定",因为这些 beta 版本的 API 已经足够稳定,且新特性(如 Effect 的改进错误追踪)对项目有实际价值。
+
+### 2. 全栈 TypeScript 的统一性
+
+从 TUI 到 Web 到桌面到基础设施,一个语言覆盖所有层级。这意味着:
+- 团队成员无需切换语言上下文
+- 类型定义可以跨包共享(`workspace:*`)
+- 一套工具链(Bun + Turbo)管理全部代码
+
+### 3. "薄封装"哲学
+
+OpenCode 倾向于选择提供薄封装的库(Vercel AI SDK vs LangChain、Drizzle vs Prisma、Hono vs Express),保留底层控制权。这在 AI 领域尤其重要——Provider API 变化快,薄封装层更容易跟进。
+
+---
+
+## 与下一节的衔接
+
+我们已经从扩展点和技术栈两个角度完成了全景扫描。但如果你只是想快速找到"我想做 X,应该看哪一章"的答案呢?
+
+下一节 [11-速查:我想做X应该看哪章](./11-速查:我想做X应该看哪章.md) 提供一张速查表,覆盖 20+ 种常见场景,直接指向对应章节。
diff --git "a/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/11-\351\200\237\346\237\245\357\274\232\346\210\221\346\203\263\345\201\232X\345\272\224\350\257\245\347\234\213\345\223\252\347\253\240.md" "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/11-\351\200\237\346\237\245\357\274\232\346\210\221\346\203\263\345\201\232X\345\272\224\350\257\245\347\234\213\345\223\252\347\253\240.md"
new file mode 100644
index 000000000000..9bae58f4045e
--- /dev/null
+++ "b/all-in-one-book/01-\345\205\250\346\231\257\350\247\206\351\207\216/11-\351\200\237\346\237\245\357\274\232\346\210\221\346\203\263\345\201\232X\345\272\224\350\257\245\347\234\213\345\223\252\347\253\240.md"
@@ -0,0 +1,155 @@
+# 速查:我想做X应该看哪章
+
+> 📌 一句话总结:不想从头读?这张表帮你直接跳到需要的章节——覆盖用户、开发者、贡献者、企业管理员四种角色的 20+ 常见场景。
+> 🗺️ 本节在全景中的位置:本节是"01-全景视野"的收尾,把前面 10 节的内容浓缩为一张可检索的速查表,作为全书的导航入口。
+
+---
+
+## 全景图:读者角色与章节映射
+
+```mermaid
+flowchart LR
+ subgraph Roles["读者角色"]
+ U["👤 用户
日常使用"]
+ D["🔧 开发者
扩展定制"]
+ C["🤝 贡献者
贡献代码"]
+ E["🏢 管理员
企业部署"]
+ end
+
+ subgraph Chapters["核心章节"]
+ C02["02-快速上手"]
+ C03["03-关键路径详解"]
+ C04["04-核心引擎"]
+ C05["05-用户界面"]
+ C06["06-扩展与集成"]
+ C07["07-基础设施与部署"]
+ C08["08-开发者指南"]
+ C09["09-周边知识与生态"]
+ C10["10-实战案例"]
+ end
+
+ U --> C02
+ U --> C10
+ D --> C06
+ D --> C04
+ C --> C08
+ C --> C03
+ E --> C07
+ E --> C06
+```
+
+---
+
+## 速查表
+
+### 👤 用户场景
+
+| 我想要… | 直接去读 | 先了解 |
+|---|---|---|
+| 安装并跑起来 | 02-快速上手 | — |
+| 了解 OpenCode 能做什么 | 01-全景视野/01-一图看懂OpenCode | — |
+| 理解一次对话背后发生了什么 | 01-全景视野/04-一次对话的完整旅程 | 01-全景视野/02-核心概念关系图谱 |
+| 自定义一个 Agent | 06-扩展与集成(Agent 章节) | 01-全景视野/09-扩展点全景图 |
+| 添加自定义命令 | 06-扩展与集成(Command 章节) | 01-全景视野/09-扩展点全景图 |
+| 切换 AI 模型 / Provider | 02-快速上手(Provider 配置) | 01-全景视野/10-技术栈全景与选型理由 |
+| 配置 MCP Server | 06-扩展与集成(MCP 章节) | 01-全景视野/09-扩展点全景图 |
+| 自定义主题 | 06-扩展与集成(Theme 章节) | — |
+| 在 VSCode 中使用 | 06-扩展与集成(VSCode 章节) | — |
+| 了解权限系统 | 03-关键路径详解(Permission 章节) | 01-全景视野/05-一次Tool调用的完整旅程 |
+
+### 🔧 开发者场景
+
+| 我想要… | 直接去读 | 先了解 |
+|---|---|---|
+| 写一个自定义 Tool | 06-扩展与集成(Custom Tool 章节) | 01-全景视野/05-一次Tool调用的完整旅程 |
+| 开发一个 Plugin | 06-扩展与集成(Plugin 章节) | 01-全景视野/09-扩展点全景图 |
+| 理解消息格式和数据流 | 01-全景视野/07-数据流全景图 | 01-全景视野/02-核心概念关系图谱 |
+| 理解 Session 状态机 | 01-全景视野/08-状态生命周期全景图 | 01-全景视野/04-一次对话的完整旅程 |
+| 对接新的 AI Provider | 04-核心引擎(Provider 章节) | 01-全景视野/10-技术栈全景与选型理由 |
+| 理解 Effect-TS 用法 | 09-周边知识与生态(Effect-TS 章节) | 01-全景视野/10-技术栈全景与选型理由 |
+| 理解数据库 Schema | 04-核心引擎(Storage 章节) | 01-全景视野/07-数据流全景图 |
+| 理解事件总线机制 | 04-核心引擎(Bus 章节) | 01-全景视野/07-数据流全景图 |
+| 使用 OpenCode SDK | 06-扩展与集成(SDK 章节) | 01-全景视野/03-Monorepo全景 |
+
+### 🤝 贡献者场景
+
+| 我想要… | 直接去读 | 先了解 |
+|---|---|---|
+| 搭建本地开发环境 | 08-开发者指南(环境搭建) | 01-全景视野/03-Monorepo全景 |
+| 理解 Monorepo 结构 | 01-全景视野/03-Monorepo全景-19个包的关系 | — |
+| 运行测试 | 08-开发者指南(测试章节) | 01-全景视野/10-技术栈全景与选型理由 |
+| 理解文件编辑的安全机制 | 01-全景视野/06-一次文件编辑的完整旅程 | 01-全景视野/05-一次Tool调用的完整旅程 |
+| 理解上下文压缩(Compaction) | 04-核心引擎(Compaction 章节) | 01-全景视野/08-状态生命周期全景图 |
+| 给 TUI 加新功能 | 05-用户界面(TUI 章节) | 01-全景视野/10-技术栈全景与选型理由(SolidJS + OpenTUI) |
+| 理解 Server API 端点 | 04-核心引擎(Server 章节) | 01-全景视野/07-数据流全景图 |
+| 构建桌面应用 | 05-用户界面(Desktop 章节) | 01-全景视野/10-技术栈全景与选型理由(Tauri vs Electron) |
+
+### 🏢 企业管理员场景
+
+| 我想要… | 直接去读 | 先了解 |
+|---|---|---|
+| 企业级部署 | 07-基础设施与部署 | 01-全景视野/10-技术栈全景与选型理由(SST) |
+| 配置全局权限策略 | 06-扩展与集成(Permission 章节) | 01-全景视野/09-扩展点全景图(配置优先级) |
+| Managed Config 管控 | 07-基础设施与部署(企业配置) | 01-全景视野/09-扩展点全景图 |
+| 理解安全模型 | 03-关键路径详解(安全章节) | 01-全景视野/05-一次Tool调用的完整旅程 |
+
+---
+
+## 按知识域速查
+
+| 知识域 | 入门 | 进阶 | 深入 |
+|---|---|---|---|
+| **架构总览** | 01-01 一图看懂 | 01-02 概念图谱 | 01-03 Monorepo 全景 |
+| **对话流程** | 01-04 对话旅程 | 01-07 数据流全景图 | 04 核心引擎 |
+| **工具系统** | 01-05 Tool 调用旅程 | 01-06 文件编辑旅程 | 04 核心引擎(Tool 章节) |
+| **状态管理** | 01-08 状态生命周期 | 04 核心引擎(Session) | 04 核心引擎(Processor) |
+| **扩展定制** | 01-09 扩展点全景 | 06 扩展与集成 | Plugin 开发实战 |
+| **技术选型** | 01-10 技术栈全景 | 09 周边知识 | 各技术官方文档 |
+| **UI 开发** | 05 用户界面 | SolidJS + OpenTUI | 05 深入章节 |
+| **部署运维** | 07 基础设施 | SST + Cloudflare | 07 企业部署 |
+
+---
+
+## 阅读路径推荐
+
+### 🚀 "我赶时间"(30 分钟)
+
+```
+01-01 一图看懂 → 02 快速上手 → 01-09 扩展点全景(按需跳转)
+```
+
+### 📖 "我想全面了解"(2-3 小时)
+
+```
+01 全景视野全部 11 节 → 02 快速上手 → 感兴趣的章节
+```
+
+### 🔬 "我要贡献代码"(1 天)
+
+```
+01 全景视野 → 08 开发者指南 → 03 关键路径详解 → 04 核心引擎
+```
+
+### 🏗️ "我要基于 OpenCode 做产品"(2-3 天)
+
+```
+01 全景视野 → 04 核心引擎 → 06 扩展与集成 → 10 实战案例
+```
+
+---
+
+## 关键设计决策
+
+本节作为全书导航,刻意**不引入新概念**,只做索引。设计原则:
+
+1. **按角色分组**——不同读者关心的问题完全不同
+2. **"直接去读"+"先了解"双列**——有些章节需要前置知识,明确告知
+3. **多维度索引**——按场景、按知识域、按阅读时间三种方式
+
+---
+
+## 与下一章的衔接
+
+"01-全景视野"到此结束。我们已经从一张总图出发,逐步展开了概念关系、包结构、对话旅程、工具调用、文件编辑、数据流、状态机、扩展点、技术栈——最后用这张速查表把所有内容串联起来。
+
+准备好了吗?下一章 [02-快速上手](../02-快速上手/) 将带你从零开始,5 分钟内跑起 OpenCode。
diff --git "a/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/01-\345\220\257\345\212\250\346\265\201\347\250\213\350\257\246\350\247\243.md" "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/01-\345\220\257\345\212\250\346\265\201\347\250\213\350\257\246\350\247\243.md"
new file mode 100644
index 000000000000..e68623a6072e
--- /dev/null
+++ "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/01-\345\220\257\345\212\250\346\265\201\347\250\213\350\257\246\350\247\243.md"
@@ -0,0 +1,605 @@
+# 第一节 启动流程详解
+
+📍 **你在这里**
+> 在第01章全景视野中,我们用一张大图鸟瞰了整个 OpenCode。现在我们沿着 **CLI 入口 → 配置加载 → 数据库迁移 → 服务器启动 → TUI 渲染** 这条线路,深入探索每一步的实现细节。
+
+---
+
+## 学习目标
+
+读完本节,你将能够:
+
+1. 理解从 `opencode` 命令到最终 TUI 界面出现之间发生的**每一步**
+2. 掌握 Yargs 命令注册(Command Registration)和中间件(Middleware)机制
+3. 了解配置(Config)的**七层加载优先级**
+4. 理解 SQLite 数据库迁移(Migration)与 WAL 模式的作用
+5. 理解 Worker 线程(Worker Thread)架构及 RPC 通信机制
+
+---
+
+## 一、CLI 入口:一切从这里开始
+
+OpenCode 的所有命令都从一个文件出发:
+
+```typescript
+// 文件: packages/opencode/src/index.ts
+
+import yargs from "yargs"
+import { hideBin } from "yargs/helpers"
+
+let cli = yargs(hideBin(process.argv))
+ .parserConfiguration({ "populate--": true })
+ .scriptName("opencode")
+ .wrap(100)
+ .help("help", "show help")
+ .version("version", "show version number", Installation.VERSION)
+```
+
+`hideBin(process.argv)` 会去掉前两个参数(`node` 和脚本路径),只保留用户实际输入的部分。`parserConfiguration({ "populate--": true })` 让双破折号后面的参数原样传递——这在转发参数给子进程时非常有用。
+
+### 1.1 全局错误守卫
+
+在任何命令执行前,两个全局错误处理器就已经就位:
+
+```typescript
+// 文件: packages/opencode/src/index.ts
+
+process.on("unhandledRejection", (e) => {
+ Log.Default.error("rejection", {
+ e: e instanceof Error ? e.message : e,
+ })
+})
+
+process.on("uncaughtException", (e) => {
+ Log.Default.error("exception", {
+ e: e instanceof Error ? e.message : e,
+ })
+})
+```
+
+这确保了即使在异步操作中出现意外错误,也能被记录而不是默默消失。
+
+### 1.2 日志初始化中间件
+
+Yargs 的 `middleware` 会在**所有命令执行前**运行:
+
+```typescript
+// 文件: packages/opencode/src/index.ts
+
+.middleware(async (opts) => {
+ await Log.init({
+ print: process.argv.includes("--print-logs"),
+ dev: Installation.isLocal(),
+ level: (() => {
+ if (opts.logLevel) return opts.logLevel as Log.Level
+ if (Installation.isLocal()) return "DEBUG"
+ return "INFO"
+ })(),
+ })
+
+ process.env.AGENT = "1"
+ process.env.OPENCODE = "1"
+ process.env.OPENCODE_PID = String(process.pid)
+
+ Log.Default.info("opencode", {
+ version: Installation.VERSION,
+ args: process.argv.slice(2),
+ })
+```
+
+注意三个环境变量的设置——`AGENT`、`OPENCODE` 和 `OPENCODE_PID`。其他工具和子进程可以通过这些变量检测自己是否运行在 OpenCode 环境中。
+
+---
+
+## 二、数据库迁移:一次性初始化
+
+紧接着日志初始化,是一段**只执行一次**的数据库迁移逻辑:
+
+```typescript
+// 文件: packages/opencode/src/index.ts
+
+const marker = path.join(Global.Path.data, "opencode.db")
+if (!(await Filesystem.exists(marker))) {
+ process.stderr.write(
+ "Performing one time database migration, may take a few minutes..." + EOL,
+ )
+ // ...带进度条的迁移过程...
+ await JsonMigration.run(Database.Client().$client, {
+ progress: (event) => {
+ const percent = Math.floor((event.current / event.total) * 100)
+ // 渲染进度条 ■■■■■■■■■■・・・・・ 45%
+ },
+ })
+ process.stderr.write("Database migration complete." + EOL)
+}
+```
+
+这段代码检查 `~/.local/share/opencode/opencode.db` 是否存在。如果是首次运行或从旧版本 JSON 存储升级,会将所有 JSON 数据迁移到 SQLite 数据库,并在终端显示一个精美的进度条。
+
+---
+
+## 三、命令注册树
+
+迁移完成后,所有子命令被注册到 Yargs:
+
+```typescript
+// 文件: packages/opencode/src/index.ts
+
+.command(AcpCommand)
+.command(McpCommand)
+.command(TuiThreadCommand) // ← 默认命令 "$0"
+.command(AttachCommand)
+.command(RunCommand)
+.command(GenerateCommand)
+.command(ServeCommand)
+.command(ModelsCommand)
+// ... 更多命令
+```
+
+其中 `TuiThreadCommand` 的命令名是 `$0`——这是 Yargs 的特殊语法,表示**默认命令**。当用户直接运行 `opencode` 而不带任何子命令时,就会执行这个 TUI 命令。
+
+```typescript
+// 文件: packages/opencode/src/cli/cmd/tui/thread.ts
+
+export const TuiThreadCommand = cmd({
+ command: "$0 [project]",
+ describe: "start opencode tui",
+ builder: (yargs) => yargs
+ .positional("project", { type: "string", describe: "path to start opencode in" })
+ .option("model", { type: "string", alias: ["m"], describe: "model to use" })
+ .option("continue", { alias: ["c"], describe: "continue last session" })
+ .option("session", { alias: ["s"], describe: "session id to continue" })
+ .option("prompt", { type: "string", describe: "initial prompt" })
+ .option("agent", { type: "string", describe: "agent to use" }),
+ handler: async (args) => { /* ... */ },
+})
+```
+
+---
+
+## 四、TUI 启动:Worker 线程架构
+
+当默认命令被触发后,OpenCode 不会在主线程中运行服务器——它启动一个独立的 **Worker 线程**:
+
+```typescript
+// 文件: packages/opencode/src/cli/cmd/tui/thread.ts
+
+handler: async (args) => {
+ // 1. Windows 平台特殊处理
+ const unguard = win32InstallCtrlCGuard()
+ win32DisableProcessedInput()
+
+ // 2. 解析工作目录
+ const cwd = Filesystem.resolve(process.cwd())
+
+ // 3. 启动 Worker 线程
+ const file = await target()
+ const worker = new Worker(file, { env: process.env })
+
+ // 4. 建立 RPC 通信
+ const client = Rpc.client(worker)
+```
+
+### 4.1 为什么使用 Worker 线程?
+
+这种架构有三个关键优势:
+
+| 优势 | 说明 |
+|------|------|
+| **UI 不阻塞** | 主线程专注于 TUI 渲染,LLM 调用在 Worker 中异步执行 |
+| **内存隔离** | Worker 有独立的 V8 堆,大模型响应不影响 UI 内存 |
+| **优雅重载** | 可以向 Worker 发送 `reload` 指令而不重启整个进程 |
+
+### 4.2 两种传输模式
+
+OpenCode 支持两种 Transport(传输模式):
+
+```typescript
+// 文件: packages/opencode/src/cli/cmd/tui/thread.ts
+
+const external = process.argv.includes("--port") || network.port !== 0
+
+const transport = external
+ ? {
+ url: (await client.call("server", network)).url,
+ fetch: undefined,
+ events: undefined,
+ }
+ : {
+ url: "http://opencode.internal",
+ fetch: createWorkerFetch(client),
+ events: createEventSource(client),
+ }
+```
+
+**内部传输(Internal)**:HTTP 请求直接通过 RPC 转发给 Worker 线程内的 Hono 服务器,无需占用真实网络端口。URL 使用虚拟地址 `http://opencode.internal`。
+
+**外部传输(External)**:在指定端口启动真实的 HTTP 服务器。这在 `opencode serve` 命令或远程访问时使用。
+
+### 4.3 Worker 内部初始化
+
+Worker 线程启动后的工作:
+
+```typescript
+// 文件: packages/opencode/src/cli/cmd/tui/worker.ts
+
+// 初始化日志
+await Log.init({
+ print: process.argv.includes("--print-logs"),
+ dev: Installation.isLocal(),
+ level: Installation.isLocal() ? "DEBUG" : "INFO",
+})
+
+// 订阅全局事件并转发
+GlobalBus.on("event", (event) => {
+ Rpc.emit("global.event", event)
+})
+
+// 暴露 RPC 接口
+export const rpc = {
+ async fetch(input) {
+ const response = await Server.Default().fetch(new Request(input.url, { ... }))
+ return { status: response.status, headers: ..., body: await response.text() }
+ },
+ async server(input) {
+ server = await Server.listen(input)
+ return { url: server.url.toString() }
+ },
+ async reload() {
+ Config.global.reset()
+ await Instance.disposeAll()
+ },
+ async shutdown() {
+ if (eventStream.abort) eventStream.abort.abort()
+ await Instance.disposeAll()
+ if (server) await server.stop(true)
+ },
+}
+
+Rpc.listen(rpc)
+```
+
+---
+
+## 五、Bootstrap 模式:实例生命周期
+
+每当一个命令需要访问项目上下文时,它会通过 `bootstrap()` 函数初始化:
+
+```typescript
+// 文件: packages/opencode/src/cli/bootstrap.ts
+
+export async function bootstrap(directory: string, cb: () => Promise) {
+ return Instance.provide({
+ directory,
+ init: InstanceBootstrap,
+ fn: async () => {
+ try {
+ const result = await cb()
+ return result
+ } finally {
+ await Instance.dispose()
+ }
+ },
+ })
+}
+```
+
+`Instance.provide()` 做了这些事情:
+
+1. 解析目录路径,查找 Git worktree 根目录
+2. 加载项目配置(`opencode.json`)
+3. 初始化数据库连接
+4. 缓存实例——同一目录不会重复初始化
+5. 回调执行完毕后,调用 `dispose()` 清理资源
+
+---
+
+## 六、配置加载:七层优先级
+
+配置加载是启动过程中最复杂的部分。OpenCode 使用多层配置合并(Merge),优先级从低到高:
+
+```typescript
+// 文件: packages/opencode/src/config/config.ts
+
+export const state = Instance.state(async () => {
+ let result = {} as Config
+
+ // 1. 远程配置(Organization 默认)
+ for (const [key, value] of Object.entries(auth)) {
+ if (value.type === "wellknown") {
+ const response = await fetch(`${url}/.well-known/opencode`)
+ result = mergeConfigConcatArrays(result, remoteConfig)
+ }
+ }
+
+ // 2. 全局用户配置 (~/.config/opencode/opencode.json)
+ result = mergeConfigConcatArrays(result, await global())
+
+ // 3. 自定义路径 (OPENCODE_CONFIG 环境变量)
+ if (Flag.OPENCODE_CONFIG) {
+ result = mergeConfigConcatArrays(result, await loadFile(Flag.OPENCODE_CONFIG))
+ }
+
+ // 4. 项目配置 (./opencode.json)
+ for (const file of await ConfigPaths.projectFiles(...)) {
+ result = mergeConfigConcatArrays(result, await loadFile(file))
+ }
+
+ // 5. .opencode 目录 (agents/, commands/, plugins/)
+ for (const dir of unique(directories)) {
+ if (dir.endsWith(".opencode")) {
+ result.plugin.push(...(await loadPlugin(dir)))
+ result.agent = mergeDeep(result.agent, await loadAgent(dir))
+ }
+ }
+
+ // 6. 内联配置 (OPENCODE_CONFIG_CONTENT 环境变量)
+ if (process.env.OPENCODE_CONFIG_CONTENT) {
+ result = mergeConfigConcatArrays(result, await load(...))
+ }
+
+ // 7. 企业托管配置(最高优先级)
+ if (existsSync(managedDir)) {
+ result = mergeConfigConcatArrays(result, await loadFile(...))
+ }
+
+ return { config: result, directories, deps }
+})
+```
+
+```
+┌─────────────────────────────────────┐ 优先级最高
+│ 7. 企业托管配置 (Managed Config) │ ↑
+├─────────────────────────────────────┤
+│ 6. 内联环境变量 │
+├─────────────────────────────────────┤
+│ 5. .opencode/ 目录 │
+├─────────────────────────────────────┤
+│ 4. 项目 opencode.json │
+├─────────────────────────────────────┤
+│ 3. OPENCODE_CONFIG 路径 │
+├─────────────────────────────────────┤
+│ 2. 全局 ~/.config/opencode/ │
+├─────────────────────────────────────┤
+│ 1. 远程 .well-known/opencode │ ↓
+└─────────────────────────────────────┘ 优先级最低
+```
+
+---
+
+## 七、数据库初始化
+
+OpenCode 使用 **SQLite + Drizzle ORM** 作为存储引擎:
+
+```typescript
+// 文件: packages/opencode/src/storage/db.ts
+
+export const Client = lazy(() => {
+ const db = init(Path)
+
+ // 性能优化 Pragma
+ db.run("PRAGMA journal_mode = WAL") // 写前日志(并发读写)
+ db.run("PRAGMA synchronous = NORMAL") // 平衡性能与安全
+ db.run("PRAGMA busy_timeout = 5000") // 等待锁 5 秒
+ db.run("PRAGMA cache_size = -64000") // 64MB 缓存
+ db.run("PRAGMA foreign_keys = ON") // 启用外键
+ db.run("PRAGMA wal_checkpoint(PASSIVE)") // 被动检查点
+
+ // 执行数据库迁移
+ migrate(db, entries)
+ return db
+})
+```
+
+> 💡 **为什么选择 WAL 模式?** WAL(Write-Ahead Logging)模式允许读操作和写操作同时进行,而不互相阻塞。这对于 TUI 前端实时刷新消息列表非常关键——前端可以在 LLM 写入新 token 的同时读取消息历史。
+
+---
+
+## 八、服务器启动
+
+服务器基于 **Hono 框架** 构建,通过 `Bun.serve()` 启动:
+
+```typescript
+// 文件: packages/opencode/src/server/server.ts
+
+export function listen(opts) {
+ const app = createApp(opts)
+
+ const tryServe = (port: number) => {
+ try {
+ return Bun.serve({ ...args, port })
+ } catch {
+ return undefined
+ }
+ }
+
+ // 端口分配策略:指定端口 → 4096 → 系统分配
+ const server = opts.port === 0
+ ? (tryServe(4096) ?? tryServe(0))
+ : tryServe(opts.port)
+}
+```
+
+服务器的中间件栈(Middleware Stack):
+
+```typescript
+// 文件: packages/opencode/src/server/server.ts
+
+const app = new Hono()
+ // 1. 错误处理
+ .onError((err, c) => {
+ if (err instanceof NamedError) {
+ let status = 500
+ if (err instanceof NotFoundError) status = 404
+ return c.json(err.toObject(), { status })
+ }
+ })
+ // 2. 基础认证(可选)
+ .use((c, next) => {
+ const password = Flag.OPENCODE_SERVER_PASSWORD
+ if (!password) return next()
+ return basicAuth({ username, password })(c, next)
+ })
+ // 3. 请求日志
+ .use(async (c, next) => {
+ const timer = log.time("request", { method: c.req.method, path: c.req.path })
+ await next()
+ timer.stop()
+ })
+ // 4. CORS 策略
+ .use(cors({ origin(input) { /* localhost 和 opencode.ai 域名 */ } }))
+ // 5. 实例上下文注入
+ .use(async (c, next) => {
+ const directory = c.req.query("directory") || process.cwd()
+ return Instance.provide({ directory, init: InstanceBootstrap, fn: () => next() })
+ })
+ // 6. 路由注册
+ .route("/session", SessionRoutes())
+ .route("/config", ConfigRoutes())
+ .route("/project", ProjectRoutes())
+ // ... 更多路由
+```
+
+---
+
+## 九、完整启动时序
+
+```mermaid
+sequenceDiagram
+ participant User as 用户终端
+ participant CLI as index.ts (Yargs)
+ participant Log as 日志系统
+ participant DB as SQLite 数据库
+ participant Thread as TUI 主线程
+ participant Worker as Worker 线程
+ participant Server as Hono 服务器
+ participant TUI as React TUI
+
+ User->>CLI: opencode [project]
+ CLI->>CLI: hideBin(process.argv)
+ CLI->>Log: Log.init({ level, dev })
+ CLI->>CLI: 设置环境变量 AGENT, OPENCODE
+
+ alt 首次运行
+ CLI->>DB: JsonMigration.run()
+ DB-->>CLI: 迁移完成 ■■■■■■■ 100%
+ end
+
+ CLI->>Thread: TuiThreadCommand.handler(args)
+ Thread->>Thread: 解析工作目录 cwd
+ Thread->>Worker: new Worker(file, { env })
+ Worker->>Log: Log.init()
+ Worker->>Worker: 订阅 GlobalBus 事件
+ Worker->>Worker: Rpc.listen(rpc)
+
+ Thread->>Thread: Rpc.client(worker)
+
+ alt 外部传输模式
+ Thread->>Worker: client.call("server", { port, hostname })
+ Worker->>Server: Server.listen({ port })
+ Server-->>Worker: server.url
+ Worker-->>Thread: { url }
+ else 内部传输模式
+ Thread->>Thread: createWorkerFetch(client)
+ Thread->>Thread: url = "http://opencode.internal"
+ end
+
+ Thread->>TUI: tui({ url, config, directory })
+ TUI->>Server: HTTP/SSE 请求
+ Server->>Server: Instance.provide(directory)
+ Server->>Server: 加载配置(七层合并)
+ Server->>DB: Database.Client() + WAL 模式
+ Server-->>TUI: 响应数据
+ TUI-->>User: 渲染终端界面
+```
+
+---
+
+## 十、XDG 目录规范
+
+OpenCode 严格遵循 **XDG Base Directory** 规范来组织文件:
+
+```typescript
+// 文件: packages/opencode/src/global/index.ts
+
+export const Path = {
+ data: path.join(xdgData, "opencode"), // ~/.local/share/opencode
+ config: path.join(xdgConfig, "opencode"), // ~/.config/opencode
+ cache: path.join(xdgCache, "opencode"), // ~/.cache/opencode
+ state: path.join(xdgState, "opencode"), // ~/.local/state/opencode
+ log: path.join(data, "log"), // ~/.local/share/opencode/log
+ bin: path.join(cache, "bin"), // ~/.cache/opencode/bin
+}
+```
+
+| 路径 | 用途 | 示例内容 |
+|------|------|----------|
+| `data/` | 持久数据 | `opencode.db`、会话文件 |
+| `config/` | 用户配置 | `opencode.json` |
+| `cache/` | 可清除缓存 | 下载的二进制工具 |
+| `state/` | 运行时状态 | 锁文件 |
+| `log/` | 日志文件 | `opencode.log` |
+
+---
+
+## 动手练习
+
+### 练习 1:追踪启动日志
+
+启用详细日志来观察启动过程:
+
+```bash
+opencode --print-logs --log-level DEBUG 2>startup.log
+# 在另一个终端查看日志
+tail -f startup.log | grep -E "(creating instance|request|migration)"
+```
+
+### 练习 2:观察配置合并
+
+在项目根目录创建 `opencode.json`,设置一个自定义选项,然后在 `~/.config/opencode/opencode.json` 设置不同的值,观察最终生效的是哪一个。
+
+### 练习 3:使用外部传输模式
+
+```bash
+# 启动带有外部端口的 TUI
+opencode --port 4096
+
+# 在另一个终端用 curl 查看 API
+curl http://localhost:4096/session?directory=$(pwd) | jq
+```
+
+---
+
+## 常见问题
+
+### Q: 为什么 OpenCode 启动时要检查 `opencode.db` 是否存在?
+
+这是一个**一次性迁移守卫**。早期版本使用 JSON 文件存储会话数据,升级后需要将这些数据迁移到 SQLite。通过检查数据库文件是否存在,可以判断是否需要执行迁移。
+
+### Q: Worker 线程崩溃会怎样?
+
+Worker 线程中的 `unhandledRejection` 和 `uncaughtException` 都被捕获并记录日志。如果 Worker 意外终止,主线程的 TUI 也会跟着退出(通过 `finally` 块中的 `stop()`)。
+
+### Q: 内部传输模式中,HTTP 请求真的会走网络吗?
+
+不会。`createWorkerFetch(client)` 创建了一个自定义的 `fetch` 函数,它通过 RPC 直接调用 Worker 线程内的 Hono 服务器的 `fetch` 方法。整个过程在进程内部完成,不涉及 TCP 连接。
+
+### Q: 我能在同一台机器上运行多个 OpenCode 实例吗?
+
+可以。端口分配策略会自动处理冲突——如果 4096 被占用,系统会自动分配一个可用端口。每个实例有独立的 Worker 线程和数据库连接。
+
+---
+
+## 小结
+
+本节我们完整追踪了 OpenCode 的启动流程:
+
+1. **CLI 解析**:Yargs 解析命令行参数,执行日志初始化中间件
+2. **数据库迁移**:首次运行时从 JSON 迁移到 SQLite
+3. **命令路由**:默认执行 `TuiThreadCommand`(`$0`)
+4. **Worker 架构**:主线程负责 TUI 渲染,Worker 线程运行服务器
+5. **传输模式**:内部 RPC 传输(默认)或外部 HTTP 传输
+6. **配置合并**:七层优先级从远程到企业托管
+7. **服务器中间件**:错误处理 → 认证 → 日志 → CORS → 实例注入
+
+> ⏭️ 下一节,我们将深入对话循环——从用户发送消息到 LLM 返回响应的完整路径。
diff --git "a/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/02-\345\257\271\350\257\235\345\276\252\347\216\257\350\257\246\350\247\243.md" "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/02-\345\257\271\350\257\235\345\276\252\347\216\257\350\257\246\350\247\243.md"
new file mode 100644
index 000000000000..a277412067a4
--- /dev/null
+++ "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/02-\345\257\271\350\257\235\345\276\252\347\216\257\350\257\246\350\247\243.md"
@@ -0,0 +1,824 @@
+# 第二节 对话循环详解
+
+📍 **你在这里**
+> 在第01章全景视野中,我们用一张大图鸟瞰了整个 OpenCode。现在我们沿着 **消息发送 → Prompt 组装 → LLM 流式调用 → Tool 检测与执行 → 结果反馈 → 下一轮调用** 这条线路,深入探索每一步的实现细节。
+
+---
+
+## 学习目标
+
+读完本节,你将能够:
+
+1. 理解 OpenCode 的 **ReAct 循环**(Reasoning + Acting)完整实现
+2. 掌握从用户消息到 LLM 响应的**三层架构**:Prompt → Processor → LLM
+3. 了解流式事件(Stream Event)的种类和处理方式
+4. 理解 Doom Loop(死循环)检测机制
+5. 掌握结构化输出(Structured Output)模式的工作原理
+
+---
+
+## 一、概念解释:ReAct 模式
+
+OpenCode 的对话循环基于 **ReAct** 模式——LLM 交替执行"推理"(Reasoning)和"行动"(Acting):
+
+```
+用户提问 → LLM 思考 → 调用工具 → 获得结果 → LLM 再思考 → 调用工具 → ... → 最终回答
+```
+
+这不是简单的一问一答,而是一个**多步循环**,LLM 可以在一轮对话中多次调用工具,直到它认为任务完成。
+
+---
+
+## 二、三层架构总览
+
+对话循环由三层组件协作完成:
+
+```
+┌──────────────────────────────────────────────┐
+│ SessionPrompt(会话提示层) │
+│ 创建用户消息 → 管理循环生命周期 → 处理退出条件 │
+├──────────────────────────────────────────────┤
+│ SessionProcessor(处理器层) │
+│ 驱动流式调用 → 处理事件 → 检测死循环 → 错误重试 │
+├──────────────────────────────────────────────┤
+│ LLM(调用层) │
+│ 组装 System Prompt → 转换消息格式 → 调用 AI SDK │
+└──────────────────────────────────────────────┘
+```
+
+我们从最上层开始,逐层深入。
+
+---
+
+## 三、SessionPrompt:循环的起点
+
+### 3.1 用户消息创建
+
+当用户在 TUI 中按下回车发送消息时,`SessionPrompt.prompt()` 被调用:
+
+```typescript
+// 文件: packages/opencode/src/session/prompt.ts
+
+export const prompt = fn(PromptInput, async (input) => {
+ // 创建用户消息
+ const msg = await Session.updateMessage({
+ id: MessageID.ascending(),
+ role: "user",
+ sessionID: input.sessionID,
+ agent: agent.name,
+ model: { providerID: model.providerID, modelID: model.id },
+ format: input.format,
+ time: { created: Date.now() },
+ })
+
+ // 添加消息部件(文本、文件、Agent 引用)
+ for (const part of input.parts) {
+ await Session.updatePart({
+ id: PartID.ascending(),
+ messageID: msg.id,
+ sessionID: input.sessionID,
+ ...part,
+ })
+ }
+
+ // 启动对话循环
+ if (!input.noReply) {
+ loop({ sessionID: input.sessionID })
+ }
+})
+```
+
+> 💡 `MessageID.ascending()` 生成**单调递增**的 ID(基于 ULID),保证消息的时间顺序。
+
+### 3.2 主循环 — loop()
+
+`loop()` 是整个对话循环的核心。它是一个 `while(true)` 循环,每一轮都做出"继续"或"停止"的决策:
+
+```typescript
+// 文件: packages/opencode/src/session/prompt.ts
+
+export const loop = fn(LoopInput, async (input) => {
+ const abort = input.resume_existing
+ ? resume(input.sessionID)
+ : start(input.sessionID)
+
+ while (true) {
+ // 获取所有消息(跳过已压缩的)
+ let msgs = await MessageV2.filterCompacted(
+ MessageV2.stream(input.sessionID),
+ )
+
+ // 找到最后的用户消息和助手消息
+ let lastUser: MessageV2.User
+ let lastAssistant: MessageV2.Assistant
+
+ // ========== 退出条件检查 ==========
+ if (
+ lastAssistant.finish &&
+ !["tool-calls", "unknown"].includes(lastAssistant.finish) &&
+ lastUser.id < lastAssistant.id
+ ) {
+ break // LLM 正常结束,退出循环
+ }
+
+ // ========== 处理挂起任务 ==========
+ if (task?.type === "subtask") {
+ // 执行子任务(委派给其他 Agent)
+ continue
+ }
+ if (task?.type === "compaction") {
+ // 压缩会话历史
+ continue
+ }
+
+ // ========== 上下文溢出检查 ==========
+ if (await SessionCompaction.isOverflow({ ... })) {
+ await SessionCompaction.create({ ... })
+ continue
+ }
+
+ // ========== 构建工具集 ==========
+ const agent = await Agent.get(lastUser.agent)
+ const tools = await resolveTools({ ... })
+
+ // ========== 构建 System Prompt ==========
+ const system = [
+ ...(await SystemPrompt.environment(model)),
+ ...(skills ? [skills] : []),
+ ...(await InstructionPrompt.system()),
+ ]
+
+ // ========== 调用 Processor ==========
+ const processor = SessionProcessor.create({
+ assistantMessage,
+ sessionID: input.sessionID,
+ model,
+ abort,
+ })
+
+ const result = await processor.process({
+ user: lastUser,
+ agent,
+ abort,
+ messages: MessageV2.toModelMessages(msgs, model),
+ tools,
+ system,
+ })
+
+ // ========== 处理结果 ==========
+ if (result === "stop") break
+ if (result === "compact") {
+ await SessionCompaction.create({ ... })
+ }
+ // result === "continue" → 下一轮循环
+ }
+
+ // 循环结束后:修剪旧工具输出
+ SessionCompaction.prune({ sessionID: input.sessionID })
+ return lastAssistantMessage
+})
+```
+
+### 3.3 循环流程图
+
+```mermaid
+flowchart TD
+ A[获取所有消息] --> B{LLM 已正常结束?}
+ B -->|是| Z[退出循环]
+ B -->|否| C{有挂起的子任务?}
+ C -->|是| D[执行子任务] --> A
+ C -->|否| E{有挂起的压缩?}
+ E -->|是| F[压缩会话] --> A
+ E -->|否| G{上下文溢出?}
+ G -->|是| H[创建压缩任务] --> A
+ G -->|否| I[构建工具集和 System Prompt]
+ I --> J[创建 Processor]
+ J --> K[调用 processor.process]
+ K --> L{返回结果}
+ L -->|stop| Z
+ L -->|compact| H
+ L -->|continue| A
+ Z --> M[修剪旧工具输出]
+ M --> N[返回最终助手消息]
+```
+
+---
+
+## 四、SessionProcessor:流式事件处理引擎
+
+### 4.1 创建处理器
+
+```typescript
+// 文件: packages/opencode/src/session/processor.ts
+
+export namespace SessionProcessor {
+ const DOOM_LOOP_THRESHOLD = 3
+
+ export function create(input: {
+ assistantMessage: MessageV2.Assistant
+ sessionID: SessionID
+ model: Provider.Model
+ abort: AbortSignal
+ }) {
+ const toolcalls: Record = {}
+ let snapshot: string | undefined
+ let blocked = false
+ let attempt = 0
+ let needsCompaction = false
+ // ...
+ }
+}
+```
+
+`toolcalls` 字典是整个处理器的核心数据结构——它追踪每一个正在执行的工具调用,从 `pending` 到 `running` 到 `completed`/`error`。
+
+### 4.2 process() — 事件驱动的内循环
+
+`process()` 方法驱动了实际的 LLM 调用和响应处理:
+
+```typescript
+// 文件: packages/opencode/src/session/processor.ts
+
+async process(streamInput: LLM.StreamInput) {
+ const shouldBreak =
+ (await Config.get()).experimental?.continue_loop_on_deny !== true
+
+ while (true) {
+ try {
+ const stream = await LLM.stream(streamInput)
+
+ for await (const value of stream.fullStream) {
+ input.abort.throwIfAborted()
+
+ switch (value.type) {
+ case "start":
+ await SessionStatus.set(input.sessionID, { type: "busy" })
+ break
+
+ case "text-start":
+ // 创建文本部件
+ currentText = {
+ id: PartID.ascending(),
+ messageID: input.assistantMessage.id,
+ sessionID: input.assistantMessage.sessionID,
+ type: "text",
+ text: "",
+ time: { start: Date.now() },
+ }
+ await Session.updatePart(currentText)
+ break
+
+ case "text-delta":
+ // 流式追加文本
+ currentText.text += value.text
+ await Session.updatePartDelta({
+ sessionID: currentText.sessionID,
+ messageID: currentText.messageID,
+ partID: currentText.id,
+ field: "text",
+ delta: value.text,
+ })
+ break
+
+ case "tool-call":
+ // 工具被调用
+ // ...
+ break
+
+ case "tool-result":
+ // 工具执行完毕
+ // ...
+ break
+
+ case "finish-step":
+ // 一步完成,更新 token 计数
+ // ...
+ break
+ }
+ }
+ } catch (e) {
+ // 错误处理与重试
+ }
+ }
+}
+```
+
+### 4.3 流式事件完整清单
+
+LLM 返回的流式事件(Stream Event)包括:
+
+| 事件类型 | 触发时机 | 处理方式 |
+|----------|----------|----------|
+| `start` | 流开始 | 设置会话状态为 `busy` |
+| `reasoning-start` | 开始思考(Claude/o1) | 创建 Reasoning Part |
+| `reasoning-delta` | 思考增量 | 追加文本 + 发布 delta |
+| `reasoning-end` | 思考结束 | 更新时间戳 |
+| `text-start` | 开始输出文本 | 创建 Text Part |
+| `text-delta` | 文本增量 | 追加文本 + 发布 delta |
+| `text-end` | 文本结束 | 触发 Plugin hook |
+| `tool-input-start` | 开始构建工具参数 | 创建 Tool Part (pending) |
+| `tool-call` | 工具调用确认 | 更新为 running + 死循环检测 |
+| `tool-result` | 工具返回结果 | 更新为 completed |
+| `tool-error` | 工具执行出错 | 更新为 error |
+| `start-step` | 新步骤开始 | 创建文件快照 |
+| `finish-step` | 步骤结束 | 更新 token、创建 patch |
+| `error` | 流式错误 | 抛出异常 |
+
+### 4.4 文本流式更新机制
+
+文本更新使用了两层机制——**持久化**和**广播**:
+
+```typescript
+// 文件: packages/opencode/src/session/processor.ts
+
+case "text-delta":
+ if (currentText) {
+ // 1. 内存中追加
+ currentText.text += value.text
+
+ // 2. 通过 Bus 广播增量(TUI 实时更新)
+ await Session.updatePartDelta({
+ sessionID: currentText.sessionID,
+ messageID: currentText.messageID,
+ partID: currentText.id,
+ field: "text",
+ delta: value.text, // 仅发送增量,不是全文
+ })
+ }
+ break
+
+case "text-end":
+ if (currentText) {
+ currentText.text = currentText.text.trimEnd()
+ // 3. 文本完成后,触发插件 hook
+ const output = await Plugin.trigger(
+ "experimental.text.complete",
+ { sessionID, messageID, partID: currentText.id },
+ { text: currentText.text },
+ )
+ currentText.text = output.text
+ // 4. 最终持久化到数据库
+ await Session.updatePart(currentText)
+ }
+ break
+```
+
+> 💡 `updatePartDelta` 只发布增量文本——这让 TUI 可以高效地逐字符渲染,而不需要每次重新获取全文。
+
+---
+
+## 五、Doom Loop 检测
+
+当 LLM 陷入死循环,反复用相同参数调用同一个工具时,OpenCode 能检测到并中断:
+
+```typescript
+// 文件: packages/opencode/src/session/processor.ts
+
+case "tool-call": {
+ const match = toolcalls[value.toolCallId]
+ if (match) {
+ // 更新工具状态为 running
+ const part = await Session.updatePart({
+ ...match,
+ tool: value.toolName,
+ state: {
+ status: "running",
+ input: value.input,
+ time: { start: Date.now() },
+ },
+ })
+
+ // ===== 死循环检测 =====
+ const parts = await MessageV2.parts(input.assistantMessage.id)
+ const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD)
+
+ if (
+ lastThree.length === DOOM_LOOP_THRESHOLD &&
+ lastThree.every(
+ (p) =>
+ p.type === "tool" &&
+ p.tool === value.toolName &&
+ p.state.status !== "pending" &&
+ JSON.stringify(p.state.input) === JSON.stringify(value.input),
+ )
+ ) {
+ // 连续 3 次相同工具+相同参数 → 请求用户许可
+ await Permission.ask({
+ permission: "doom_loop",
+ patterns: [value.toolName],
+ sessionID: input.assistantMessage.sessionID,
+ metadata: { tool: value.toolName, input: value.input },
+ always: [value.toolName],
+ ruleset: agent.permission,
+ })
+ }
+ }
+ break
+}
+```
+
+检测算法非常简洁:取最近 3 个工具调用,如果工具名和输入参数完全相同,就触发权限询问。用户可以选择:
+
+- **允许一次**:继续执行,但下次还会问
+- **始终允许**:加入白名单
+- **拒绝**:中断循环
+
+---
+
+## 六、错误处理与重试
+
+流式调用可能因为多种原因失败。处理器有一套完整的重试机制:
+
+```typescript
+// 文件: packages/opencode/src/session/processor.ts
+
+catch (e: any) {
+ const error = MessageV2.fromError(e, {
+ providerID: input.model.providerID,
+ aborted: input.abort.aborted,
+ })
+
+ if (MessageV2.ContextOverflowError.isInstance(error)) {
+ // 上下文溢出 → 触发压缩
+ needsCompaction = true
+ Bus.publish(Session.Event.Error, { sessionID: input.sessionID, error })
+ } else {
+ const retry = SessionRetry.retryable(error)
+ if (retry !== undefined) {
+ // 可重试错误 → 指数退避
+ attempt++
+ const delay = SessionRetry.delay(attempt, error)
+ await SessionStatus.set(input.sessionID, {
+ type: "retry",
+ attempt,
+ message: retry,
+ next: Date.now() + delay,
+ })
+ await SessionRetry.sleep(delay, input.abort).catch(() => {})
+ continue // 重试
+ }
+ // 不可重试错误 → 记录并停止
+ input.assistantMessage.error = error
+ Bus.publish(Session.Event.Error, { ... })
+ }
+}
+```
+
+错误处理流程:
+
+```mermaid
+flowchart TD
+ E[捕获错误] --> F{上下文溢出?}
+ F -->|是| G[标记需要压缩] --> H[发布错误事件]
+ F -->|否| I{可重试?}
+ I -->|是| J[计算退避延迟]
+ J --> K[设置状态 retry]
+ K --> L[等待延迟]
+ L --> M[continue 重试]
+ I -->|否| N[记录错误到消息]
+ N --> O[发布错误事件]
+ O --> P[设置状态 idle]
+```
+
+---
+
+## 七、LLM 调用层
+
+### 7.1 StreamInput 结构
+
+```typescript
+// 文件: packages/opencode/src/session/llm.ts
+
+export type StreamInput = {
+ user: MessageV2.User // 用户消息
+ sessionID: string // 会话 ID
+ model: Provider.Model // 模型定义
+ agent: Agent.Info // Agent 配置
+ permission?: Permission.Ruleset // 权限规则
+ system: string[] // System Prompt 数组
+ abort: AbortSignal // 取消信号
+ messages: ModelMessage[] // 对话历史
+ tools: Record // 可用工具集
+ toolChoice?: "auto" | "required" | "none"
+}
+```
+
+### 7.2 System Prompt 组装
+
+System Prompt 的构建遵循严格的优先级:
+
+```typescript
+// 文件: packages/opencode/src/session/llm.ts
+
+export async function stream(input: StreamInput) {
+ // System Prompt 分两部分(用于缓存优化)
+ // 第一部分:稳定内容(命中缓存)
+ // 第二部分:动态内容(可能变化)
+
+ const system = input.system
+
+ // 插件可以修改 System Prompt
+ const transformed = await Plugin.trigger(
+ "experimental.chat.system.transform",
+ { sessionID: input.sessionID },
+ { system },
+ )
+```
+
+### 7.3 消息历史转换
+
+不同 Provider(提供商)对消息格式有不同要求:
+
+```typescript
+// 文件: packages/opencode/src/session/llm.ts
+
+// OAuth 模型:system 放在 instructions 字段
+// 普通模型:system 作为消息历史的第一条
+if (isOAuthModel) {
+ messages = input.messages // system 在别处传入
+} else {
+ messages = [
+ { role: "system", content: systemPrompt },
+ ...input.messages,
+ ]
+}
+```
+
+### 7.4 工具准备
+
+```typescript
+// 文件: packages/opencode/src/session/llm.ts
+
+// 过滤被禁用的工具
+const filtered = Object.fromEntries(
+ Object.entries(input.tools).filter(([id]) => {
+ // 检查权限规则
+ return evaluate(id, "*", input.permission).action !== "deny"
+ }),
+)
+
+// 为 LiteLLM 兼容性添加空操作工具
+if (needsNoop) {
+ filtered["_noop"] = tool({ /* 什么都不做的工具 */ })
+}
+```
+
+> 💡 `_noop` 工具是一个兼容性 hack——某些 LLM 代理(如 LiteLLM)在工具列表为空时会报错,所以加一个空操作工具。
+
+---
+
+## 八、完整的 ReAct 循环时序
+
+```mermaid
+sequenceDiagram
+ participant User as 用户
+ participant Prompt as SessionPrompt
+ participant Proc as SessionProcessor
+ participant LLM as LLM 层
+ participant AI as AI Provider
+ participant Tool as 工具系统
+ participant DB as 数据库
+
+ User->>Prompt: 发送消息
+ Prompt->>DB: 创建 User Message
+ Prompt->>DB: 创建 Message Parts
+ Prompt->>Prompt: 进入 loop()
+
+ loop ReAct 循环
+ Prompt->>Prompt: 获取消息历史
+ Prompt->>Prompt: 检查退出条件
+ Prompt->>Prompt: 构建工具集 + System Prompt
+ Prompt->>Proc: 创建 Processor
+
+ Proc->>LLM: stream(StreamInput)
+ LLM->>AI: streamText() / AI SDK
+
+ loop 流式事件
+ AI-->>Proc: text-delta
+ Proc->>DB: updatePartDelta
+ Proc-->>User: 实时显示文本
+
+ AI-->>Proc: tool-call
+ Proc->>Proc: 死循环检测
+ Proc->>Tool: 执行工具
+ Tool-->>Proc: tool-result
+ Proc->>DB: updatePart (completed)
+
+ AI-->>Proc: finish-step
+ Proc->>DB: 更新 token 计数
+ Proc->>DB: 创建文件 patch
+ end
+
+ Proc-->>Prompt: "continue" | "stop" | "compact"
+
+ alt stop
+ Prompt->>Prompt: 退出循环
+ else compact
+ Prompt->>Prompt: 创建压缩任务
+ else continue
+ Prompt->>Prompt: 下一轮循环
+ end
+ end
+
+ Prompt->>DB: prune 旧工具输出
+ Prompt-->>User: 返回最终响应
+```
+
+---
+
+## 九、工具调用的状态机
+
+每个工具调用都经历一个明确的状态机转换:
+
+```mermaid
+stateDiagram-v2
+ [*] --> pending: tool-input-start
+ pending --> running: tool-call
+ running --> completed: tool-result
+ running --> error: tool-error
+ completed --> [*]
+ error --> [*]
+
+ note right of pending
+ 创建 Tool Part
+ input 为空对象
+ end note
+
+ note right of running
+ 解析完整参数
+ 开始执行
+ 检测死循环
+ end note
+
+ note right of completed
+ 记录 output
+ 记录 metadata
+ 记录时间戳
+ end note
+
+ note right of error
+ 权限被拒 → blocked
+ 工具异常 → error message
+ end note
+```
+
+对应的数据结构:
+
+```typescript
+// 文件: packages/opencode/src/session/message-v2.ts
+
+// Pending 状态
+{ status: "pending", input: {}, raw: "" }
+
+// Running 状态
+{ status: "running", input: { command: "ls -la" }, time: { start: 1234567890 } }
+
+// Completed 状态
+{
+ status: "completed",
+ input: { command: "ls -la" },
+ output: "total 42\ndrwxr-xr-x ...",
+ title: "Listed directory contents",
+ metadata: { exitCode: 0 },
+ time: { start: 1234567890, end: 1234567900 },
+}
+
+// Error 状态
+{
+ status: "error",
+ input: { command: "rm -rf /" },
+ error: "Permission denied",
+ time: { start: 1234567890, end: 1234567895 },
+}
+```
+
+---
+
+## 十、结构化输出模式
+
+当用户需要 JSON 格式的输出时,OpenCode 会注入一个特殊的 `StructuredOutput` 工具:
+
+```typescript
+// 文件: packages/opencode/src/session/prompt.ts
+
+if (lastUser.format?.type === "json_schema") {
+ tools["StructuredOutput"] = createStructuredOutputTool({
+ schema: lastUser.format.schema,
+ onOutput: (output) => {
+ structuredOutput = output
+ },
+ })
+}
+```
+
+当 LLM 调用 `StructuredOutput` 工具并传入有效 JSON 时,循环立即停止并返回结构化数据。这是一种巧妙的设计——不修改 LLM 的行为,而是通过工具机制"截获"输出。
+
+---
+
+## 十一、步骤快照与文件 Patch
+
+每一步(Step)开始和结束时,OpenCode 会创建文件系统快照:
+
+```typescript
+// 文件: packages/opencode/src/session/processor.ts
+
+case "start-step":
+ snapshot = await Snapshot.track()
+ await Session.updatePart({
+ id: PartID.ascending(),
+ messageID: input.assistantMessage.id,
+ sessionID: input.sessionID,
+ snapshot,
+ type: "step-start",
+ })
+ break
+
+case "finish-step":
+ // ...更新 token 和 cost...
+ if (snapshot) {
+ const patch = await Snapshot.patch(snapshot)
+ if (patch.files.length) {
+ await Session.updatePart({
+ id: PartID.ascending(),
+ messageID: input.assistantMessage.id,
+ sessionID: input.sessionID,
+ type: "patch",
+ hash: patch.hash,
+ files: patch.files,
+ })
+ }
+ snapshot = undefined
+ }
+ // 在步骤结束后触发摘要生成
+ SessionSummary.summarize({
+ sessionID: input.sessionID,
+ messageID: input.assistantMessage.parentID,
+ })
+ break
+```
+
+这使得 OpenCode 能够精确追踪每一步修改了哪些文件,并在需要时回滚到之前的状态。
+
+---
+
+## 动手练习
+
+### 练习 1:观察 ReAct 循环
+
+在 TUI 中向 OpenCode 发送一个需要多步工具调用的请求,例如:
+
+```
+请阅读 src/index.ts 文件,然后在其中添加一个注释
+```
+
+观察日志中的 `tool-call` → `tool-result` 循环。
+
+### 练习 2:触发死循环检测
+
+尝试构造一个让 LLM 反复执行相同操作的提示,观察 Doom Loop 检测是否触发权限询问。
+
+### 练习 3:追踪流式事件
+
+启用 DEBUG 日志后,观察一次完整对话产生的事件序列:
+
+```bash
+opencode --print-logs --log-level DEBUG 2>events.log
+# 发送一条消息后查看
+grep "process" events.log
+```
+
+---
+
+## 常见问题
+
+### Q: `process()` 返回 "continue" 和 "stop" 的区别是什么?
+
+`"continue"` 表示 LLM 执行了工具调用后还需要继续处理(`finishReason` 是 `"tool-calls"`)。`"stop"` 表示 LLM 认为任务已完成,或者发生了不可恢复的错误。`"compact"` 表示上下文溢出,需要先压缩才能继续。
+
+### Q: 为什么要用 `Bus.publish` 广播 delta 而不是直接写入数据库?
+
+性能考虑。LLM 每秒可能产生数十个 `text-delta` 事件,如果每个都写入 SQLite 会造成 I/O 瓶颈。通过 Bus 广播(内存中的发布/订阅),TUI 可以即时收到更新。最终的完整文本在 `text-end` 时才写入数据库。
+
+### Q: 如果 LLM 提供商的 API 临时不可用怎么办?
+
+`SessionRetry` 模块会计算指数退避(Exponential Backoff)延迟。第一次重试等待几秒,随后每次翻倍。同时会在 TUI 中显示重试状态和下次重试时间,用户可以选择等待或取消。
+
+### Q: 一轮对话循环最多能调用多少次工具?
+
+没有硬编码的限制。循环会持续到 LLM 自己决定停止,或者上下文窗口溢出触发压缩。不过,Doom Loop 检测会在连续 3 次相同调用时介入。
+
+---
+
+## 小结
+
+本节我们完整追踪了对话循环的每一个环节:
+
+1. **用户消息创建**:`SessionPrompt.prompt()` 创建 User Message 和 Parts
+2. **主循环管理**:`loop()` 持续驱动,检查退出条件、处理子任务和压缩
+3. **流式处理**:`SessionProcessor.process()` 消费 LLM 流式事件
+4. **事件类型**:从 `text-delta` 到 `tool-call`,11 种事件各司其职
+5. **安全机制**:Doom Loop 检测防止 LLM 陷入死循环
+6. **错误恢复**:上下文溢出触发压缩,API 错误触发指数退避重试
+7. **快照系统**:每步开始/结束时创建快照,精确追踪文件变更
+
+> ⏭️ 下一节,我们将深入 Tool 执行系统——了解 Bash、Edit、Read 等工具的具体实现。
diff --git "a/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/03-Tool\346\211\247\350\241\214\350\257\246\350\247\243.md" "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/03-Tool\346\211\247\350\241\214\350\257\246\350\247\243.md"
new file mode 100644
index 000000000000..b7d2e1c076a1
--- /dev/null
+++ "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/03-Tool\346\211\247\350\241\214\350\257\246\350\247\243.md"
@@ -0,0 +1,853 @@
+# 第三节 Tool 执行详解
+
+📍 **你在这里**
+> 在第01章全景视野中,我们用一张大图鸟瞰了整个 OpenCode。现在我们沿着 **Tool 注册 → 初始化 → 选择 → 权限检查 → 执行 → 结果格式化** 这条线路,深入探索每一步的实现细节。
+
+---
+
+## 学习目标
+
+读完本节,你将能够:
+
+1. 理解 Tool 的**定义、注册和初始化**三阶段生命周期
+2. 掌握权限系统(Permission System)的**规则评估算法**
+3. 深入了解 **Bash Tool** 的 AST 分析和安全沙箱机制
+4. 理解 **Edit Tool** 的 9 种替换策略(Replacement Strategy)
+5. 了解 **Read Tool** 的文件类型检测和分页机制
+
+---
+
+## 一、概念解释:Tool 是什么?
+
+在 OpenCode 中,Tool 是 LLM 与现实世界交互的**唯一通道**。LLM 本身不能读文件、不能执行命令——它只能请求调用一个 Tool,并等待结果。
+
+```
+LLM: "我想读取 src/index.ts 的内容"
+ → 调用 ReadTool({ filePath: "src/index.ts" })
+ → OpenCode 执行读取,检查权限
+ → 返回文件内容给 LLM
+```
+
+每个 Tool 都有严格的 **Zod Schema** 定义输入参数,并经过**权限检查**才能执行。
+
+---
+
+## 二、Tool 定义:`Tool.define()`
+
+### 2.1 核心接口
+
+```typescript
+// 文件: packages/opencode/src/tool/tool.ts
+
+export namespace Tool {
+ export type Context = {
+ sessionID: SessionID
+ messageID: MessageID
+ agent: string
+ abort: AbortSignal
+ callID?: string
+ messages: MessageV2.WithParts[]
+ metadata(input: { title?: string; metadata?: M }): void
+ ask(input: Omit): Promise
+ }
+
+ export interface Info {
+ id: string
+ init: (ctx?: InitContext) => Promise<{
+ description: string
+ parameters: Parameters
+ execute(
+ args: z.infer,
+ ctx: Context,
+ ): Promise<{
+ title: string
+ metadata: M
+ output: string
+ attachments?: Omit[]
+ }>
+ formatValidationError?(error: z.ZodError): string
+ }>
+ }
+}
+```
+
+关键设计点:
+
+| 字段 | 作用 |
+|------|------|
+| `init()` | 惰性初始化——只在需要时加载描述和参数 Schema |
+| `ctx.metadata()` | 流式更新元数据——工具运行中就能在 TUI 显示进度 |
+| `ctx.ask()` | 权限请求——向用户申请执行许可 |
+| `attachments` | 文件附件——如图片、PDF 的 base64 内容 |
+
+### 2.2 `define()` 的包装逻辑
+
+`Tool.define()` 不只是创建工具——它还包装了**参数验证**和**输出截断**:
+
+```typescript
+// 文件: packages/opencode/src/tool/tool.ts
+
+export function define(
+ id: string,
+ init: Info["init"] | Awaited["init"]>>,
+): Info {
+ return {
+ id,
+ init: async (initCtx) => {
+ const toolInfo = init instanceof Function ? await init(initCtx) : init
+ const execute = toolInfo.execute
+
+ toolInfo.execute = async (args, ctx) => {
+ // 1. 参数验证(Zod Schema)
+ try {
+ toolInfo.parameters.parse(args)
+ } catch (error) {
+ if (error instanceof z.ZodError && toolInfo.formatValidationError) {
+ throw new Error(toolInfo.formatValidationError(error), { cause: error })
+ }
+ throw new Error(
+ `The ${id} tool was called with invalid arguments: ${error}.` +
+ `\nPlease rewrite the input so it satisfies the expected schema.`,
+ { cause: error },
+ )
+ }
+
+ // 2. 执行工具
+ const result = await execute(args, ctx)
+
+ // 3. 输出截断(除非工具已自行处理)
+ if (result.metadata.truncated !== undefined) {
+ return result
+ }
+ const truncated = await Truncate.output(result.output, {}, initCtx?.agent)
+ return {
+ ...result,
+ output: truncated.content,
+ metadata: {
+ ...result.metadata,
+ truncated: truncated.truncated,
+ outputPath: truncated.truncated ? truncated.outputPath : undefined,
+ },
+ }
+ }
+ return toolInfo
+ },
+ }
+}
+```
+
+> 💡 如果工具的返回值中 `metadata.truncated` 已经有值(如 Bash Tool 自行截断了输出),`define()` 就不会再次截断。这避免了双重截断的问题。
+
+---
+
+## 三、Tool 注册:Registry
+
+### 3.1 内置工具列表
+
+```typescript
+// 文件: packages/opencode/src/tool/registry.ts
+
+const BUILTIN_TOOLS = [
+ InvalidTool, // 无效工具调用的 fallback
+ QuestionTool, // 向用户提问
+ BashTool, // 执行 Shell 命令
+ ReadTool, // 读取文件/目录
+ GlobTool, // 文件模式匹配
+ GrepTool, // 内容搜索
+ EditTool, // 编辑文件
+ WriteTool, // 创建新文件
+ TaskTool, // 委派子任务
+ WebFetchTool, // 获取网页内容
+ TodoWriteTool, // 管理待办事项
+ WebSearchTool, // 网络搜索
+ CodeSearchTool, // 代码语义搜索
+ SkillTool, // 自定义技能
+ ApplyPatchTool, // 应用 Git Patch
+ LspTool, // LSP 诊断(实验性)
+ BatchTool, // 批量工具调用(实验性)
+ PlanExitTool, // Plan 模式退出(实验性)
+]
+```
+
+### 3.2 基于模型的工具过滤
+
+不同模型会看到不同的工具集:
+
+```typescript
+// 文件: packages/opencode/src/tool/registry.ts
+
+// GPT 模型使用 ApplyPatchTool 代替 EditTool/WriteTool
+if (isGptModel) {
+ tools = tools.filter(t => t.id !== "edit" && t.id !== "write")
+} else {
+ tools = tools.filter(t => t.id !== "apply_patch")
+}
+
+// WebSearch/CodeSearch 仅在特定条件下启用
+if (!isOpenCodeProvider && !Flag.OPENCODE_ENABLE_EXA) {
+ tools = tools.filter(t => !["websearch", "codesearch"].includes(t.id))
+}
+```
+
+### 3.3 插件工具加载
+
+```typescript
+// 文件: packages/opencode/src/tool/registry.ts
+
+function fromPlugin(id: string, def: ToolDefinition): Tool.Info {
+ return {
+ id,
+ init: async (initCtx) => ({
+ parameters: z.object(def.args),
+ description: def.description,
+ execute: async (args, toolCtx) => {
+ const result = await def.execute(args, pluginCtx)
+ const out = await Truncate.output(result, {}, initCtx?.agent)
+ return {
+ title: "",
+ output: out.truncated ? out.content : result,
+ metadata: {
+ truncated: out.truncated,
+ outputPath: out.truncated ? out.outputPath : undefined,
+ },
+ }
+ },
+ }),
+ }
+}
+```
+
+---
+
+## 四、权限系统
+
+### 4.1 权限评估算法
+
+权限评估的核心在一个仅有 15 行的文件中:
+
+```typescript
+// 文件: packages/opencode/src/permission/evaluate.ts
+
+import { Wildcard } from "@/util/wildcard"
+
+type Rule = {
+ permission: string
+ pattern: string
+ action: "allow" | "deny" | "ask"
+}
+
+export function evaluate(
+ permission: string,
+ pattern: string,
+ ...rulesets: Rule[][],
+): Rule {
+ const rules = rulesets.flat()
+ const match = rules.findLast(
+ (rule) =>
+ Wildcard.match(permission, rule.permission) &&
+ Wildcard.match(pattern, rule.pattern),
+ )
+ return match ?? { action: "ask", permission, pattern: "*" }
+}
+```
+
+算法非常简洁:
+
+1. 将所有规则集扁平化为一个数组
+2. **从后向前查找**第一个匹配的规则(最后添加的规则优先级最高)
+3. 使用通配符匹配 `permission` 和 `pattern`
+4. 没有匹配规则时,默认 `"ask"`(询问用户)
+
+### 4.2 权限请求流程
+
+当工具需要执行敏感操作时:
+
+```typescript
+// 简化的权限请求流程
+
+// 1. 工具调用 ctx.ask()
+await ctx.ask({
+ permission: "edit",
+ patterns: ["src/index.ts"],
+ always: ["*"],
+ metadata: { filepath, diff },
+})
+
+// 2. 评估规则
+for (const pattern of request.patterns) {
+ const rule = evaluate(request.permission, pattern, ruleset, approved)
+ if (rule.action === "deny") throw new DeniedError()
+ if (rule.action === "allow") continue
+ needsAsk = true
+}
+
+// 3. 如果需要询问,创建 Deferred 并等待
+if (needsAsk) {
+ const deferred = Deferred.make()
+ pending.set(id, { info, deferred })
+ Bus.publish(Event.Asked, info) // 通知 TUI 显示权限弹窗
+ return await deferred // 阻塞直到用户响应
+}
+```
+
+### 4.3 权限级联效应
+
+当用户对一个权限做出回应时,会触发级联效应:
+
+```mermaid
+flowchart TD
+ A[用户回应权限请求] --> B{回应类型}
+ B -->|reject 拒绝| C[当前请求失败]
+ C --> D[同会话所有挂起请求也失败]
+ B -->|once 允许一次| E[当前请求通过]
+ B -->|always 始终允许| F[当前请求通过]
+ F --> G[添加到 approved 规则集]
+ G --> H[自动审批匹配的挂起请求]
+```
+
+这个设计非常巧妙——当用户选择"始终允许编辑 `*`"时,队列中等待的其他编辑请求也会自动通过,无需逐个确认。
+
+---
+
+## 五、Bash Tool 详解
+
+Bash Tool 是最复杂的内置工具,它使用 **Tree-Sitter** 解析 Shell 语法来进行安全分析。
+
+### 5.1 参数定义
+
+```typescript
+// 文件: packages/opencode/src/tool/bash.ts
+
+parameters: z.object({
+ command: z.string().describe("The command to execute"),
+ timeout: z.number().optional().describe("Timeout in milliseconds"),
+ workdir: z.string().optional().describe("Working directory"),
+ description: z.string().describe("Description of what command does"),
+})
+```
+
+### 5.2 命令 AST 分析
+
+Bash Tool 不会盲目执行命令——它先用 Tree-Sitter 解析命令的 AST(抽象语法树)来识别潜在的安全问题:
+
+```typescript
+// 文件: packages/opencode/src/tool/bash.ts
+
+// 1. 解析命令语法
+const ast = parseBashCommand(params.command)
+
+// 2. 分析文件操作
+// 识别: cd, rm, cp, mv, mkdir, touch, chmod, chown, cat 等
+const ops = analyzeFileOperations(ast)
+
+// 3. 检测外部目录访问
+for (const op of ops) {
+ if (!Instance.containsPath(op.path)) {
+ await ctx.ask({
+ permission: "external_directory",
+ patterns: [op.path + "/*"],
+ always: [op.path + "/*"],
+ metadata: {},
+ })
+ }
+}
+
+// 4. 请求 Bash 执行权限
+await ctx.ask({
+ permission: "bash",
+ patterns: [fullCommandText],
+ always: [BashArity.prefix(command).join(" ") + " *"],
+ metadata: {},
+})
+```
+
+> 💡 `BashArity.prefix()` 提取命令的前缀部分用于"始终允许"规则。例如 `git commit -m "fix"` 的前缀是 `git commit`,这样用户选择"始终允许"时,所有 `git commit *` 命令都会被自动批准。
+
+### 5.3 进程生成与管理
+
+```typescript
+// 文件: packages/opencode/src/tool/bash.ts
+
+const proc = spawn(params.command, {
+ shell: Shell.acceptable(), // sh (Unix) 或 cmd.exe (Windows)
+ cwd: params.workdir || Instance.directory,
+ env: { ...process.env, ...shellEnv },
+ stdio: ["ignore", "pipe", "pipe"],
+ detached: process.platform !== "win32",
+})
+```
+
+### 5.4 超时与中止处理
+
+```typescript
+// 文件: packages/opencode/src/tool/bash.ts
+
+// 默认超时 2 分钟
+const timeout = params.timeout ?? DEFAULT_TIMEOUT_MS
+
+// 超时处理
+const timeoutHandler = setTimeout(() => {
+ Shell.killTree(proc.pid) // 杀死整个进程树
+}, timeout)
+
+// 中止信号处理
+ctx.abort.addEventListener("abort", () => {
+ Shell.killTree(proc.pid)
+})
+```
+
+### 5.5 输出流处理
+
+```typescript
+// 文件: packages/opencode/src/tool/bash.ts
+
+// stdout 和 stderr 交织收集
+let output = ""
+const MAX_METADATA = 30 * 1024 // 30KB 元数据截断
+
+proc.stdout.on("data", (data) => {
+ output += data.toString()
+ // 流式更新元数据(TUI 实时显示输出)
+ ctx.metadata({
+ title: params.description,
+ metadata: {
+ output: output.slice(-MAX_METADATA),
+ exitCode: undefined,
+ },
+ })
+})
+
+proc.stderr.on("data", (data) => {
+ output += data.toString()
+ ctx.metadata({ ... })
+})
+```
+
+### 5.6 完整执行流程
+
+```mermaid
+flowchart TD
+ A[接收 command 参数] --> B[Tree-Sitter 解析 AST]
+ B --> C[分析文件操作]
+ C --> D{访问外部目录?}
+ D -->|是| E[请求 external_directory 权限]
+ D -->|否| F[请求 bash 执行权限]
+ E --> F
+ F --> G{权限通过?}
+ G -->|拒绝| H[返回 PermissionError]
+ G -->|通过| I[spawn 子进程]
+ I --> J[设置超时计时器]
+ J --> K[流式收集 stdout/stderr]
+ K --> L[实时更新 metadata]
+ L --> M{进程结束?}
+ M -->|超时| N[Shell.killTree 杀死进程树]
+ M -->|中止| N
+ M -->|正常退出| O[清除超时计时器]
+ N --> P[附加超时/中止信息到输出]
+ O --> P
+ P --> Q[返回 output + exitCode]
+```
+
+---
+
+## 六、Edit Tool 详解
+
+Edit Tool 采用了一种极其健壮的**多策略替换引擎**来处理 LLM 输出中可能存在的格式差异。
+
+### 6.1 参数定义
+
+```typescript
+// 文件: packages/opencode/src/tool/edit.ts
+
+parameters: z.object({
+ filePath: z.string().describe("Absolute path to file"),
+ oldString: z.string().describe("Text to replace"),
+ newString: z.string().describe("Replacement text"),
+ replaceAll: z.boolean().optional().describe("Replace all occurrences"),
+})
+```
+
+### 6.2 九种替换策略
+
+当 LLM 生成的 `oldString` 与文件中的实际内容存在细微差异时(缩进不同、空白字符差异等),Edit Tool 会依次尝试 9 种策略:
+
+| 顺序 | 策略名 | 匹配方式 |
+|------|--------|----------|
+| 1 | **SimpleReplacer** | 精确字符串匹配 |
+| 2 | **LineTrimmedReplacer** | 每行去除首尾空白后匹配 |
+| 3 | **BlockAnchorReplacer** | 用首行和末行作为锚点,Levenshtein 距离模糊匹配 |
+| 4 | **WhitespaceNormalizedReplacer** | 将所有连续空白规范化为单个空格 |
+| 5 | **IndentationFlexibleReplacer** | 忽略缩进差异 |
+| 6 | **EscapeNormalizedReplacer** | 处理转义序列差异(`\n` vs 实际换行) |
+| 7 | **MultiOccurrenceReplacer** | 查找所有匹配(用于 `replaceAll`) |
+| 8 | **TrimmedBoundaryReplacer** | 首尾内容被截断时的匹配 |
+| 9 | **ContextAwareReplacer** | 使用上下文锚点和相似度检查 |
+
+### 6.3 执行流程
+
+```typescript
+// 文件: packages/opencode/src/tool/edit.ts(简化)
+
+async execute(args, ctx) {
+ // 1. 权限检查——展示 diff 给用户
+ const diff = createTwoFilesPatch(args.filePath, args.filePath, args.oldString, args.newString)
+ await ctx.ask({
+ permission: "edit",
+ patterns: [path.relative(Instance.worktree, args.filePath)],
+ always: ["*"],
+ metadata: { filepath: args.filePath, diff },
+ })
+
+ // 2. 读取文件内容
+ const content = await Bun.file(args.filePath).text()
+
+ // 3. 检测行尾风格
+ const lineEnding = detectLineEnding(content)
+
+ // 4. 依次尝试替换策略
+ let result: string | undefined
+ for (const strategy of strategies) {
+ result = strategy.replace(content, args.oldString, args.newString)
+ if (result !== undefined) break
+ }
+
+ if (result === undefined) {
+ throw new Error("oldString not found in file")
+ }
+
+ // 5. 写入文件
+ await Bun.write(args.filePath, result)
+
+ // 6. 自动格式化
+ await Format.run(args.filePath)
+
+ // 7. LSP 诊断
+ const diagnostics = await LSP.diagnostics(args.filePath)
+ if (diagnostics.length > 0) {
+ output += formatDiagnostics(diagnostics)
+ }
+
+ return { title: "Edit applied", output, metadata: {} }
+}
+```
+
+### 6.4 LSP 集成
+
+编辑完成后,Edit Tool 会自动运行 LSP 诊断并报告错误:
+
+```
+Edit applied successfully.
+
+LSP errors detected in this file, please fix:
+
+Error at line 42: Property 'foo' does not exist on type 'Bar'.
+Error at line 58: Expected 2 arguments, but got 1.
+
+```
+
+这让 LLM 能够**立即发现并修复**编辑引入的类型错误或语法错误。
+
+---
+
+## 七、Read Tool 详解
+
+### 7.1 参数定义
+
+```typescript
+// 文件: packages/opencode/src/tool/read.ts
+
+parameters: z.object({
+ filePath: z.string().describe("Absolute path to file or directory"),
+ offset: z.coerce.number().optional().describe("Line number to start (1-indexed)"),
+ limit: z.coerce.number().optional().describe("Max lines to read (default 2000)"),
+})
+```
+
+### 7.2 文件类型检测
+
+Read Tool 能智能处理不同类型的文件:
+
+```mermaid
+flowchart TD
+ A[接收 filePath] --> B{是目录?}
+ B -->|是| C[列出目录内容 + 分页]
+ B -->|否| D{是图片/PDF?}
+ D -->|是| E[返回 base64 附件]
+ D -->|否| F{是二进制文件?}
+ F -->|是| G[返回错误: 二进制文件不可读]
+ F -->|否| H[逐行读取 + 分页]
+```
+
+### 7.3 文本文件读取
+
+```typescript
+// 文件: packages/opencode/src/tool/read.ts(简化)
+
+// 读取限制
+const DEFAULT_LIMIT = 2000 // 默认行数
+const MAX_LINE_LENGTH = 2000 // 最大行长
+const MAX_OUTPUT = 50 * 1024 // 50KB 最大输出
+
+// 逐行读取
+const lines: string[] = []
+let lineNum = 0
+
+for await (const line of readline.createInterface({ input: stream })) {
+ lineNum++
+ if (lineNum < offset) continue
+ if (lines.length >= limit) break
+
+ // 截断超长行
+ const display = line.length > MAX_LINE_LENGTH
+ ? line.slice(0, MAX_LINE_LENGTH) + "... (truncated)"
+ : line
+
+ lines.push(`${lineNum}: ${display}`)
+}
+```
+
+### 7.4 输出格式
+
+```xml
+/home/user/project/src/index.ts
+file
+
+1: import { createApp } from "./app"
+2: import { Config } from "./config"
+3:
+4: const app = createApp()
+5: app.listen(3000)
+...
+(Showing lines 1-2000 of 3456. Use offset=2001 to continue.)
+
+```
+
+### 7.5 二进制检测
+
+```typescript
+// 文件: packages/opencode/src/tool/read.ts
+
+// 检查前 8KB 内容中非打印字符的比例
+// 超过 30% 判定为二进制文件
+const sample = await file.slice(0, 8192).arrayBuffer()
+const bytes = new Uint8Array(sample)
+let nonPrintable = 0
+for (const byte of bytes) {
+ if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) {
+ nonPrintable++
+ }
+}
+const isBinary = nonPrintable / bytes.length > 0.3
+```
+
+---
+
+## 八、权限检查的实际场景
+
+### 8.1 不同工具的权限类型
+
+| 工具 | 权限类型 | 模式示例 |
+|------|---------|---------|
+| Bash | `bash` | `git commit *`, `npm install *` |
+| Bash | `external_directory` | `/etc/*`, `~/../other-project/*` |
+| Edit | `edit` | `src/index.ts`, `*.config.js` |
+| Read | `read` | `src/index.ts`, `*` |
+| Write | `write` | `src/new-file.ts` |
+
+### 8.2 规则匹配示例
+
+假设有以下规则:
+
+```json
+[
+ { "permission": "read", "pattern": "*", "action": "allow" },
+ { "permission": "edit", "pattern": "src/*", "action": "allow" },
+ { "permission": "edit", "pattern": "*.lock", "action": "deny" },
+ { "permission": "bash", "pattern": "git *", "action": "allow" }
+]
+```
+
+评估结果:
+
+| 请求 | 匹配规则 | 结果 |
+|------|---------|------|
+| `read`, `src/index.ts` | `read` + `*` → allow | ✅ 通过 |
+| `edit`, `src/app.ts` | `edit` + `src/*` → allow | ✅ 通过 |
+| `edit`, `package-lock.json` | `edit` + `*.lock` → deny | ❌ 拒绝 |
+| `bash`, `git status` | `bash` + `git *` → allow | ✅ 通过 |
+| `bash`, `rm -rf /` | 无匹配 → ask | ❓ 询问 |
+
+---
+
+## 九、完整的 Tool 执行时序
+
+```mermaid
+sequenceDiagram
+ participant LLM as LLM
+ participant Proc as Processor
+ participant Tool as Tool.define 包装器
+ participant Impl as 工具实现 (Bash/Edit/Read)
+ participant Perm as 权限系统
+ participant User as 用户
+
+ LLM->>Proc: tool-call { toolName, input }
+ Proc->>Proc: 死循环检测
+ Proc->>Tool: execute(args, ctx)
+
+ Tool->>Tool: Zod Schema 验证参数
+ alt 参数无效
+ Tool-->>Proc: Error: invalid arguments
+ Proc->>LLM: tool-error
+ end
+
+ Tool->>Impl: execute(validatedArgs, ctx)
+ Impl->>Perm: ctx.ask({ permission, patterns })
+
+ Perm->>Perm: evaluate(permission, pattern, rulesets)
+ alt action = "allow"
+ Perm-->>Impl: 通过
+ else action = "deny"
+ Perm-->>Impl: DeniedError
+ Impl-->>Tool: throw
+ Tool-->>Proc: tool-error
+ else action = "ask"
+ Perm->>User: 显示权限弹窗
+ User-->>Perm: once | always | reject
+ alt reject
+ Perm-->>Impl: RejectedError
+ Impl-->>Tool: throw
+ Tool-->>Proc: tool-error
+ else once/always
+ Perm-->>Impl: 通过
+ end
+ end
+
+ Impl->>Impl: 执行具体操作
+ Impl->>Impl: ctx.metadata() 流式更新
+ Impl-->>Tool: { title, output, metadata }
+
+ Tool->>Tool: Truncate.output() 截断
+
+ Tool-->>Proc: tool-result
+ Proc->>Proc: 更新 ToolPart (completed)
+ Proc->>LLM: 结果反馈
+```
+
+---
+
+## 十、其他内置工具简介
+
+### 10.1 GlobTool — 文件模式匹配
+
+```typescript
+// 参数: { pattern: "**/*.ts", path?: "/project/src" }
+// 输出: 匹配的文件路径列表
+```
+
+### 10.2 GrepTool — 内容搜索
+
+```typescript
+// 参数: { pattern: "TODO|FIXME", path?: "src/", include?: "*.ts" }
+// 输出: 匹配行及上下文
+```
+
+### 10.3 WriteTool — 创建新文件
+
+```typescript
+// 参数: { filePath: "/abs/path/new.ts", content: "..." }
+// 需要 "write" 权限
+// 文件已存在时报错
+```
+
+### 10.4 TaskTool — 子任务委派
+
+```typescript
+// 参数: { agent: "explore", prompt: "分析这段代码的复杂度" }
+// 创建子会话,委派给指定 Agent
+// 结果汇总后返回给主会话
+```
+
+### 10.5 ApplyPatchTool — Git Patch
+
+```typescript
+// 参数: { patch: "--- a/file\n+++ b/file\n@@ ..." }
+// GPT 模型专用——替代 Edit/Write
+// 支持标准 unified diff 格式
+```
+
+---
+
+## 动手练习
+
+### 练习 1:观察权限规则
+
+在 `opencode.json` 中配置权限规则,然后观察不同操作的权限行为:
+
+```jsonc
+{
+ "permission": [
+ { "permission": "read", "pattern": "*", "action": "allow" },
+ { "permission": "edit", "pattern": "*.test.ts", "action": "deny" }
+ ]
+}
+```
+
+### 练习 2:追踪 Tool 执行
+
+用 DEBUG 日志观察一次 Edit Tool 的执行过程:
+
+```bash
+opencode --print-logs --log-level DEBUG 2>tool.log
+# 发送编辑请求后查看
+grep -E "(tool-call|permission|edit)" tool.log
+```
+
+### 练习 3:创建自定义 Tool
+
+在 `.opencode/tools/` 目录下创建一个简单的自定义工具:
+
+```typescript
+// .opencode/tools/hello.ts
+export default {
+ description: "Say hello",
+ args: { name: z.string() },
+ async execute({ name }) {
+ return `Hello, ${name}!`
+ },
+}
+```
+
+---
+
+## 常见问题
+
+### Q: 为什么 Edit Tool 需要 9 种替换策略?
+
+因为 LLM 生成的代码片段经常与实际文件有细微差异——缩进用了 tab 而不是空格、多了一个空行、转义字符不同等。9 种策略从精确到模糊依次尝试,极大地提高了编辑的成功率。
+
+### Q: Bash Tool 能执行任意命令吗?
+
+技术上可以,但每次执行都需要经过权限系统。首次执行时用户必须确认,之后如果选择了"始终允许",匹配前缀的命令会自动通过。关键命令如 `rm` 通常需要逐次确认。
+
+### Q: 工具的输出截断阈值是多少?
+
+默认约 **50KB**。超过此大小的输出会被截断,截断的完整内容会保存到一个临时文件中,LLM 可以通过 `outputPath` 元数据知道完整内容的位置。
+
+### Q: 权限规则存储在哪里?
+
+权限规则来自三个来源:1) `opencode.json` 配置文件中的静态规则;2) Agent 定义中的默认规则;3) 用户在会话中选择"始终允许"后动态添加的规则(存储在数据库中)。
+
+---
+
+## 小结
+
+本节我们深入探索了 Tool 系统的每一个环节:
+
+1. **Tool.define()**:自动包装参数验证和输出截断
+2. **Registry**:管理 17+ 内置工具,支持基于模型的过滤和插件扩展
+3. **权限系统**:15 行核心算法,支持通配符匹配、级联审批和持久化规则
+4. **Bash Tool**:Tree-Sitter AST 分析 → 安全检查 → 进程管理 → 超时保护
+5. **Edit Tool**:9 种替换策略 → 自动格式化 → LSP 诊断反馈
+6. **Read Tool**:文件类型检测 → 分页读取 → 二进制保护
+
+> ⏭️ 下一节,我们将深入 Session 管理——了解会话的创建、消息持久化、上下文压缩和摘要生成。
diff --git "a/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/04-Session\347\256\241\347\220\206\350\257\246\350\247\243.md" "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/04-Session\347\256\241\347\220\206\350\257\246\350\247\243.md"
new file mode 100644
index 000000000000..95dfc33dc774
--- /dev/null
+++ "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/04-Session\347\256\241\347\220\206\350\257\246\350\247\243.md"
@@ -0,0 +1,862 @@
+# 第四节 Session 管理详解
+
+📍 **你在这里**
+> 在第01章全景视野中,我们用一张大图鸟瞰了整个 OpenCode。现在我们沿着 **Session 创建 → 消息写入 → 持久化 → 上下文压缩 → 摘要生成 → 会话恢复** 这条线路,深入探索每一步的实现细节。
+
+---
+
+## 学习目标
+
+读完本节,你将能够:
+
+1. 理解 Session 和 Message 的**数据库 Schema** 设计
+2. 掌握消息持久化的**事件溯源**(Event Sourcing)模式
+3. 了解上下文压缩(Compaction)的**触发条件和执行策略**
+4. 理解 Prune(修剪)机制如何在不丢失结构的前提下减少上下文
+5. 掌握摘要生成(Summary)和 Diff 快照的工作原理
+
+---
+
+## 一、概念解释:Session 的数据模型
+
+一个 Session(会话)包含一系列交替出现的 User Message(用户消息)和 Assistant Message(助手消息)。每条消息又包含多个 Part(部件),如文本、工具调用、文件附件等。
+
+```
+Session
+├── User Message #1
+│ ├── TextPart: "请读取 index.ts"
+│ └── FilePart: screenshot.png
+├── Assistant Message #1
+│ ├── TextPart: "好的,让我读取..."
+│ ├── ToolPart: read({ filePath: "index.ts" }) → completed
+│ ├── TextPart: "文件内容如下..."
+│ ├── StepStartPart: { snapshot: "abc123" }
+│ └── StepFinishPart: { tokens: {...}, cost: 0.003 }
+├── User Message #2
+│ └── TextPart: "请修改第10行"
+└── Assistant Message #2
+ ├── ToolPart: edit({ filePath: "index.ts", ... }) → completed
+ ├── PatchPart: { hash: "def456", files: ["index.ts"] }
+ └── TextPart: "已完成修改"
+```
+
+---
+
+## 二、数据库 Schema
+
+### 2.1 Session 表
+
+```typescript
+// 文件: packages/opencode/src/session/index.ts
+
+export type Info = {
+ id: SessionID
+ slug: string // URL 友好的短标识
+ projectID: ProjectID
+ workspaceID?: WorkspaceID
+ directory: string // 工作目录
+ parentID?: SessionID // 父会话(fork 场景)
+ title: string
+ version: string
+ summary?: {
+ additions: number // 新增行数
+ deletions: number // 删除行数
+ files: number // 变更文件数
+ diffs?: FileDiff[] // 详细 diff
+ }
+ share?: { url: string } // 分享链接
+ revert?: { // 回滚信息
+ messageID: MessageID
+ partID?: PartID
+ snapshot?: string
+ diff?: string
+ }
+ permission?: Permission.Ruleset // 会话级权限规则
+ time: {
+ created: number
+ updated: number
+ compacting?: number // 正在压缩中
+ archived?: number // 归档时间
+ }
+}
+```
+
+对应的数据库列:
+
+```sql
+CREATE TABLE session (
+ id TEXT PRIMARY KEY,
+ project_id TEXT NOT NULL,
+ workspace_id TEXT,
+ directory TEXT NOT NULL,
+ parent_id TEXT,
+ title TEXT NOT NULL,
+ version TEXT NOT NULL,
+ slug TEXT NOT NULL,
+ share_url TEXT,
+ permission TEXT, -- JSON
+ summary_additions INTEGER,
+ summary_deletions INTEGER,
+ summary_files INTEGER,
+ summary_diffs TEXT, -- JSON
+ time_created INTEGER NOT NULL,
+ time_updated INTEGER NOT NULL,
+ time_compacting INTEGER,
+ time_archived INTEGER,
+ revert TEXT -- JSON
+);
+```
+
+### 2.2 Message 表
+
+```typescript
+// 文件: packages/opencode/src/session/message-v2.ts
+
+// User Message
+export type User = {
+ id: MessageID
+ sessionID: SessionID
+ role: "user"
+ time: { created: number }
+ format?: {
+ type: "text" | "json_schema"
+ schema?: Record
+ }
+ agent: string
+ model: { providerID: ProviderID; modelID: ModelID }
+ system?: string // 自定义 System Prompt
+ variant?: string
+}
+
+// Assistant Message
+export type Assistant = {
+ id: MessageID
+ sessionID: SessionID
+ role: "assistant"
+ parentID: MessageID // 关联的用户消息
+ time: { created: number; completed?: number }
+ error?: ErrorObject
+ agent: string
+ modelID: ModelID
+ providerID: ProviderID
+ summary?: boolean // 压缩消息标记
+ cost: number
+ tokens: {
+ total?: number
+ input: number
+ output: number
+ reasoning: number
+ cache: { read: number; write: number }
+ }
+ structured?: any // JSON Schema 输出
+ finish?: string // 结束原因
+}
+```
+
+```sql
+CREATE TABLE message (
+ id TEXT PRIMARY KEY,
+ session_id TEXT NOT NULL REFERENCES session(id),
+ data TEXT NOT NULL, -- JSON: 完整消息对象
+ time_created INTEGER NOT NULL
+);
+```
+
+### 2.3 Part 表
+
+```sql
+CREATE TABLE part (
+ id TEXT PRIMARY KEY,
+ session_id TEXT NOT NULL REFERENCES session(id),
+ message_id TEXT NOT NULL REFERENCES message(id),
+ data TEXT NOT NULL -- JSON: 完整部件对象
+);
+```
+
+### 2.4 Part 类型一览
+
+```typescript
+// 文件: packages/opencode/src/session/message-v2.ts
+
+// 所有 Part 类型
+type Part =
+ | TextPart // LLM 文本输出
+ | ReasoningPart // 扩展思考(Claude, o1)
+ | ToolPart // 工具调用及结果
+ | FilePart // 文件附件(图片、PDF)
+ | StepStartPart // 步骤开始快照
+ | StepFinishPart // 步骤结束(token 计数)
+ | PatchPart // Git 风格 diff
+ | CompactionPart // 压缩标记
+ | SubtaskPart // 子任务执行
+ | AgentPart // Agent 引用 (@agent)
+ | SnapshotPart // 文件系统快照
+```
+
+---
+
+## 三、消息持久化:事件溯源模式
+
+OpenCode 使用**事件溯源**(Event Sourcing)模式来持久化消息。每次更新都通过 `SyncEvent` 发布,然后由同步处理器写入数据库。
+
+### 3.1 更新消息
+
+```typescript
+// 文件: packages/opencode/src/session/index.ts(简化)
+
+export async function updateMessage(msg: MessageV2.User | MessageV2.Assistant) {
+ // 通过事件溯源写入
+ SyncEvent.run(MessageV2.Event.Updated, {
+ sessionID: msg.sessionID,
+ info: msg,
+ })
+}
+```
+
+### 3.2 更新 Part
+
+```typescript
+// 文件: packages/opencode/src/session/index.ts(简化)
+
+export async function updatePart(part: MessageV2.Part) {
+ SyncEvent.run(MessageV2.Event.PartUpdated, {
+ sessionID: part.sessionID,
+ messageID: part.messageID,
+ info: part,
+ })
+ return part
+}
+```
+
+### 3.3 流式增量更新
+
+对于流式文本输出,使用更轻量的增量更新:
+
+```typescript
+// 文件: packages/opencode/src/session/index.ts(简化)
+
+export async function updatePartDelta(delta: {
+ sessionID: SessionID
+ messageID: MessageID
+ partID: PartID
+ field: string
+ delta: string
+}) {
+ // 只通过 Bus 广播,不写入数据库
+ Bus.publish(MessageV2.Event.PartDelta, delta)
+}
+```
+
+> 💡 **关键区分**:`updatePart()` 触发数据库写入(持久化),`updatePartDelta()` 只触发 Bus 广播(内存通知)。这避免了每个 token 都写入数据库的 I/O 开销。
+
+### 3.4 事件流架构
+
+```mermaid
+flowchart LR
+ A[Processor] -->|updateMessage| B[SyncEvent]
+ A -->|updatePart| B
+ A -->|updatePartDelta| C[Bus 广播]
+ B -->|写入| D[(SQLite)]
+ B -->|发布| E[SSE 推送]
+ C -->|发布| F[TUI 实时更新]
+ D -->|查询| G[Session.messages]
+```
+
+---
+
+## 四、Session 生命周期
+
+### 4.1 创建会话
+
+```typescript
+// 文件: packages/opencode/src/session/index.ts(简化)
+
+export async function create() {
+ return createNext({})
+}
+
+export async function createNext(input: {
+ parentID?: SessionID
+ title?: string
+ permission?: Permission.Ruleset
+}) {
+ const id = SessionID.ascending()
+ const session: Info = {
+ id,
+ slug: generateSlug(),
+ projectID: Instance.current.project.id,
+ directory: Instance.directory,
+ title: input.title ?? "",
+ version: Installation.VERSION,
+ time: {
+ created: Date.now(),
+ updated: Date.now(),
+ },
+ // ...
+ }
+
+ // 写入数据库
+ Database.transaction((tx) => {
+ tx.insert(SessionTable).values(toRow(session)).run()
+ })
+
+ // 发布创建事件
+ Bus.publish(Session.Event.Created, { session })
+ return session
+}
+```
+
+### 4.2 Fork 会话
+
+Fork(分叉)允许从历史某一点创建新的分支会话:
+
+```typescript
+// 文件: packages/opencode/src/session/index.ts(简化)
+
+export async function fork(sessionID: SessionID, messageID?: MessageID) {
+ // 1. 创建新会话
+ const forked = await createNext({
+ parentID: sessionID,
+ title: original.title + " (fork)",
+ permission: original.permission,
+ })
+
+ // 2. 复制消息到分叉点
+ const messages = await Session.messages({ sessionID })
+ for (const msg of messages) {
+ if (messageID && msg.info.id > messageID) break
+ // 复制消息和部件到新会话
+ await copyMessage(msg, forked.id)
+ }
+
+ return forked
+}
+```
+
+### 4.3 查询消息
+
+```typescript
+// 文件: packages/opencode/src/session/index.ts(简化)
+
+export async function messages(input: { sessionID: SessionID; limit?: number }) {
+ // 从数据库查询
+ const rows = Database.Client()
+ .select()
+ .from(MessageTable)
+ .where(eq(MessageTable.session_id, input.sessionID))
+ .orderBy(asc(MessageTable.time_created))
+ .limit(input.limit ?? Infinity)
+ .all()
+
+ // 关联查询 Parts
+ const parts = Database.Client()
+ .select()
+ .from(PartTable)
+ .where(eq(PartTable.session_id, input.sessionID))
+ .all()
+
+ // 组装 WithParts 结构
+ return rows.map(row => ({
+ info: JSON.parse(row.data),
+ parts: parts.filter(p => p.message_id === row.id).map(p => JSON.parse(p.data)),
+ }))
+}
+```
+
+---
+
+## 五、上下文压缩(Compaction)
+
+当对话历史超过模型的上下文窗口时,OpenCode 会自动触发压缩。
+
+### 5.1 溢出检测
+
+```typescript
+// 文件: packages/opencode/src/session/compaction.ts
+
+const COMPACTION_BUFFER = 20_000
+
+export async function isOverflow(input: {
+ tokens: MessageV2.Assistant["tokens"]
+ model: Provider.Model
+}) {
+ const config = await Config.get()
+ if (config.compaction?.auto === false) return false
+
+ const context = input.model.limit.context
+ if (context === 0) return false
+
+ const count =
+ input.tokens.total ||
+ input.tokens.input +
+ input.tokens.output +
+ input.tokens.cache.read +
+ input.tokens.cache.write
+
+ const reserved = config.compaction?.reserved ??
+ Math.min(COMPACTION_BUFFER, ProviderTransform.maxOutputTokens(input.model))
+ const usable = input.model.limit.input
+ ? input.model.limit.input - reserved
+ : context - ProviderTransform.maxOutputTokens(input.model)
+
+ return count >= usable
+}
+```
+
+核心公式:
+
+```
+可用上下文 = min(input_limit, context_limit - max_output) - reserved_buffer
+如果 实际使用 token ≥ 可用上下文 → 触发压缩
+```
+
+### 5.2 Prune(修剪)机制
+
+在触发完整压缩之前,OpenCode 先尝试"修剪"——清除旧工具调用的输出内容:
+
+```typescript
+// 文件: packages/opencode/src/session/compaction.ts
+
+export const PRUNE_MINIMUM = 20_000
+export const PRUNE_PROTECT = 40_000
+
+const PRUNE_PROTECTED_TOOLS = ["skill"]
+
+export async function prune(input: { sessionID: SessionID }) {
+ const config = await Config.get()
+ if (config.compaction?.prune === false) return
+
+ const msgs = await Session.messages({ sessionID: input.sessionID })
+ let total = 0
+ let pruned = 0
+ let turns = 0
+
+ // 从后向前遍历
+ loop: for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) {
+ const msg = msgs[msgIndex]
+ if (msg.info.role === "user") turns++
+ if (turns < 2) continue // 保护最近 2 轮
+ if (msg.info.role === "assistant" && msg.info.summary) break loop
+
+ for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) {
+ const part = msg.parts[partIndex]
+ if (part.type !== "tool") continue
+ if (part.state.status !== "completed") continue
+ if (PRUNE_PROTECTED_TOOLS.includes(part.tool)) continue
+
+ const size = Token.estimate(part.state.output ?? "")
+ total += size
+
+ if (total <= PRUNE_PROTECT) continue // 保护前 40K tokens
+ // 超过保护阈值,开始修剪
+ if (part.state.time.compacted) continue
+
+ part.state.time.compacted = Date.now()
+ part.state.output = "[pruned]"
+ await Session.updatePart(part)
+ pruned += size
+ }
+ }
+}
+```
+
+修剪策略的核心思想:
+
+```mermaid
+flowchart TD
+ A[从后向前遍历消息] --> B{最近 2 轮?}
+ B -->|是| C[跳过,不修剪]
+ B -->|否| D{是完成的工具调用?}
+ D -->|否| A
+ D -->|是| E{累计 token < 40K?}
+ E -->|是| F[保留,计入保护配额]
+ E -->|否| G["替换输出为 [pruned]"]
+ G --> H[记录压缩时间戳]
+ F --> A
+ H --> A
+```
+
+**关键设计**:
+- **保护最近 2 轮**:避免修剪用户刚看到的内容
+- **保护前 40K tokens**:保留一定量的上下文
+- **保护 `skill` 工具**:自定义技能的输出通常包含重要说明
+- **标记而非删除**:`time.compacted` 时间戳标记修剪,方便调试
+
+### 5.3 压缩处理
+
+当修剪不够时,执行完整压缩——让 LLM 生成对话摘要:
+
+```typescript
+// 文件: packages/opencode/src/session/compaction.ts(简化)
+
+export async function process(input: {
+ parentID: MessageID
+ messages: MessageV2.WithParts[]
+ sessionID: SessionID
+ abort: AbortSignal
+ auto: boolean
+ overflow?: boolean
+}) {
+ // 1. 创建压缩助手消息
+ const msg = await Session.updateMessage({
+ id: MessageID.ascending(),
+ role: "assistant",
+ sessionID: input.sessionID,
+ parentID: input.parentID,
+ agent: "compaction",
+ summary: true, // 标记为压缩消息
+ cost: 0,
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
+ time: { created: Date.now() },
+ })
+
+ // 2. 调用 LLM 生成摘要
+ // 使用专门的压缩 Prompt,要求:
+ // - 保留关键决策和上下文
+ // - 记录已执行的文件变更
+ // - 压缩重复信息
+ const processor = SessionProcessor.create({
+ assistantMessage: msg,
+ sessionID: input.sessionID,
+ model: compactionModel,
+ abort: input.abort,
+ })
+
+ await processor.process({
+ system: [COMPACTION_PROMPT],
+ messages: MessageV2.toModelMessages(input.messages, compactionModel),
+ tools: {}, // 压缩时不提供工具
+ agent: compactionAgent,
+ // ...
+ })
+
+ // 3. 发布压缩完成事件
+ Bus.publish(Event.Compacted, { sessionID: input.sessionID })
+}
+```
+
+### 5.4 压缩前后的消息结构
+
+```
+压缩前:
+├── User #1: "帮我重构 auth 模块"
+├── Assistant #1: [read auth.ts] [edit auth.ts] [read test.ts] "重构完成"
+├── User #2: "测试通过了吗?"
+├── Assistant #2: [bash "npm test"] "3 个测试通过"
+├── User #3: "再优化一下性能"
+├── Assistant #3: [read auth.ts] [edit auth.ts] "已优化"
+
+压缩后:
+├── Assistant (summary=true): "对话摘要:
+│ 1. 用户请求重构 auth 模块
+│ 2. 已完成重构,修改了 auth.ts
+│ 3. 测试通过(3/3)
+│ 4. 进行了性能优化
+│ 5. 当前状态:auth.ts 已更新..."
+├── CompactionPart: { auto: true }
+├── User #3: "再优化一下性能" ← 最后的用户消息被保留
+└── Assistant #3: ... ← 继续对话
+```
+
+---
+
+## 六、摘要生成(Summary)
+
+### 6.1 会话级摘要
+
+每次步骤完成后,都会异步生成摘要:
+
+```typescript
+// 文件: packages/opencode/src/session/summary.ts(简化)
+
+export async function summarize(input: {
+ sessionID: SessionID
+ messageID: MessageID
+}) {
+ const all = await Session.messages({ sessionID: input.sessionID })
+
+ // 计算整个会话的文件变更
+ const diffs = await computeDiff({ messages: all })
+ await Session.setSummary({
+ additions: sum(diffs.map(d => d.additions)),
+ deletions: sum(diffs.map(d => d.deletions)),
+ files: diffs.length,
+ })
+
+ // 存储详细 diff 到文件
+ await Storage.write(["session_diff", input.sessionID], diffs)
+
+ // 广播 diff 事件
+ Bus.publish(Session.Event.Diff, { sessionID: input.sessionID, diff: diffs })
+}
+```
+
+### 6.2 消息级摘要
+
+```typescript
+// 文件: packages/opencode/src/session/summary.ts(简化)
+
+// 每条用户消息也有独立的摘要
+const msgWithParts = messages.find(m => m.info.id === input.messageID)
+const diffs = await computeDiff({ messages: [userMsg, ...assistantMsgs] })
+userMsg.summary = { diffs }
+await Session.updateMessage(userMsg)
+```
+
+### 6.3 Diff 计算
+
+```typescript
+// 文件: packages/opencode/src/session/summary.ts(简化)
+
+export async function computeDiff(input: {
+ messages: MessageV2.WithParts[]
+}) {
+ // 找最早的 "step-start" 快照作为起点
+ let from: string | undefined
+ // 找最晚的 "step-finish" 快照作为终点
+ let to: string | undefined
+
+ for (const msg of input.messages) {
+ for (const part of msg.parts) {
+ if (part.type === "step-start" && part.snapshot) {
+ from = from ?? part.snapshot
+ }
+ if (part.type === "step-finish" && part.snapshot) {
+ to = part.snapshot
+ }
+ }
+ }
+
+ if (from && to) {
+ return Snapshot.diffFull(from, to)
+ }
+ return []
+}
+```
+
+### 6.4 Diff 输出格式
+
+```typescript
+export type FileDiff = {
+ file: string // 文件路径
+ additions: number // 新增行数
+ deletions: number // 删除行数
+ hunks: Hunk[] // diff 区块
+}
+```
+
+---
+
+## 七、会话恢复
+
+### 7.1 继续上次会话
+
+```bash
+# 继续上次的会话
+opencode --continue
+
+# 继续指定会话
+opencode --session
+
+# Fork 后继续
+opencode --session --fork
+```
+
+### 7.2 消息流过滤
+
+恢复会话时,需要跳过已压缩的消息:
+
+```typescript
+// 文件: packages/opencode/src/session/message-v2.ts(简化)
+
+export async function filterCompacted(
+ messages: AsyncIterable,
+) {
+ const result: MessageV2.WithParts[] = []
+ let lastCompactionIndex = -1
+
+ // 找到最后一个压缩标记
+ for await (const msg of messages) {
+ result.push(msg)
+ const hasCompaction = msg.parts.some(p => p.type === "compaction")
+ if (hasCompaction) {
+ lastCompactionIndex = result.length - 1
+ }
+ }
+
+ // 跳过压缩标记之前的所有消息
+ if (lastCompactionIndex >= 0) {
+ return result.slice(lastCompactionIndex)
+ }
+ return result
+}
+```
+
+---
+
+## 八、完整数据流
+
+```mermaid
+flowchart TD
+ subgraph 创建阶段
+ A[用户发送消息] --> B[Session.create / 获取现有]
+ B --> C[创建 User Message]
+ C --> D[创建 Parts: text/file/agent]
+ end
+
+ subgraph 处理阶段
+ D --> E[loop 循环]
+ E --> F[Processor 处理流事件]
+ F --> G[创建 Assistant Message]
+ G --> H[流式写入 Parts]
+ H -->|text-delta| I[Bus 广播增量]
+ H -->|text-end/tool-result| J[SyncEvent 持久化]
+ H -->|step-finish| K[创建 Snapshot + Patch]
+ end
+
+ subgraph 维护阶段
+ K --> L{上下文溢出?}
+ L -->|否| M[SessionSummary.summarize]
+ L -->|是| N[Prune 修剪旧工具输出]
+ N --> O[Compaction 压缩摘要]
+ O --> P[创建压缩标记 Part]
+ M --> Q[更新 Session.summary]
+ Q --> R[存储 diff 到文件]
+ end
+
+ subgraph 恢复阶段
+ S[opencode --continue] --> T[加载 Session]
+ T --> U[filterCompacted 跳过已压缩]
+ U --> V[resume loop 继续循环]
+ end
+```
+
+---
+
+## 九、Session 事件系统
+
+Session 通过 Bus 发布多种事件,TUI 和 API 客户端可以订阅:
+
+| 事件 | 触发时机 | 包含数据 |
+|------|---------|---------|
+| `Session.Event.Created` | 会话创建 | `{ session }` |
+| `Session.Event.Updated` | 会话元数据更新 | `{ session }` |
+| `Session.Event.Deleted` | 会话删除 | `{ sessionID }` |
+| `MessageV2.Event.Updated` | 消息创建/更新 | `{ sessionID, info }` |
+| `MessageV2.Event.PartUpdated` | Part 创建/更新 | `{ sessionID, messageID, info }` |
+| `MessageV2.Event.PartDelta` | 流式增量 | `{ sessionID, messageID, partID, delta }` |
+| `Session.Event.Error` | 错误发生 | `{ sessionID, error }` |
+| `Session.Event.Diff` | Diff 计算完成 | `{ sessionID, diff }` |
+| `SessionCompaction.Event.Compacted` | 压缩完成 | `{ sessionID }` |
+
+---
+
+## 十、数据库事务管理
+
+OpenCode 使用上下文感知的事务管理:
+
+```typescript
+// 文件: packages/opencode/src/storage/db.ts
+
+export function transaction(
+ callback: (tx: TxOrDb) => NotPromise,
+ options?: { behavior?: "deferred" | "immediate" | "exclusive" },
+): NotPromise {
+ try {
+ // 尝试使用当前上下文中的事务
+ return callback(ctx.use().tx)
+ } catch (err) {
+ if (err instanceof Context.NotFound) {
+ // 没有现有事务 → 创建新事务
+ const effects: (() => void | Promise)[] = []
+ const result = Client().transaction(
+ (tx: TxOrDb) => {
+ return ctx.provide({ tx, effects }, () => callback(tx))
+ },
+ { behavior: options?.behavior },
+ )
+ // 事务成功后执行副作用
+ for (const effect of effects) effect()
+ return result as NotPromise
+ }
+ throw err
+ }
+}
+```
+
+**关键设计**:
+- 如果已在事务上下文中,复用现有事务(嵌套友好)
+- 如果不在事务中,创建新事务
+- 副作用(如 Bus 事件发布)在事务**提交后**才执行
+
+---
+
+## 动手练习
+
+### 练习 1:查看 Session 数据库
+
+```bash
+# 查看数据库路径
+ls ~/.local/share/opencode/opencode*.db
+
+# 用 sqlite3 查看表结构
+sqlite3 ~/.local/share/opencode/opencode.db ".schema session"
+sqlite3 ~/.local/share/opencode/opencode.db ".schema message"
+sqlite3 ~/.local/share/opencode/opencode.db ".schema part"
+```
+
+### 练习 2:观察压缩过程
+
+创建一个会使用大量 token 的对话(让 LLM 读取多个大文件),然后观察日志中的压缩事件:
+
+```bash
+opencode --print-logs 2>session.log
+# 发送需要大量上下文的请求
+grep -E "(compaction|prune|overflow)" session.log
+```
+
+### 练习 3:Fork 会话
+
+```bash
+# 列出会话
+opencode session list
+
+# Fork 一个会话
+opencode --session --fork
+# 在 fork 的会话中做不同的修改
+```
+
+---
+
+## 常见问题
+
+### Q: 为什么消息和 Part 分开存储?
+
+这种设计有两个优势:1) Part 可以独立更新而不需要重写整个消息(工具调用状态频繁变化);2) 查询时可以选择只加载消息元数据,需要时再加载 Part(延迟加载)。
+
+### Q: 压缩会丢失历史信息吗?
+
+不会完全丢失。压缩后的摘要保留了关键决策和文件变更记录。原始消息仍然存在于数据库中,只是在 `filterCompacted()` 时被跳过。如果需要,可以通过数据库直接查看完整历史。
+
+### Q: `updatePartDelta` 的增量数据如果丢失怎么办?
+
+没有关系。`updatePartDelta` 是纯广播机制(用于 TUI 实时更新),最终的完整内容会通过 `updatePart()` 持久化到数据库。TUI 重新连接时会从数据库读取完整状态。
+
+### Q: Prune 和 Compaction 有什么区别?
+
+**Prune**(修剪)只清除旧工具调用的输出文本,不调用 LLM,速度快且成本为零。**Compaction**(压缩)调用 LLM 生成对话摘要,替换整个历史前缀,更彻底但需要消耗 token。Prune 是压缩的轻量级替代方案。
+
+---
+
+## 小结
+
+本节我们深入探索了 Session 管理的完整生命周期:
+
+1. **数据模型**:Session → Message → Part 三层结构,JSON 序列化存储在 SQLite
+2. **持久化模式**:事件溯源(SyncEvent)+ 增量广播(Bus)双通道
+3. **上下文压缩**:`isOverflow()` 检测 → `prune()` 修剪旧输出 → `process()` LLM 摘要
+4. **摘要生成**:Snapshot 快照 + Diff 计算,追踪每步文件变更
+5. **会话恢复**:`filterCompacted()` 跳过已压缩消息,`resume()` 恢复循环
+6. **事务管理**:上下文感知的嵌套事务 + 提交后副作用
+
+> ⏭️ 下一节,我们将深入 Provider 调用系统——了解多提供商适配、认证、消息转换和流式处理。
diff --git "a/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/05-Provider\350\260\203\347\224\250\350\257\246\350\247\243.md" "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/05-Provider\350\260\203\347\224\250\350\257\246\350\247\243.md"
new file mode 100644
index 000000000000..16560eb9d59b
--- /dev/null
+++ "b/all-in-one-book/03-\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243/05-Provider\350\260\203\347\224\250\350\257\246\350\247\243.md"
@@ -0,0 +1,354 @@
+# Provider 调用详解
+
+📍 **你在这里**
+> 在第01章全景视野中,我们看到 Provider 是连接 Agent 与 LLM 的"万能适配器"。现在我们深入探索 Provider 从配置到调用的完整链路。
+
+> 📌 Provider 系统负责将 20+ 种 LLM 服务统一为一致的调用接口,处理认证、模型解析、参数转换和流式响应。
+
+## 本章学习目标
+
+- [ ] 理解 Provider 和 Model 的关系
+- [ ] 掌握模型解析(Model Resolution)的完整流程
+- [ ] 理解认证凭据的管理方式
+- [ ] 知道 LLM API 调用是如何构造和发送的
+- [ ] 理解流式响应的处理机制
+
+## 概念解释
+
+### Provider 是什么?
+
+提供商(Provider)是 OpenCode 与各种大语言模型(LLM)服务之间的适配层。每个 Provider 对应一个 LLM 服务商,如 Anthropic(Claude)、OpenAI(GPT)、Google(Gemini)等。
+
+```
+// 文件: packages/opencode/src/provider/schema.ts
+// ProviderID 和 ModelID 都是品牌类型(Branded Type)
+export const ProviderID = Schema.String.pipe(Schema.brand("ProviderID"))
+export const ModelID = Schema.String.pipe(Schema.brand("ModelID"))
+```
+
+### 支持的 Provider 列表
+
+| Provider ID | 服务商 | AI SDK 包 | 代表模型 |
+|-------------|--------|-----------|----------|
+| `anthropic` | Anthropic | `@ai-sdk/anthropic` | Claude 4, Claude 3.5 Sonnet |
+| `openai` | OpenAI | `@ai-sdk/openai` | GPT-4o, GPT-4.1, o3 |
+| `google` | Google | `@ai-sdk/google` | Gemini 2.5 Pro/Flash |
+| `amazon-bedrock` | AWS | `@ai-sdk/amazon-bedrock` | Claude via Bedrock |
+| `azure` | Microsoft | `@ai-sdk/azure` | GPT via Azure |
+| `openrouter` | OpenRouter | `@openrouter/ai-sdk-provider` | 多模型路由 |
+| `mistral` | Mistral | `@ai-sdk/mistral` | Mistral Large |
+| `groq` | Groq | `@ai-sdk/groq` | Llama 3, Mixtral |
+| `deepinfra` | DeepInfra | `@ai-sdk/openai` (兼容) | 各开源模型 |
+| `gitlab` | GitLab | `gitlab-ai-provider` | GitLab Duo |
+| `opencode` | OpenCode | 内置 | Zen (托管模型) |
+| `copilot` | GitHub Copilot | 自定义 | GPT-4o via Copilot |
+| `cerebras` | Cerebras | `@ai-sdk/cerebras` | 快速推理 |
+| `cohere` | Cohere | `@ai-sdk/cohere` | Command R |
+| `together` | Together AI | `@ai-sdk/togetherai` | 开源模型 |
+| `perplexity` | Perplexity | `@ai-sdk/openai` (兼容) | 搜索增强 |
+| `vercel` | Vercel | `@ai-sdk/vercel` | v0 模型 |
+
+```
+// 文件: packages/opencode/src/provider/provider.ts
+// BUNDLED_PROVIDERS 定义了所有内置的 Provider SDK 映射
+const BUNDLED_PROVIDERS: Record Promise<...>> = {
+ "@ai-sdk/anthropic": () => import("@ai-sdk/anthropic"),
+ "@ai-sdk/openai": () => import("@ai-sdk/openai"),
+ "@ai-sdk/google": () => import("@ai-sdk/google-vertex"),
+ // ... 15+ 个 Provider
+}
+```
+
+## 模型解析流程
+
+当用户选择一个模型(或使用默认模型)时,Provider 系统需要解析出完整的模型信息:
+
+```mermaid
+flowchart TD
+ A[用户指定 modelID + providerID] --> B{配置中有自定义模型?}
+ B -->|是| C[使用配置中的模型定义]
+ B -->|否| D[从 models.dev 获取模型目录]
+ D --> E{models.dev 有此模型?}
+ E -->|是| F[使用远程模型定义]
+ E -->|否| G[使用默认参数创建模型]
+ C --> H[合并模型能力/限制/成本]
+ F --> H
+ G --> H
+ H --> I[Provider.Model 完整对象]
+ I --> J[获取 LanguageModel 实例]
+```
+
+### Model 数据结构
+
+```typescript
+// 文件: packages/opencode/src/provider/provider.ts
+// Provider.Model 包含模型的所有元数据
+Model = {
+ id: ModelID // 如 "claude-sonnet-4-20250514"
+ providerID: ProviderID // 如 "anthropic"
+ family?: string // 如 "claude"
+ capabilities: {
+ temperature: boolean // 是否支持温度调节
+ reasoning: boolean // 是否支持推理模式
+ interleaved?: boolean // 是否支持交错内容
+ attachment: boolean // 是否支持文件附件
+ toolCall: boolean // 是否支持工具调用
+ }
+ cost?: {
+ input: number // 每百万 token 输入成本
+ output: number // 每百万 token 输出成本
+ cache_read?: number // 缓存读取成本
+ cache_write?: number // 缓存写入成本
+ }
+ limit: {
+ context: number // 上下文窗口大小(token 数)
+ output: number // 最大输出 token 数
+ }
+ variants?: Record> // 模型变体
+ headers?: Record // 自定义请求头
+}
+```
+
+### models.dev 集成
+
+OpenCode 使用 [models.dev](https://models.dev) 作为模型目录服务,获取最新的模型信息:
+
+```typescript
+// 文件: packages/opencode/src/provider/models.ts
+// 从 models.dev API 获取模型目录
+// 包含本地缓存,避免重复请求
+// 可通过 OPENCODE_DISABLE_MODELS_FETCH 禁用
+```
+
+## 认证管理
+
+### 认证类型
+
+```typescript
+// 文件: packages/opencode/src/auth/index.ts
+// 三种认证方式
+type AuthInfo =
+ | { type: "oauth"; refresh: string; access: string; expires: number }
+ | { type: "api"; key: string }
+ | { type: "wellknown"; key: string; token: string }
+```
+
+### 认证解析流程
+
+```mermaid
+flowchart TD
+ A[需要 Provider 认证] --> B{检查环境变量}
+ B -->|有| C[使用环境变量 API Key]
+ B -->|无| D{检查配置文件}
+ D -->|有| E[使用配置中的凭据]
+ D -->|无| F{检查 auth.json}
+ F -->|有| G[使用存储的认证信息]
+ F -->|无| H{是否 OAuth Provider?}
+ H -->|是| I[触发 OAuth 流程]
+ H -->|否| J[报错: 缺少认证]
+ C --> K[构造请求头]
+ E --> K
+ G --> K
+ I --> K
+```
+
+### 凭据存储
+
+认证信息存储在 `~/.opencode/auth.json`,权限设为 `0o600`(仅所有者可读写):
+
+```typescript
+// 文件: packages/opencode/src/auth/index.ts
+// 安全存储认证信息
+// set(key, info) - 保存凭据
+// get(providerID) - 获取凭据
+// remove(key) - 删除凭据
+```
+
+## API 调用构造
+
+### LanguageModel 获取
+
+```typescript
+// 文件: packages/opencode/src/provider/provider.ts
+// getLanguage(model) 将 Provider.Model 转换为 AI SDK 的 LanguageModel
+// 1. 查找对应的 BUNDLED_PROVIDER SDK
+// 2. 获取认证凭据
+// 3. 创建 provider 实例(带 baseURL、headers)
+// 4. 调用 provider(modelID) 返回 LanguageModel
+```
+
+### 参数转换(ProviderTransform)
+
+```typescript
+// 文件: packages/opencode/src/provider/transform.ts
+// ProviderTransform.options(model, agent) 将模型+代理配置转为 AI SDK 选项
+// - maxTokens: 根据 model.limit.output 设置
+// - temperature: 根据 agent 配置或模型默认值
+// - topP: 同上
+// - providerOptions: Provider 特定的参数
+// - anthropic: thinking.type, cacheControl
+// - openai: reasoningEffort
+// - google: thinkingConfig
+```
+
+### Provider 特定参数
+
+不同 Provider 需要不同的参数格式:
+
+| Provider | 特殊参数 | 说明 |
+|----------|---------|------|
+| Anthropic | `thinking.type`, `cacheControl` | 推理模式和缓存控制 |
+| OpenAI | `reasoningEffort` | 推理强度(low/medium/high) |
+| Google | `thinkingConfig.thinkingBudget` | 思考预算 token 数 |
+| Bedrock | `anthropic.thinking` | Bedrock 中的 Claude 推理 |
+| OpenRouter | `x-openrouter-*` 请求头 | 路由控制 |
+
+## 流式响应处理
+
+### 调用流程
+
+```mermaid
+sequenceDiagram
+ participant Agent
+ participant LLM as LLM Module
+ participant Provider
+ participant SDK as AI SDK
+ participant API as LLM API
+
+ Agent->>LLM: stream(input)
+ LLM->>Provider: getLanguage(model)
+ Provider-->>LLM: LanguageModel
+ LLM->>SDK: streamText({model, messages, tools, ...})
+ SDK->>API: HTTP POST (streaming)
+
+ loop 流式响应
+ API-->>SDK: SSE chunk
+ SDK-->>LLM: onChunk callback
+ LLM-->>Agent: text/toolCall/reasoning delta
+ end
+
+ SDK-->>LLM: onFinish callback
+ LLM->>LLM: 记录 usage (token 计数)
+ LLM-->>Agent: 完整响应
+```
+
+### 流式事件类型
+
+```typescript
+// 文件: packages/opencode/src/session/llm.ts
+// streamText 返回的事件流包含以下类型:
+// - text-delta: 文本增量
+// - tool-call: 工具调用请求
+// - tool-call-streaming-start: 工具调用流开始
+// - tool-call-delta: 工具调用参数增量
+// - tool-result: 工具执行结果
+// - reasoning: 推理过程文本
+// - finish: 完成信号(含 usage 统计)
+```
+
+### Token 计费
+
+```typescript
+// 文件: packages/opencode/src/session/llm.ts
+// onFinish 回调中记录 token 使用量:
+// usage.promptTokens - 输入 token 数
+// usage.completionTokens - 输出 token 数
+// usage.totalTokens - 总 token 数
+// 结合 model.cost 计算实际费用
+```
+
+## 错误处理
+
+Provider 调用可能遇到的错误和处理策略:
+
+| 错误类型 | 原因 | 处理方式 |
+|----------|------|---------|
+| 认证失败 (401) | API Key 无效或过期 | 提示用户重新配置 |
+| 配额超限 (429) | 速率限制或余额不足 | 自动重试 + 退避 |
+| 模型不可用 (404) | 模型 ID 错误或已下线 | 提示切换模型 |
+| 超时 | 网络问题或响应过长 | 重试机制 |
+| 内容过滤 | 提供商安全策略 | 返回错误信息 |
+
+```typescript
+// 文件: packages/opencode/src/provider/error.ts
+// ProviderError 封装了各种 Provider 相关错误
+// 包含友好的错误消息和原始错误信息
+```
+
+## 插件扩展点
+
+Provider 系统提供多个插件钩子(Hook):
+
+```typescript
+// 文件: packages/opencode/src/plugin/index.ts
+// Plugin hooks 相关到 Provider:
+// - chat.params: 修改 LLM 调用参数(temperature, topP 等)
+// - chat.headers: 添加自定义请求头
+// - auth: 自定义认证流程
+```
+
+## 动手练习
+
+### 练习 1:查看当前 Provider 配置
+
+```bash
+# 启动 OpenCode 后查看可用 Provider
+opencode providers
+```
+
+### 练习 2:配置一个新的 Provider
+
+在项目的 `.opencode/opencode.jsonc` 中添加:
+
+```jsonc
+{
+ "provider": {
+ "openai": {
+ "options": {}
+ }
+ }
+}
+```
+
+然后设置环境变量:
+```bash
+export OPENAI_API_KEY="sk-..."
+```
+
+### 练习 3:阅读 Provider 源码
+
+```bash
+# 查看所有支持的 Provider
+cat packages/opencode/src/provider/provider.ts | grep "BUNDLED_PROVIDERS" -A 30
+
+# 查看模型转换逻辑
+cat packages/opencode/src/provider/transform.ts | head -100
+```
+
+## 常见问题
+
+**Q: 如何添加自定义 Provider?**
+A: 在配置中设置 provider,使用 OpenAI 兼容的 baseURL 指向自定义端点。许多 Provider 使用 `@ai-sdk/openai` 的兼容模式。
+
+**Q: 为什么有些模型不支持工具调用?**
+A: 模型的 `capabilities.toolCall` 标志决定了是否支持。某些小模型或特殊模型(如嵌入模型)不支持工具调用。
+
+**Q: 如何切换模型?**
+A: 在 TUI 中使用 `/model` 命令或快捷键选择模型。也可以在配置中设置默认模型。
+
+## 本章小结
+
+Provider 系统是 OpenCode 多模型支持的核心。通过统一的抽象层,它将 20+ 种 LLM 服务整合为一致的接口:
+
+1. **模型解析**:从配置、models.dev 或默认值获取模型信息
+2. **认证管理**:支持 API Key、OAuth、环境变量等多种方式
+3. **参数转换**:将通用参数转为 Provider 特定格式
+4. **流式处理**:基于 Vercel AI SDK 的统一流式接口
+5. **错误处理**:友好的错误提示和自动重试
+
+## 延伸阅读
+
+- [01-全景视野/04-一次对话的完整旅程](../01-全景视野/04-一次对话的完整旅程.md) — Provider 在对话链路中的位置
+- [04-核心引擎/04-Provider-LLM提供商](../04-核心引擎-packages-opencode/04-Provider-LLM提供商.md) — Provider 模块源码深入分析
+- [09-周边知识/03-AI-SDK与LLM调用](../09-周边知识与生态/03-AI-SDK与LLM调用.md) — Vercel AI SDK 详解
+- [06-扩展与集成/06-Provider适配](../06-扩展与集成/06-Provider适配.md) — 如何适配新 Provider
diff --git "a/all-in-one-book/04-\346\240\270\345\277\203\345\274\225\346\223\216-packages-opencode/08-Command\347\263\273\347\273\237.md" "b/all-in-one-book/04-\346\240\270\345\277\203\345\274\225\346\223\216-packages-opencode/08-Command\347\263\273\347\273\237.md"
new file mode 100644
index 000000000000..2cae1552fe36
--- /dev/null
+++ "b/all-in-one-book/04-\346\240\270\345\277\203\345\274\225\346\223\216-packages-opencode/08-Command\347\263\273\347\273\237.md"
@@ -0,0 +1,244 @@
+# 08 - Command 系统
+
+## 学习目标
+
+- 理解 OpenCode 的斜杠命令(Slash Command)系统架构
+- 掌握命令的四种来源:内置命令、配置命令、MCP Prompts、Skills
+- 了解命令注册、模板解析与执行流程
+- 学会自定义命令扩展系统
+
+## 概念解释
+
+在 OpenCode 中,**Command 系统**是用户与 AI 交互的快捷入口。当我们在输入框中键入 `/review` 或 `/init` 时,背后就是 Command 系统在工作。它将用户的简短指令扩展为完整的提示词(Prompt),再交给 AI 模型执行。
+
+核心概念包括:
+
+- **命令(Command)**:一个带名称、描述和模板的指令单元
+- **模板(Template)**:命令对应的提示词模板,支持 `$1`、`$ARGUMENTS` 等占位符(Placeholder)
+- **提示词(Hints)**:从模板中自动提取的占位符列表,用于 UI 提示
+- **命令来源(Source)**:命令可来自内置 `"command"`、MCP `"mcp"` 或技能 `"skill"`
+
+## 设计原理
+
+Command 系统采用**多源聚合、懒加载、名称去重**的设计:
+
+```mermaid
+graph TD
+ A[Command Service 初始化] --> B[内置命令 init/review]
+ A --> C[配置文件命令 config.command]
+ A --> D[MCP Prompts 远程命令]
+ A --> E[Skills 技能命令]
+ B --> F[命令注册表 Record]
+ C --> F
+ D --> F
+ E --> F
+ F --> G[get/list API]
+```
+
+**设计决策:**
+
+1. **InstanceState 缓存**:命令注册表只初始化一次,后续调用从缓存读取
+2. **优先级机制**:同名命令中,先注册者胜出(内置 > 配置 > MCP > Skill)
+3. **异步模板**:MCP Prompts 的模板是 `Promise`,实现按需加载
+4. **Effect 架构**:整个服务基于 Effect.js 构建,支持依赖注入与资源管理
+
+## 源码分析
+
+### 文件结构
+
+```
+packages/opencode/src/command/
+├── index.ts # 主模块(~186 行)
+└── template/
+ ├── initialize.txt # init 命令模板
+ └── review.txt # review 命令模板(~102 行)
+```
+
+### 命令信息 Schema
+
+```typescript
+// packages/opencode/src/command/index.ts (约第 33-51 行)
+const Info = z.object({
+ name: z.string(),
+ description: z.string().optional(),
+ agent: z.string().optional(),
+ model: z.string().optional(),
+ source: z.enum(["command", "mcp", "skill"]).optional(),
+ template: z.custom | string>(),
+ subtask: z.boolean().optional(),
+ hints: z.array(z.string()), // 自动提取的占位符
+})
+```
+
+每个命令由 `Info` 描述。注意 `template` 字段支持同步字符串或异步 Promise——这是为了适配 MCP 远程获取的场景。
+
+### Hints 提取函数
+
+```typescript
+// packages/opencode/src/command/index.ts (约第 53-61 行)
+function hints(template: string) {
+ const numbered = [...new Set(template.match(/\$\d+/g) || [])].sort()
+ const args = template.includes("$ARGUMENTS") ? ["$ARGUMENTS"] : []
+ return [...numbered, ...args]
+}
+```
+
+这个函数从模板中提取 `$1`、`$2`、`$ARGUMENTS` 等占位符,供 UI 层展示参数提示。
+
+### 内置命令注册
+
+```typescript
+// 约第 63-100 行
+const DEFAULT = { INIT: "init", REVIEW: "review" } as const
+
+// init 命令 —— 创建/更新 AGENTS.md
+commands[DEFAULT.INIT] = {
+ name: DEFAULT.INIT,
+ description: "create/update AGENTS.md",
+ source: "command",
+ template: initTemplate.replace("${path}", Instance.directory),
+ hints: hints(initTemplate),
+}
+
+// review 命令 —— 代码审查
+commands[DEFAULT.REVIEW] = {
+ name: DEFAULT.REVIEW,
+ description: "review changes",
+ source: "command",
+ template: reviewTemplate,
+ hints: hints(reviewTemplate),
+}
+```
+
+### 配置文件命令
+
+```typescript
+// 约第 102-115 行
+for (const [name, cmd] of Object.entries(cfg.command ?? {})) {
+ commands[name] = {
+ name,
+ agent: cmd.agent,
+ model: cmd.model,
+ description: cmd.description,
+ template: cmd.template,
+ subtask: cmd.subtask,
+ hints: hints(cmd.template),
+ }
+}
+```
+
+用户可以在配置文件中自定义命令,指定使用的 agent 和 model。
+
+### MCP Prompts 命令
+
+```typescript
+// 约第 117-140 行
+const prompts = yield* Effect.promise(() => Mcp.prompts())
+for (const [key, prompt] of Object.entries(prompts)) {
+ if (commands[prompt.name]) continue // 名称去重
+ commands[prompt.name] = {
+ name: prompt.name,
+ description: prompt.description,
+ source: "mcp",
+ // 异步模板 —— 按需从 MCP 服务器获取
+ template: Mcp.getPrompt(key, args).then(r => r.messages.map(...).join("\n")),
+ hints: (prompt.arguments ?? []).map((_, i) => `$${i + 1}`),
+ }
+}
+```
+
+### Skills 命令
+
+```typescript
+// 约第 142-153 行
+const skills = yield* Effect.promise(() => Skill.all())
+for (const skill of skills) {
+ if (commands[skill.name]) continue // 已注册则跳过
+ commands[skill.name] = {
+ name: skill.name,
+ description: skill.description,
+ source: "skill",
+ template: skill.content,
+ hints: hints(skill.content),
+ }
+}
+```
+
+## 执行流程
+
+```mermaid
+sequenceDiagram
+ participant User as 用户
+ participant UI as TUI 界面
+ participant Cmd as Command Service
+ participant Bus as Event Bus
+ participant AI as AI Provider
+
+ User->>UI: 输入 /review
+ UI->>Cmd: Command.get("review")
+ Cmd-->>UI: 返回 Info{template, hints}
+ UI->>UI: 替换模板占位符
+ UI->>Bus: 发布 Command.Event.Executed
+ Note over Bus: {name, sessionID, arguments, messageID}
+ Bus->>AI: 将展开后的模板作为用户消息发送
+ AI-->>User: 返回代码审查结果
+```
+
+**关键步骤:**
+
+1. 用户键入 `/review commit abc123`
+2. UI 调用 `Command.get("review")` 获取命令信息
+3. 模板中的 `$ARGUMENTS` 被替换为 `commit abc123`
+4. 通过 `Bus.publish(Command.Event.Executed, ...)` 触发执行
+5. AI 接收完整提示词并返回结果
+
+## 动手练习
+
+### 练习 1:查看所有可用命令
+
+在 OpenCode 中输入 `/` 即可看到命令列表。尝试理解每个命令的来源(source)。
+
+### 练习 2:自定义配置命令
+
+在配置文件中添加:
+
+```json
+{
+ "command": {
+ "explain": {
+ "description": "解释代码",
+ "template": "请详细解释以下代码的功能和设计思路:$ARGUMENTS"
+ }
+ }
+}
+```
+
+重启后输入 `/explain` 即可使用。
+
+### 练习 3:阅读 review 模板
+
+打开 `packages/opencode/src/command/template/review.txt`,理解它如何指导 AI 进行代码审查——包括确定审查范围(未提交、特定 commit、分支、PR)和审查关注点。
+
+## 常见问题
+
+**Q:自定义命令和 MCP 命令同名会怎样?**
+A:配置命令优先。注册顺序是:内置 → 配置 → MCP → Skill,同名命令先到先得。
+
+**Q:模板中 `$1` 和 `$ARGUMENTS` 有什么区别?**
+A:`$1`、`$2` 是按位置匹配的参数,`$ARGUMENTS` 则捕获所有剩余参数。
+
+**Q:为什么 MCP 命令的模板是 Promise?**
+A:MCP Prompts 内容存储在远程服务器上,使用 Promise 实现懒加载,避免初始化时的网络开销。
+
+**Q:命令注册表何时刷新?**
+A:当前实现中,命令表在 `InstanceState` 初始化时加载一次。MCP 工具变更会触发 `ToolsChanged` 事件,但命令表需重启刷新。
+
+## 小结
+
+OpenCode 的 Command 系统通过**多源聚合**将内置命令、用户配置、MCP Prompts 和 Skills 统一为斜杠命令接口。核心设计要点:
+
+- **Zod Schema** 定义命令元数据,`hints` 自动提取模板占位符
+- **InstanceState** 实现一次初始化、全程缓存
+- **名称去重**保证优先级:内置 > 配置 > MCP > Skill
+- **异步模板**支持远程 MCP Prompts 的按需加载
+- **Event Bus** 解耦命令执行与 UI 层
diff --git "a/all-in-one-book/04-\346\240\270\345\277\203\345\274\225\346\223\216-packages-opencode/09-MCP\351\233\206\346\210\220.md" "b/all-in-one-book/04-\346\240\270\345\277\203\345\274\225\346\223\216-packages-opencode/09-MCP\351\233\206\346\210\220.md"
new file mode 100644
index 000000000000..be538e581a36
--- /dev/null
+++ "b/all-in-one-book/04-\346\240\270\345\277\203\345\274\225\346\223\216-packages-opencode/09-MCP\351\233\206\346\210\220.md"
@@ -0,0 +1,325 @@
+# 09 - MCP 集成
+
+## 学习目标
+
+- 理解 MCP(Model Context Protocol)在 OpenCode 中的集成架构
+- 掌握 MCP 客户端的三种传输方式:StreamableHTTP、SSE、Stdio
+- 了解工具(Tool)、资源(Resource)、提示词(Prompt)的代理机制
+- 学会配置远程和本地 MCP 服务器
+
+## 概念解释
+
+**MCP(Model Context Protocol)** 是一种标准化协议,让 AI 模型能够调用外部工具和访问外部资源。在 OpenCode 中,MCP 集成模块扮演着**桥梁(Bridge)**的角色——它管理多个 MCP 服务器连接,将远程工具转换为 AI SDK 可用的工具格式。
+
+核心概念:
+
+- **MCP 客户端(Client)**:与 MCP 服务器建立连接的客户端实例
+- **传输层(Transport)**:通信方式,支持 HTTP 流、SSE(Server-Sent Events)和 Stdio
+- **工具代理(Tool Proxying)**:将 MCP 工具定义转换为 AI SDK 的 `Tool` 类型
+- **OAuth 认证(Authentication)**:远程服务器的身份验证流程
+- **状态机(Status)**:`connected` | `disabled` | `failed` | `needs_auth` | `needs_client_registration`
+
+## 设计原理
+
+```mermaid
+graph TD
+ A[Config 配置] --> B[MCP Service 初始化]
+ B --> C{服务器类型?}
+ C -->|远程 URL| D[StreamableHTTP Transport]
+ C -->|远程 URL 降级| E[SSE Transport]
+ C -->|本地命令| F[Stdio Transport]
+ D --> G[MCP Client]
+ E --> G
+ F --> G
+ G --> H[Tool 代理]
+ G --> I[Resource 代理]
+ G --> J[Prompt 代理]
+ H --> K[AI SDK Tools]
+ G --> L[状态追踪 Status]
+ G --> M[工具变更监听 Watch]
+```
+
+**设计决策:**
+
+1. **并行初始化**:所有 MCP 服务器以 `concurrency: "unbounded"` 并行连接
+2. **传输降级**:远程服务器先尝试 StreamableHTTP,失败则降级到 SSE
+3. **命名空间隔离**:工具名格式为 `{clientName}_{toolName}`,避免冲突
+4. **优雅清理**:通过 Effect Finalizer 管理子进程生命周期,防止僵尸进程
+
+## 源码分析
+
+### 文件结构
+
+```
+packages/opencode/src/mcp/
+├── index.ts # 核心实现(~936 行)
+├── auth.ts # OAuth 认证状态管理(~182 行)
+├── oauth-callback.ts # OAuth 回调服务器(~216 行)
+└── oauth-provider.ts # OAuth Provider 实现(~186 行)
+```
+
+### 资源定义 Schema
+
+```typescript
+// packages/opencode/src/mcp/index.ts (约第 39-48 行)
+const Resource = z.object({
+ name: z.string(),
+ uri: z.string(),
+ description: z.string().optional(),
+ mimeType: z.string().optional(),
+ client: z.string(),
+})
+```
+
+### 状态类型
+
+```typescript
+// 约第 74-117 行
+type Status =
+ | { status: "connected" }
+ | { status: "disabled" }
+ | { status: "failed"; error: string }
+ | { status: "needs_auth" }
+ | { status: "needs_client_registration" }
+```
+
+### MCP 工具转换
+
+这是整个模块最核心的函数——将 MCP 工具定义转换为 AI SDK 格式:
+
+```typescript
+// 约第 133-161 行
+function convertMcpTool(tool, client, timeout?) {
+ const schema = {
+ ...tool.inputSchema,
+ type: "object",
+ properties: tool.inputSchema.properties ?? {},
+ }
+ return {
+ parameters: schema,
+ execute: async (args) => {
+ const result = await client.callTool(
+ { name: tool.name, arguments: args },
+ CallToolResultSchema,
+ { resetTimeoutOnProgress: true }
+ )
+ return result
+ },
+ }
+}
+```
+
+### 远程服务器连接
+
+```typescript
+// 约第 205-310 行
+async function create(key, mcp) {
+ // 远程服务器:尝试两种传输方式
+ const transports = [
+ new StreamableHTTPClientTransport(new URL(mcp.url), {
+ authProvider,
+ requestInit: mcp.headers ? { headers: mcp.headers } : undefined,
+ }),
+ new SSEClientTransport(new URL(mcp.url), {
+ authProvider,
+ requestInit: mcp.headers ? { headers: mcp.headers } : undefined,
+ }),
+ ]
+
+ for (const transport of transports) {
+ try {
+ await withTimeout(client.connect(transport), timeout)
+ return { mcpClient: client, status: { status: "connected" } }
+ } catch (err) {
+ if (err instanceof UnauthorizedError) {
+ // OAuth 认证流程
+ return { status: { status: "needs_auth" } }
+ }
+ lastError = err
+ }
+ }
+}
+```
+
+### 本地 Stdio 服务器
+
+```typescript
+// 约第 314-355 行
+// 本地服务器:通过子进程通信
+const [cmd, ...args] = mcp.command
+const transport = new StdioClientTransport({
+ stderr: "pipe",
+ command: cmd,
+ args,
+ cwd: Instance.directory,
+ env: {
+ ...process.env,
+ ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}),
+ ...mcp.environment,
+ },
+})
+
+// 捕获 stderr 用于调试
+transport.stderr?.on("data", (chunk) => {
+ log.info(`mcp stderr: ${chunk.toString()}`, { key })
+})
+```
+
+### 工具代理与命名
+
+```typescript
+// 约第 616-650 行
+// 只代理已连接的客户端工具
+const connected = Object.entries(s.clients).filter(
+ ([name]) => s.status[name]?.status === "connected"
+)
+
+for (const tool of listed) {
+ // 命名格式:clientName_toolName(特殊字符替换为下划线)
+ const sanitized = clientName.replace(/[^a-zA-Z0-9_-]/g, "_")
+ const toolName = tool.name.replace(/[^a-zA-Z0-9_-]/g, "_")
+ result[sanitized + "_" + toolName] = convertMcpTool(tool, client, timeout)
+}
+```
+
+### 工具变更监听
+
+```typescript
+// 约第 461-475 行
+function watch(s, name, client, timeout?) {
+ client.setNotificationHandler(
+ ToolListChangedNotificationSchema,
+ async () => {
+ if (s.clients[name] !== client) return
+ const listed = await defs(name, client, timeout)
+ if (!listed) return
+ s.defs[name] = listed // 更新缓存
+ await Bus.publish(ToolsChanged, { server: name })
+ }
+ )
+}
+```
+
+### 进程清理(Finalizer)
+
+```typescript
+// 约第 514-535 行
+yield* Effect.addFinalizer(() =>
+ Effect.gen(function* () {
+ for (const client of Object.values(s.clients)) {
+ // 获取子进程 PID,杀死进程树
+ const pid = (client.transport as any)?.pid
+ if (pid) {
+ const children = execSync(`pgrep -P ${pid}`).toString().trim().split("\n")
+ children.forEach(c => process.kill(Number(c), "SIGTERM"))
+ }
+ await client.close()
+ }
+ })
+)
+```
+
+## 执行流程
+
+```mermaid
+sequenceDiagram
+ participant Config as 配置文件
+ participant MCP as MCP Service
+ participant Transport as Transport 层
+ participant Server as MCP Server
+ participant AI as AI Provider
+
+ Config->>MCP: 加载 mcp 配置
+ MCP->>MCP: 并行初始化所有服务器
+ MCP->>Transport: 创建传输(HTTP/SSE/Stdio)
+ Transport->>Server: 建立连接
+ Server-->>MCP: 连接成功,获取工具列表
+ MCP->>MCP: 缓存工具定义 (defs)
+
+ Note over AI: AI 需要调用工具时
+ AI->>MCP: tools() 获取所有可用工具
+ MCP-->>AI: 返回 {clientName_toolName: Tool}
+ AI->>MCP: 调用 tool.execute(args)
+ MCP->>Server: client.callTool(name, args)
+ Server-->>AI: 返回执行结果
+```
+
+### OAuth 认证流程
+
+```mermaid
+sequenceDiagram
+ participant MCP as MCP Service
+ participant Server as 远程服务器
+ participant Auth as OAuth Provider
+ participant Browser as 浏览器
+
+ MCP->>Server: 尝试连接
+ Server-->>MCP: 401 UnauthorizedError
+ MCP->>Auth: 启动 OAuth 流程
+ Auth->>Browser: 打开授权 URL
+ Browser->>Auth: 回调 OAuth Code
+ Auth->>Server: 交换 Access Token
+ Auth->>MCP: 保存 token 到 mcp-auth.json
+ MCP->>Server: 重新连接(携带 token)
+```
+
+## 动手练习
+
+### 练习 1:配置本地 MCP 服务器
+
+```json
+{
+ "mcp": {
+ "my-tools": {
+ "command": ["npx", "-y", "my-mcp-server"],
+ "environment": { "API_KEY": "your-key" }
+ }
+ }
+}
+```
+
+### 练习 2:配置远程 MCP 服务器
+
+```json
+{
+ "mcp": {
+ "remote-server": {
+ "url": "https://mcp.example.com/api",
+ "headers": { "Authorization": "Bearer token" },
+ "timeout": 60000
+ }
+ }
+}
+```
+
+### 练习 3:观察工具命名
+
+启动 OpenCode 并查看日志,观察 MCP 工具如何被命名为 `serverName_toolName` 格式。
+
+## 常见问题
+
+**Q:StreamableHTTP 和 SSE 有什么区别?**
+A:StreamableHTTP 是更新的双向流协议,SSE 是单向事件流的降级方案。系统会自动尝试前者,失败后回退到后者。
+
+**Q:本地命令中 `BUN_BE_BUN: "1"` 是什么?**
+A:当 MCP 命令是 `opencode` 自身时,这个环境变量确保 Bun 运行时正确初始化。
+
+**Q:MCP 认证信息存储在哪里?**
+A:存储在 `{dataDir}/mcp-auth.json`,文件权限为 `0o600`(仅当前用户可读写)。
+
+**Q:工具列表变更后会自动更新吗?**
+A:是的。系统通过 `ToolListChangedNotification` 监听服务器推送的工具变更事件,自动更新缓存。
+
+**Q:默认超时是多少?**
+A:默认 30 秒(`DEFAULT_TIMEOUT`),可通过 `mcp.timeout` 或全局 `experimental.mcp_timeout` 配置。
+
+## 小结
+
+MCP 集成模块是 OpenCode 连接外部工具生态的核心桥梁:
+
+- **三种传输层**:StreamableHTTP → SSE 降级(远程),Stdio(本地子进程)
+- **并行初始化**:所有服务器 `concurrency: "unbounded"` 并发连接
+- **工具代理**:MCP 工具定义自动转换为 AI SDK Tool,命名空间隔离
+- **OAuth 支持**:完整的 OAuth 认证流程,token 安全存储
+- **生命周期管理**:Effect Finalizer 确保子进程正确清理,防止僵尸进程
+- **实时监听**:工具变更通知自动刷新缓存,通过 Bus 事件广播
diff --git a/all-in-one-book/README.md b/all-in-one-book/README.md
new file mode 100644
index 000000000000..dcb0455f6851
--- /dev/null
+++ b/all-in-one-book/README.md
@@ -0,0 +1,127 @@
+# 📖 OpenCode 全方位技术书籍
+
+> **从零到精通:深入理解开源 Coding Agent 的一切**
+
+---
+
+## 🎯 本书简介
+
+这是一本关于 [OpenCode](https://opencode.ai) 的全面中文技术书籍。OpenCode 是一个开源的 AI Coding Agent,基于 TypeScript/Bun 构建,支持多种 LLM 提供商,通过 MCP 协议扩展工具能力,提供 TUI、Web、桌面多端体验。
+
+本书基于 `propress/opencode`(fork 自 `anomalyco/opencode`)`dev` 分支的源码编写,带你从概念理解到源码精读,从快速上手到深度扩展。
+
+---
+
+## 🧠 阅读方法:地图 → 路线 → 细节
+
+本书采用三级认知结构,建议按以下方式阅读:
+
+| 级别 | 目标 | 时间 | 章节 |
+|------|------|------|------|
+| 🗺️ **第一级:全景地图** | 建立完整心智模型 | 30 分钟 | 01-全景视野 |
+| 🛤️ **第二级:路线导航** | 跑通每条关键路径 | 2-3 小时 | 02-快速上手、03-关键路径详解 |
+| 🔬 **第三级:模块深入** | 逐个零件拆解 | 按需 | 04~10 章 |
+
+> 💡 **强烈建议**:无论你的目标是什么,都先读完 **01-全景视野**。它是整本书的"导航仪"。
+
+---
+
+## 📚 目录
+
+### 🗺️ 第一级:全景地图
+
+#### [00-前言](./00-前言/)
+- [01-本书简介与阅读指南](./00-前言/01-本书简介与阅读指南.md)
+- [02-OpenCode是什么](./00-前言/02-OpenCode是什么.md)
+- [03-为什么学习OpenCode](./00-前言/03-为什么学习OpenCode.md)
+
+#### [01-全景视野](./01-全景视野/) ⭐ 全书最核心章节
+- [01-一图看懂OpenCode](./01-全景视野/01-一图看懂OpenCode.md) — 总架构图 + 每个组件一句话
+- [02-核心概念关系图谱](./01-全景视野/02-核心概念关系图谱.md) — 所有核心概念及其关系
+- [03-Monorepo全景-19个包的关系](./01-全景视野/03-Monorepo全景-19个包的关系.md) — 包间依赖拓扑图
+- [04-一次对话的完整旅程](./01-全景视野/04-一次对话的完整旅程.md) ⭐ — 全链路追踪
+- [05-一次Tool调用的完整旅程](./01-全景视野/05-一次Tool调用的完整旅程.md) ⭐ — Tool 执行全链路
+- [06-一次文件编辑的完整旅程](./01-全景视野/06-一次文件编辑的完整旅程.md) ⭐ — 从指令到文件落盘
+- [07-数据流全景图](./01-全景视野/07-数据流全景图.md) — 数据在各模块间的流动
+- [08-状态生命周期全景图](./01-全景视野/08-状态生命周期全景图.md) — 各实体的状态机
+- [09-扩展点全景图](./01-全景视野/09-扩展点全景图.md) — 所有可扩展的点
+- [10-技术栈全景与选型理由](./01-全景视野/10-技术栈全景与选型理由.md) — 技术选型分析
+- [11-速查:我想做X应该看哪章](./01-全景视野/11-速查:我想做X应该看哪章.md) — 快速导航
+
+### 🛤️ 第二级:路线导航
+
+#### [02-快速上手](./02-快速上手/)
+- 01-安装与环境准备
+- 02-五分钟体验OpenCode
+- 03-核心概念详解
+- 04-常见使用场景手册
+
+#### [03-关键路径详解](./03-关键路径详解/)
+- 01-启动流程详解
+- 02-对话循环详解
+- 03-Tool执行详解
+- 04-Session管理详解
+- 05-Provider调用详解
+
+### 🔬 第三级:模块深入
+
+#### [04-核心引擎-packages-opencode](./04-核心引擎-packages-opencode/)
+- 01~21:Agent系统、Session管理、Provider、Tool系统、Skill、Command、MCP、LSP、Git、Shell、文件系统、存储、HTTP Server、权限、快照、事件总线、插件、配置、Effect-TS、ACP
+
+#### [05-用户界面](./05-用户界面/)
+- TUI终端界面、Web应用、桌面客户端、Console管理平台、UI组件库
+
+#### [06-扩展与集成](./06-扩展与集成/)
+- 自定义Agent/Command/Tool、Plugin SDK、MCP服务器、Provider适配、主题、VSCode扩展、Slack集成
+
+#### [07-基础设施与部署](./07-基础设施与部署/)
+- SST基础设施、容器化、Nix打包、CI/CD、企业版部署、安装脚本
+
+#### [08-开发者指南](./08-开发者指南/)
+- 本地开发、调试、测试、代码风格、发布、贡献指南
+
+#### [09-周边知识与生态](./09-周边知识与生态/)
+- Bun、Effect-TS、AI SDK、MCP协议、LSP、SolidJS、Tauri、Drizzle、Turborepo、SST
+
+#### [10-实战案例](./10-实战案例/)
+- 从零创建Agent、开发Tool、接入Provider、构建MCP服务器、开发插件、贡献代码
+
+### 📎 附录
+
+#### [11-附录](./11-附录/)
+- 术语表、配置参考、API参考、CLI命令、内置Tool/Skill列表、Provider列表、FAQ、安全策略
+
+---
+
+## 📊 质量报告
+
+- [QUALITY_REPORT.md](./QUALITY_REPORT.md) — 各章质量评分与改进计划
+
+---
+
+## 🔧 技术栈速览
+
+| 层次 | 技术 | 版本 |
+|------|------|------|
+| 运行时 | Bun | 1.3.11 |
+| 语言 | TypeScript | 5.8.2 |
+| 副作用管理 | Effect-TS | 4.0.0-beta.35 |
+| AI 集成 | Vercel AI SDK | 5.x |
+| 协议 | MCP (Model Context Protocol) | 1.27.1 |
+| Web 框架 | Hono | 4.10.7 |
+| 前端 | SolidJS | 1.9.x |
+| ORM | Drizzle | 1.0.0-beta |
+| 桌面 | Tauri 2 + Electron 40 | — |
+| 构建 | Turborepo | — |
+| 基础设施 | SST | — |
+
+---
+
+## 📝 关于本书
+
+- **语言**: 中文(简体)
+- **基于仓库**: `propress/opencode` (fork of `anomalyco/opencode`)
+- **分支**: `dev`
+- **许可证**: 与仓库一致 (MIT)
+
+> ⚠️ 本书内容基于特定时间点的源码编写,如源码有更新,部分内容可能需要对照最新代码阅读。