<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>DoneHub</title>
  
  
  <link href="https://donehub.github.io/atom.xml" rel="self"/>
  
  <link href="https://donehub.github.io/"/>
  <updated>2026-05-16T04:37:38.232Z</updated>
  <id>https://donehub.github.io/</id>
  
  <author>
    <name>Zou Rongsheng</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>TrendRadar：告别无效刷屏，只看真正关心的新闻</title>
    <link href="https://donehub.github.io/2026/05/16/trendradar-hot-news-aggregator-guide/"/>
    <id>https://donehub.github.io/2026/05/16/trendradar-hot-news-aggregator-guide/</id>
    <published>2026-05-15T16:00:00.000Z</published>
    <updated>2026-05-16T04:37:38.232Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h2><p>每天打开手机，十几个 APP 轮番刷一遍，微博热搜、知乎热榜、抖音热点、今日头条……刷完一圈下来，两个小时过去了，真正有用的信息可能就三五条。剩下的是什么？震惊体标题党、营销软文、明星八卦、各种算法硬塞给你的”你可能感兴趣”。</p><p>更气人的是，明明只想看看科技圈今天发生了什么，却被”某明星离婚”霸占了热搜第一。平台算法绑架了我们的注意力，想看的内容找不到，不想看的铺天盖地。</p><p>有没有一种工具，能帮你从”被动接收”变成”主动获取”？<strong>TrendRadar</strong> 就是这么一个开源项目——聚合全网热点，按你的关键词筛选，定时推送到你的手机。更重要的是，它还能让 AI 帮你分析这些热点背后的趋势和情绪。</p><h2 id="二、TrendRadar-是什么"><a href="#二、TrendRadar-是什么" class="headerlink" title="二、TrendRadar 是什么"></a>二、TrendRadar 是什么</h2><p>一句话概括：<strong>TrendRadar 是一个开源的热点新闻聚合分析工具</strong>。</p><p>它的核心思路很简单——把全网 50+ 个平台的热榜抓过来，按你设定的关键词过滤，把真正关心的内容推给你。推送渠道也很丰富：飞书、钉钉、企业微信、Telegram、邮件、Bark（iOS）、Slack，甚至自定义 Webhook。</p><p>更厉害的是，它内置了 <strong>AI 分析功能</strong>。不仅是聚合热点，还能让 AI 帮你：</p><ul><li>分析热点趋势走向</li><li>判断舆论情绪（正面/负面/争议）</li><li>跨平台关联分析</li><li>生成洞察报告</li></ul><p>这就像雇了一个私人新闻助理，每天帮你从海量信息中提炼出真正有价值的干货。</p><h2 id="三、数据是怎么来的"><a href="#三、数据是怎么来的" class="headerlink" title="三、数据是怎么来的"></a>三、数据是怎么来的</h2><p>TrendRadar 的数据来源是另一个开源项目 <strong>NewsNow</strong>。这个项目聚合了全网 50+ 个平台的热榜数据，包括：</p><div class="table-container"><table><thead><tr><th>国内综合</th><th>科技平台</th><th>金融平台</th><th>国际媒体</th></tr></thead><tbody><tr><td>知乎、微博</td><td>IT之家、36氪</td><td>华尔街见闻</td><td>Hacker News</td></tr><tr><td>百度热搜</td><td>稀土掘金</td><td>财联社</td><td>GitHub Trending</td></tr><tr><td>抖音、今日头条</td><td>V2EX</td><td>雪球</td><td>Product Hunt</td></tr><tr><td>澎湃新闻、凤凰网</td><td>酷安</td><td>金十数据</td><td>联合早报</td></tr><tr><td>虎扑、贴吧</td><td>少数派</td><td>格隆汇</td><td>卫星通讯社</td></tr></tbody></table></div><p>NewsNow 通过调用各平台的官方 API 或爬取页面来获取热榜数据，然后统一输出成标准格式。TrendRadar 直接调用 NewsNow 的公开 API：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https:&#x2F;&#x2F;newsnow.busiyi.world&#x2F;api&#x2F;s?id&#x3D;zhihu&amp;latest</span><br></pre></td></tr></table></figure><p>返回的数据格式是这样的：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"status"</span>: <span class="string">"success"</span>,</span><br><span class="line">  <span class="attr">"items"</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">"title"</span>: <span class="string">"如何评价DeepSeek新模型?"</span>,</span><br><span class="line">      <span class="attr">"url"</span>: <span class="string">"https://zhuanlan.zhihu.com/p/xxx"</span>,</span><br><span class="line">      <span class="attr">"extra"</span>: &#123;</span><br><span class="line">        <span class="attr">"info"</span>: <span class="string">"1234万热度"</span>,</span><br><span class="line">        <span class="attr">"hover"</span>: <span class="string">"摘要描述..."</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以 TrendRadar 不需要自己去啃各平台的反爬机制，数据源维护这个苦活儿由 NewsNow 项目负责。万一某个平台接口变了，NewsNow 更一下就行，TrendRadar 用户完全不用操心。</p><h2 id="四、核心功能一览"><a href="#四、核心功能一览" class="headerlink" title="四、核心功能一览"></a>四、核心功能一览</h2><h3 id="热榜聚合"><a href="#热榜聚合" class="headerlink" title="热榜聚合"></a>热榜聚合</h3><p>默认支持 11 个主流平台：知乎、微博、百度热搜、抖音、今日头条、B站热搜、华尔街见闻、财联社、澎湃新闻、凤凰网、贴吧。想加更多平台？直接在配置文件里加就行。</p><h3 id="关键词筛选"><a href="#关键词筛选" class="headerlink" title="关键词筛选"></a>关键词筛选</h3><p>这是核心功能。你在 <code>frequency_words.txt</code> 里写上关心的关键词，系统就只推送包含这些词的新闻。语法很灵活：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"># 最简单的：直接写关键词</span><br><span class="line">华为</span><br><span class="line"></span><br><span class="line"># 多个关键词归为一组（空行分隔）</span><br><span class="line">华为</span><br><span class="line">鸿蒙</span><br><span class="line">任正非</span><br><span class="line"></span><br><span class="line"># 给词组起个名字</span><br><span class="line">[科技巨头]</span><br><span class="line">华为</span><br><span class="line">腾讯</span><br><span class="line">字节</span><br><span class="line"></span><br><span class="line"># 正则匹配（精确匹配英文单词，避免误匹配）</span><br><span class="line">&#x2F;\bAI\b&#x2F; &#x3D;&gt; AI相关</span><br><span class="line">人工智能</span><br><span class="line"></span><br><span class="line"># 排除不想看的</span><br><span class="line">[苹果公司]</span><br><span class="line">苹果</span><br><span class="line">!水果        # 排除&quot;水果&quot;相关的</span><br><span class="line"></span><br><span class="line"># 限制显示条数</span><br><span class="line">特斯拉</span><br><span class="line">@10          # 最多显示10条</span><br><span class="line"></span><br><span class="line"># 必须同时包含多个词</span><br><span class="line">+发布会</span><br><span class="line">+新品        # 必须同时出现&quot;发布会&quot;和&quot;新品&quot;</span><br></pre></td></tr></table></figure><h3 id="AI-智能筛选（新功能）"><a href="#AI-智能筛选（新功能）" class="headerlink" title="AI 智能筛选（新功能）"></a>AI 智能筛选（新功能）</h3><p>如果你不想自己写关键词，可以用 <strong>自然语言描述</strong> 你关注的方向。在 <code>ai_interests.txt</code> 里写：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">下面是我要关注的内容：</span><br><span class="line"></span><br><span class="line">1. 中国科技与互联网公司：重点关注 DeepSeek、华为、腾讯...</span><br><span class="line">2. 大模型与 AI 产品：关注 OpenAI、Claude、ChatGPT...</span><br><span class="line">3. AI 基础设施与云算力：关注英伟达、AMD...</span><br><span class="line">4. 芯片与半导体制造：关注芯片、光刻机...</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"># 标题质量要求</span><br><span class="line">- 不要标题党&#x2F;震惊体</span><br><span class="line">- 不要营销软文</span><br></pre></td></tr></table></figure><p>AI 会自动理解你的兴趣，给每条新闻打分，只推送高相关度的内容。这个功能需要配置 AI API（支持 DeepSeek、OpenAI、Gemini 等）。</p><h3 id="三种推送模式"><a href="#三种推送模式" class="headerlink" title="三种推送模式"></a>三种推送模式</h3><div class="table-container"><table><thead><tr><th>模式</th><th>说明</th><th>适用人群</th></tr></thead><tbody><tr><td><strong>daily（当日汇总）</strong></td><td>每天定时推送当天所有匹配新闻</td><td>企业管理者、普通用户</td></tr><tr><td><strong>current（当前榜单）</strong></td><td>每次推送当前榜单匹配新闻</td><td>自媒体人、内容创作者</td></tr><tr><td><strong>incremental（增量监控）</strong></td><td>只推送新出现的内容，零重复</td><td>投资者、交易员</td></tr></tbody></table></div><p>举个例子：你监控”特斯拉”，每小时执行一次。如果选择 <code>incremental</code> 模式，只有第一次出现的新闻才会推送给你，后续重复出现的就不打扰了。适合高频监控场景。</p><h3 id="调度系统（时间线）"><a href="#调度系统（时间线）" class="headerlink" title="调度系统（时间线）"></a>调度系统（时间线）</h3><p>你可以精细控制”什么时间做什么事”。比如：</p><ul><li>工作日：早上9点速览、中午看热点、晚上7点汇总</li><li>周末：睡到自然醒，10点开始推送，有新增就推</li></ul><p>预设了 5 种模板：<code>always_on</code>（全天候）、<code>morning_evening</code>（早晚汇总）、<code>office_hours</code>（办公时间）、<code>night_owl</code>（夜猫子）、<code>custom</code>（完全自定义）。</p><h3 id="AI-分析推送"><a href="#AI-分析推送" class="headerlink" title="AI 分析推送"></a>AI 分析推送</h3><p>开启后，每次推送都会附带一份 AI 生成的洞察报告，包含：</p><ul><li>核心热点态势</li><li>舆论风向争议</li><li>异动与弱信号</li><li>研判策略建议</li></ul><p>AI 还能分析每条新闻的排名变化轨迹、热度持续时间、跨平台表现。比如某条新闻在微博排第3，知乎排第5，抖音排第8——AI 能告诉你这个话题的”全网热度分布”。</p><h3 id="AI-多语言翻译"><a href="#AI-多语言翻译" class="headerlink" title="AI 多语言翻译"></a>AI 多语言翻译</h3><p>如果你订阅了海外 RSS（如 Hacker News），AI 可以帮你把英文标题翻译成中文。反过来，如果你想用英文读国内热点，也可以翻译成英文。</p><h3 id="MCP-智能分析（进阶功能）"><a href="#MCP-智能分析（进阶功能）" class="headerlink" title="MCP 智能分析（进阶功能）"></a>MCP 智能分析（进阶功能）</h3><p>这是给深度用户准备的。TrendRadar 实现了 <strong>MCP (Model Context Protocol)</strong> 协议，可以接入 Claude Desktop、Cherry Studio、Cursor 等 AI 客户端。</p><p>你可以用自然语言跟新闻数据”对话”：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&quot;分析过去一周 DeepSeek 的热度变化&quot;</span><br><span class="line">&quot;对比知乎和微博今天的热点差异&quot;</span><br><span class="line">&quot;生成一份今天的科技热点摘要，推送到飞书&quot;</span><br><span class="line">&quot;搜索特斯拉相关新闻，分析情感倾向&quot;</span><br></pre></td></tr></table></figure><p>AI 会自动调用 TrendRadar 的 21 个分析工具，帮你做深度数据挖掘。</p><h2 id="五、部署方式"><a href="#五、部署方式" class="headerlink" title="五、部署方式"></a>五、部署方式</h2><h3 id="GitHub-Actions（零服务器）"><a href="#GitHub-Actions（零服务器）" class="headerlink" title="GitHub Actions（零服务器）"></a>GitHub Actions（零服务器）</h3><p>适合没有服务器的用户。流程是：</p><ol><li>Fork TrendRadar 仓库到自己的 GitHub</li><li>配置 GitHub Secrets（填推送渠道的 webhook URL）</li><li>GitHub Actions 定时运行，自动抓取并推送</li></ol><p>缺点是每次运行完环境就销毁，数据没法本地存。需要配置云存储（如 Cloudflare R2）来持久化数据。</p><h3 id="Docker（推荐）"><a href="#Docker（推荐）" class="headerlink" title="Docker（推荐）"></a>Docker（推荐）</h3><p>适合有服务器、NAS 或长期运行电脑的用户。数据本地存储，更稳定。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 克隆项目</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/sansan0/TrendRadar.git</span><br><span class="line"><span class="built_in">cd</span> TrendRadar</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置</span></span><br><span class="line">cp config/config.yaml.example config/config.yaml</span><br><span class="line"><span class="comment"># 编辑 config.yaml 和 frequency_words.txt</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line">docker compose up -d</span><br></pre></td></tr></table></figure><p>Docker 部署还有个好处：可以同时跑两个容器——一个做新闻推送，一个做 MCP AI 分析服务。</p><h3 id="本地运行"><a href="#本地运行" class="headerlink" title="本地运行"></a>本地运行</h3><p>Windows/Mac/Linux 直接跑：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Windows</span></span><br><span class="line">setup-windows.bat</span><br><span class="line"></span><br><span class="line"><span class="comment"># Mac/Linux</span></span><br><span class="line">./setup-mac.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行</span></span><br><span class="line">python main.py</span><br></pre></td></tr></table></figure><h2 id="六、配置要点"><a href="#六、配置要点" class="headerlink" title="六、配置要点"></a>六、配置要点</h2><h3 id="config-yaml-主配置"><a href="#config-yaml-主配置" class="headerlink" title="config.yaml 主配置"></a>config.yaml 主配置</h3><p>这是核心配置文件，结构如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">app:</span></span><br><span class="line">  <span class="attr">timezone:</span> <span class="string">"Asia/Shanghai"</span>        <span class="comment"># 时区</span></span><br><span class="line"></span><br><span class="line"><span class="attr">schedule:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">preset:</span> <span class="string">"morning_evening"</span>        <span class="comment"># 调度模板</span></span><br><span class="line"></span><br><span class="line"><span class="attr">platforms:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">sources:</span>                         <span class="comment"># 监控平台列表</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">id:</span> <span class="string">"zhihu"</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">"知乎"</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">id:</span> <span class="string">"weibo"</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">"微博"</span></span><br><span class="line"></span><br><span class="line"><span class="attr">report:</span></span><br><span class="line">  <span class="attr">mode:</span> <span class="string">"incremental"</span>              <span class="comment"># 推送模式</span></span><br><span class="line">  <span class="attr">display_mode:</span> <span class="string">"keyword"</span>          <span class="comment"># 显示方式</span></span><br><span class="line"></span><br><span class="line"><span class="attr">filter:</span></span><br><span class="line">  <span class="attr">method:</span> <span class="string">"keyword"</span>                <span class="comment"># keyword | ai</span></span><br><span class="line"></span><br><span class="line"><span class="attr">notification:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">channels:</span></span><br><span class="line">    <span class="attr">feishu:</span></span><br><span class="line">      <span class="attr">webhook_url:</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">telegram:</span></span><br><span class="line">      <span class="attr">bot_token:</span> <span class="string">""</span></span><br><span class="line">      <span class="attr">chat_id:</span> <span class="string">""</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ai:</span></span><br><span class="line">  <span class="attr">model:</span> <span class="string">"deepseek/deepseek-chat"</span>  <span class="comment"># AI 模型</span></span><br><span class="line">  <span class="attr">api_key:</span> <span class="string">""</span>                      <span class="comment"># API Key</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ai_analysis:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span>                    <span class="comment"># 开启 AI 分析</span></span><br><span class="line">  <span class="attr">max_news_for_analysis:</span> <span class="number">50</span>        <span class="comment"># 分析数量上限</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ai_translation:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">language:</span> <span class="string">"中文"</span></span><br></pre></td></tr></table></figure><h3 id="frequency-words-txt-关键词配置"><a href="#frequency-words-txt-关键词配置" class="headerlink" title="frequency_words.txt 关键词配置"></a>frequency_words.txt 关键词配置</h3><p>前面已经介绍过语法，这里补充几个实用技巧：</p><p><strong>技巧1：从宽到严，逐步调整</strong></p><p>刚开始可以写宽泛的关键词，观察几天后再加过滤词：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"># 第一版：先测试</span><br><span class="line">AI</span><br><span class="line">ChatGPT</span><br><span class="line"></span><br><span class="line"># 第二版：发现太多广告，加过滤</span><br><span class="line">AI</span><br><span class="line">ChatGPT</span><br><span class="line">!培训</span><br><span class="line">!课程</span><br><span class="line">!广告</span><br><span class="line"></span><br><span class="line"># 第三版：只想看技术相关，加必须词</span><br><span class="line">AI</span><br><span class="line">ChatGPT</span><br><span class="line">+技术</span><br></pre></td></tr></table></figure><p><strong>技巧2：正则表达式精确匹配英文</strong></p><p>英文容易误匹配，比如 <code>ai</code> 会匹配到 <code>training</code> 里的 <code>ai</code>。用正则解决：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 精确匹配独立单词</span><br><span class="line">&#x2F;\bAI\b&#x2F;i &#x3D;&gt; AI相关</span><br><span class="line"></span><br><span class="line"># 匹配开头或结尾</span><br><span class="line">&#x2F;^breaking&#x2F;     # 只匹配开头是 breaking 的</span><br><span class="line">&#x2F;发布$&#x2F;         # 只匹配结尾是&quot;发布&quot;的</span><br></pre></td></tr></table></figure><p>不会写正则？直接问 ChatGPT：”帮我写一个正则表达式，精确匹配英文单词 AI，不匹配 training 里的 ai，格式是 /正则/ =&gt; 别名”</p><p><strong>技巧3：全局过滤不想看的</strong></p><p>有些内容不管什么关键词都不想看，用 <code>[GLOBAL_FILTER]</code>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[GLOBAL_FILTER]</span><br><span class="line">震惊</span><br><span class="line">刚刚</span><br><span class="line">竟然</span><br><span class="line">广告</span><br><span class="line">推广</span><br><span class="line"></span><br><span class="line">[WORD_GROUPS]</span><br><span class="line">你的关键词配置...</span><br></pre></td></tr></table></figure><h3 id="推送渠道配置"><a href="#推送渠道配置" class="headerlink" title="推送渠道配置"></a>推送渠道配置</h3><p><strong>企业微信（最简单）</strong>：</p><ol><li>打开企业微信，进入目标群聊</li><li>点击右上角”…”，选择”群机器人”</li><li>添加机器人，复制 Webhook URL</li><li>填入配置或 GitHub Secrets</li></ol><p><strong>飞书</strong>：</p><ol><li>访问 <a href="https://botbuilder.feishu.cn/home/my-command" target="_blank" rel="noopener">https://botbuilder.feishu.cn/home/my-command</a></li><li>新建机器人指令</li><li>选择”Webhook 触发”，复制 URL</li><li>配置参数模板：<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"message_type"</span>: <span class="string">"text"</span>,</span><br><span class="line">  <span class="attr">"content"</span>: &#123; <span class="attr">"text"</span>: <span class="string">"&#123;&#123;内容&#125;&#125;"</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p><strong>Telegram</strong>：</p><p>需要两个配置：<code>bot_token</code> 和 <code>chat_id</code>。</p><ol><li>在 Telegram 搜索 @BotFather，发送 <code>/newbot</code> 创建机器人</li><li>获取 Bot Token</li><li>向你的机器人发一条消息</li><li>访问 <code>https://api.telegram.org/bot&lt;Token&gt;/getUpdates</code></li><li>从返回 JSON 找到 <code>chat.id</code></li></ol><p><strong>邮件</strong>：</p><p>支持 Gmail、QQ邮箱、163、Outlook 等。QQ邮箱需要用授权码（不是密码），在邮箱设置里开启 SMTP 服务后生成。</p><h2 id="七、实际使用体验"><a href="#七、实际使用体验" class="headerlink" title="七、实际使用体验"></a>七、实际使用体验</h2><p>我部署了一套配置，关键词设为：AI、DeepSeek、华为、特斯拉、芯片、大模型。推送模式选 <code>incremental</code>，调度选 <code>morning_evening</code>。</p><p>效果是这样的：</p><p><strong>早上 9 点</strong>：收到推送，包含昨晚到今早新出现的 15 条相关热点。AI 分析报告附在最后，告诉我”AI 领域今天舆论偏正面，DeepSeek 新模型发布引发热议，华为鸿蒙讨论度上升”。</p><p><strong>晚上 8 点</strong>：收到当日汇总，包含全天所有匹配新闻（去重后约 30 条）。AI 给了一份更完整的趋势分析，包括”哪些话题持续在榜”、”哪些是新爆发点”。</p><p><strong>好处</strong>：</p><ol><li><strong>不用刷 APP 了</strong>。之前每天刷微博知乎抖音至少两小时，现在 5 分钟看完推送就行。</li><li><strong>信息密度高</strong>。一条推送包含 11 个平台的热点，跨平台对比一目了然。</li><li><strong>AI 分析有价值</strong>。不是简单的汇总，而是告诉你趋势、情绪、关联。比如”特斯拉降价”这个话题，AI 能分析出”微博讨论偏负面（吐槽割韭菜），知乎讨论偏中性（分析影响），抖音讨论偏正面（喊降价真香）”。</li></ol><p><strong>注意点</strong>：</p><ol><li><strong>关键词不要太多</strong>。我刚开始写了 30 多个关键词，结果每次推送 100 多条，信息过载。后来精简到 6 个核心关键词，效果好多了。</li><li><strong>AI 分析有成本</strong>。默认模型是 DeepSeek，很便宜。按官方估算，每小时推送一次，每天约 0.1 元。如果想省钱，可以把 <code>max_news_for_analysis</code> 从 150 降到 50。</li><li><strong>GitHub Actions 有延迟</strong>。定时任务触发时间不稳定，可能有 ±15 分钟偏差。如果需要精准推送，建议用 Docker 部署到自己的服务器。</li></ol><h2 id="八、MCP-功能进阶用法"><a href="#八、MCP-功能进阶用法" class="headerlink" title="八、MCP 功能进阶用法"></a>八、MCP 功能进阶用法</h2><p>如果你想深度挖掘新闻数据，MCP 功能很有价值。</p><h3 id="配置-MCP-客户端"><a href="#配置-MCP-客户端" class="headerlink" title="配置 MCP 客户端"></a>配置 MCP 客户端</h3><p>以 Cherry Studio 为例（推荐，有 GUI）：</p><ol><li><p>运行 TrendRadar 的 MCP 服务：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Windows</span></span><br><span class="line">start-http.bat</span><br><span class="line"></span><br><span class="line"><span class="comment"># Mac/Linux</span></span><br><span class="line">./start-http.sh</span><br></pre></td></tr></table></figure></li><li><p>在 Cherry Studio 设置里添加 MCP 服务器：</p><ul><li>类型：<code>streamableHttp</code></li><li>URL：<code>http://127.0.0.1:3333/mcp</code></li></ul></li><li><p>开始对话。</p></li></ol><h3 id="MCP-可以做什么"><a href="#MCP-可以做什么" class="headerlink" title="MCP 可以做什么"></a>MCP 可以做什么</h3><p><strong>趋势分析</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;分析最近 7 天 DeepSeek 的热度变化&quot;</span><br></pre></td></tr></table></figure><p>AI 会调用 <code>analyze_topic_trend</code> 工具，返回：</p><ul><li>首次出现时间、持续时间</li><li>排名变化曲线（第3→第1→第5）</li><li>热度峰值、爆火判断</li><li>趋势预测</li></ul><p><strong>平台对比</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;对比知乎和微博今天关于 AI 的讨论差异&quot;</span><br></pre></td></tr></table></figure><p>AI 会对比两个平台的热点分布、情绪倾向、讨论角度差异。</p><p><strong>情感分析</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;分析特斯拉最近新闻的情感倾向&quot;</span><br></pre></td></tr></table></figure><p>返回正面/负面/中性分布，以及典型情感关键词。</p><p><strong>生成报告并推送</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;写一份今天的科技热点摘要，推送到飞书&quot;</span><br></pre></td></tr></table></figure><p>AI 会调用 <code>generate_summary_report</code> 生成报告，然后调用 <code>send_notification</code> 推送。自动处理格式转换（Markdown → 飞书格式）。</p><h3 id="MCP-工具列表"><a href="#MCP-工具列表" class="headerlink" title="MCP 工具列表"></a>MCP 工具列表</h3><div class="table-container"><table><thead><tr><th>分类</th><th>工具</th><th>功能</th></tr></thead><tbody><tr><td>基础</td><td><code>get_latest_news</code></td><td>获取最新新闻</td></tr><tr><td></td><td><code>get_news_by_date</code></td><td>按日期查询</td></tr><tr><td></td><td><code>get_trending_topics</code></td><td>热点统计</td></tr><tr><td>RSS</td><td><code>get_latest_rss</code></td><td>RSS 内容</td></tr><tr><td></td><td><code>search_rss</code></td><td>RSS 搜索</td></tr><tr><td>搜索</td><td><code>search_news</code></td><td>统一搜索</td></tr><tr><td></td><td><code>find_related_news</code></td><td>相似新闻</td></tr><tr><td>分析</td><td><code>analyze_topic_trend</code></td><td>趋势分析</td></tr><tr><td></td><td><code>analyze_sentiment</code></td><td>情感分析</td></tr><tr><td></td><td><code>aggregate_news</code></td><td>跨平台聚合</td></tr><tr><td></td><td><code>compare_periods</code></td><td>时期对比</td></tr><tr><td></td><td><code>generate_summary_report</code></td><td>生成报告</td></tr><tr><td>通知</td><td><code>send_notification</code></td><td>推送消息</td></tr><tr><td>文章</td><td><code>read_article</code></td><td>读取正文</td></tr></tbody></table></div><p>总共 21 个工具，覆盖了从查询到分析到推送的全流程。</p><h2 id="九、数据存储"><a href="#九、数据存储" class="headerlink" title="九、数据存储"></a>九、数据存储</h2><p>TrendRadar 的数据存储在 SQLite 数据库，按日期分库：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">output&#x2F;</span><br><span class="line">├── news&#x2F;</span><br><span class="line">│   ├── 2025-05-16.db    # 当天热榜数据</span><br><span class="line">│   ├── 2025-05-15.db    # 历史数据</span><br><span class="line">├── rss&#x2F;</span><br><span class="line">│   ├── 2025-05-16.db    # RSS 数据</span><br><span class="line">└── html&#x2F;</span><br><span class="line">    └── 当日汇总.html     # HTML 报告</span><br></pre></td></tr></table></figure><p>数据库表结构设计得很好：</p><ul><li><code>news_items</code>：存储新闻条目（标题、URL、排名）</li><li><code>rank_history</code>：记录排名变化历史（每次抓取的排名）</li><li><code>crawl_records</code>：记录抓取时间和数量</li></ul><p>这样设计的好处是可以追踪热度变化轨迹。比如某条新闻早上排第 5，中午排第 3，晚上掉到第 10——这些变化都会被记录下来，供 AI 分析。</p><h2 id="十、与其他工具对比"><a href="#十、与其他工具对比" class="headerlink" title="十、与其他工具对比"></a>十、与其他工具对比</h2><div class="table-container"><table><thead><tr><th>工具</th><th>TrendRadar</th><th>RSS 阅读器</th><th>热榜网站</th></tr></thead><tbody><tr><td>数据源</td><td>50+ 平台热榜</td><td>RSS订阅源</td><td>单一或少量平台</td></tr><tr><td>筛选方式</td><td>关键词+AI</td><td>手动订阅</td><td>无筛选</td></tr><tr><td>推送</td><td>多渠道</td><td>需额外工具</td><td>无推送</td></tr><tr><td>AI 分析</td><td>内置</td><td>无</td><td>无</td></tr><tr><td>趋势追踪</td><td>有</td><td>无</td><td>无</td></tr><tr><td>部署复杂度</td><td>中</td><td>低</td><td>无需部署</td></tr></tbody></table></div><p>TrendRadar 的优势在于：<strong>聚合 + 筛选 + 分析 + 推送</strong> 一条龙。RSS 阅读器适合订阅特定博客，热榜网站适合快速浏览，但都没有 AI 分析和自动推送。</p><h2 id="十一、项目地址和资源"><a href="#十一、项目地址和资源" class="headerlink" title="十一、项目地址和资源"></a>十一、项目地址和资源</h2><ul><li><strong>GitHub</strong>：<a href="https://github.com/sansan0/TrendRadar" target="_blank" rel="noopener">https://github.com/sansan0/TrendRadar</a></li><li><strong>可视化配置编辑器</strong>：<a href="https://sansan0.github.io/TrendRadar/" target="_blank" rel="noopener">https://sansan0.github.io/TrendRadar/</a></li><li><strong>NewsNow 数据源</strong>：<a href="https://github.com/ourongxing/newsnow" target="_blank" rel="noopener">https://github.com/ourongxing/newsnow</a></li></ul><p>项目维护得很活跃，版本迭代快（从 v1.0 到 v6.7），文档也很详细。有问题可以去 GitHub Issues 提，作者回复很及时。</p><h2 id="十二、总结"><a href="#十二、总结" class="headerlink" title="十二、总结"></a>十二、总结</h2><p>TrendRadar 解决的问题是：<strong>如何从信息洪流中高效获取有价值的内容</strong>。</p><p>它不是简单的热榜聚合，而是：</p><ul><li>用关键词/AI筛选过滤噪音</li><li>用多渠道推送直达手机</li><li>用AI分析提供深度洞察</li><li>用MCP协议支持自定义数据挖掘</li></ul><p>如果你每天花大量时间刷 APP 看热点，却总觉得信息过载、抓不住重点——试试 TrendRadar。部署一次，配置好关键词，之后就等着推送敲门，看完推送就完事。</p><p>从”被动接收算法推荐”变成”主动获取关心内容”，这才是高效的信息消费方式。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、背景&quot;&gt;&lt;a href=&quot;#一、背景&quot; class=&quot;headerlink&quot; title=&quot;一、背景&quot;&gt;&lt;/a&gt;一、背景&lt;/h2&gt;&lt;p&gt;每天打开手机，十几个 APP 轮番刷一遍，微博热搜、知乎热榜、抖音热点、今日头条……刷完一圈下来，两个小时过去了，真正有用的</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="AI 开源工具" scheme="https://donehub.github.io/tags/AI-%E5%BC%80%E6%BA%90%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>存储芯片双雄：DRAM与NAND Flash全景解析</title>
    <link href="https://donehub.github.io/2026/05/14/dram-nand-flash-deep-analysis/"/>
    <id>https://donehub.github.io/2026/05/14/dram-nand-flash-deep-analysis/</id>
    <published>2026-05-13T16:00:00.000Z</published>
    <updated>2026-05-16T04:36:30.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h2><p>2026年，AI浪潮席卷全球，最近都被这些新闻刷频了：</p><blockquote><p>“三星市值突破新高，HBM订单排到明年”<br>“海力士股价暴涨，成英伟达最大HBM供应商”<br>“美光宣布HBM3e量产，AI存储竞争白热化”<br>“长鑫存储融资成功，中国DRAM再获突破”</p></blockquote><p>作为科技爱好者，你可能会有很多困惑：</p><ul><li>内存条、固态硬盘、HBM… 到底都是什么？</li><li>为什么AI训练需要HBM，而不是普通内存条？</li><li>三星、海力士、美光、长鑫、长江存储… 各家到底做什么？</li></ul><p>这篇文章，用<strong>两个视角</strong>帮你彻底理清：<strong>DRAM</strong>和<strong>NAND Flash</strong>——存储芯片世界的两大支柱。</p><hr><h2 id="二、第一视角：DRAM"><a href="#二、第一视角：DRAM" class="headerlink" title="二、第一视角：DRAM"></a>二、第一视角：DRAM</h2><h3 id="一、DRAM是什么？"><a href="#一、DRAM是什么？" class="headerlink" title="一、DRAM是什么？"></a>一、DRAM是什么？</h3><p><strong>DRAM = Dynamic Random Access Memory</strong>，中文叫”动态随机存取存储器”。</p><p>通俗理解：<strong>DRAM就是你电脑上的”临时工作台”</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">你在用Word写文档：</span><br><span class="line">硬盘（SSD）── 长期保存文件，断电后还在</span><br><span class="line">     ↓ 打开文件时加载</span><br><span class="line">内存条（DRAM）── 临时存放正在编辑的内容，断电就没了</span><br><span class="line">     ↓ CPU随时读取</span><br><span class="line">CPU ── 处理文字、格式、排版</span><br></pre></td></tr></table></figure><p>忘记保存就断电？文件没了。因为DRAM里的数据瞬间清空。</p><h3 id="二、为什么叫”动态”？"><a href="#二、为什么叫”动态”？" class="headerlink" title="二、为什么叫”动态”？"></a>二、为什么叫”动态”？</h3><p>这是DRAM与其他内存技术最大的区别：<strong>数据需要不断”刷新”才能保持</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">DRAM的存储单元 &#x3D; 1个电容 + 1个晶体管</span><br><span class="line"></span><br><span class="line">电容充电 &#x3D; 存储&quot;1&quot;</span><br><span class="line">电容放电 &#x3D; 存储&quot;0&quot;</span><br><span class="line"></span><br><span class="line">问题：电容会自然漏电，电荷慢慢流失</span><br><span class="line">      几毫秒后，数据就没了</span><br><span class="line"></span><br><span class="line">解决：每隔几毫秒&quot;刷新&quot;一次</span><br><span class="line">      把电荷补回去，数据才能保持</span><br></pre></td></tr></table></figure><p>这就是”动态”的含义：数据不是静态保存的，需要<strong>动态、持续地刷新</strong>。</p><p>对比一下SRAM（静态随机存取存储器）：</p><div class="table-container"><table><thead><tr><th>特性</th><th>DRAM</th><th>SRAM</th></tr></thead><tbody><tr><td>存储单元</td><td>1电容+1晶体管</td><td>6个晶体管</td></tr><tr><td>需要刷新</td><td>✅ 必须周期刷新</td><td>❌ 不需要</td></tr><tr><td>密度</td><td>高（结构简单）</td><td>低（结构复杂）</td></tr><tr><td>容量</td><td>大（单芯片可达16GB）</td><td>小（通常几KB到几MB）</td></tr><tr><td>成本</td><td>便宜</td><td>贵</td></tr><tr><td>应用</td><td>内存条、手机内存</td><td>CPU缓存（L1/L2/L3）</td></tr></tbody></table></div><p>一句话：<strong>DRAM性价比高，适合做大容量内存；SRAM性能好但贵，只做CPU内部的小缓存</strong>。</p><h3 id="三、DRAM的核心特点"><a href="#三、DRAM的核心特点" class="headerlink" title="三、DRAM的核心特点"></a>三、DRAM的核心特点</h3><div class="table-container"><table><thead><tr><th>特点</th><th>说明</th></tr></thead><tbody><tr><td><strong>易失性</strong></td><td>断电后数据立即丢失</td></tr><tr><td><strong>需要刷新</strong></td><td>每隔几毫秒必须刷新，否则数据消失</td></tr><tr><td><strong>速度快</strong></td><td>读写速度远快于硬盘</td></tr><tr><td><strong>密度高</strong></td><td>单芯片可存储大量数据</td></tr><tr><td><strong>成本低</strong></td><td>每GB价格相对便宜</td></tr></tbody></table></div><h3 id="四、DRAM产品家族"><a href="#四、DRAM产品家族" class="headerlink" title="四、DRAM产品家族"></a>四、DRAM产品家族</h3><p>都属于DRAM技术，但针对不同场景优化：</p><div class="table-container"><table><thead><tr><th>产品</th><th>特点</th><th>用途</th><th>带宽</th><th>代表厂商</th></tr></thead><tbody><tr><td><strong>DDR4/DDR5</strong></td><td>标准内存条</td><td>PC、服务器</td><td>~25GB/s</td><td>三星/海力士/美光/长鑫</td></tr><tr><td><strong>LPDDR4/LPDDR5</strong></td><td>低功耗版</td><td>手机、平板</td><td>~60GB/s</td><td>三星/海力士/美光/长鑫</td></tr><tr><td><strong>HBM/HBM3e</strong></td><td>堆叠高带宽</td><td>AI训练GPU</td><td>~1TB/s+</td><td>三星/海力士/美光</td></tr><tr><td><strong>GDDR6/GDDR7</strong></td><td>显卡显存</td><td>游戏显卡</td><td>~160GB/s</td><td>三星/海力士/美光</td></tr><tr><td><strong>Server DRAM</strong></td><td>服务器专用</td><td>数据中心</td><td>~50GB/s</td><td>三星/海力士/美光/长鑫</td></tr><tr><td><strong>Mobile DRAM</strong></td><td>移动端定制</td><td>智能穿戴、IoT</td><td>~10GB/s</td><td>各厂商均有</td></tr></tbody></table></div><h3 id="五、重点解读：HBM为什么是AI的命门"><a href="#五、重点解读：HBM为什么是AI的命门" class="headerlink" title="五、重点解读：HBM为什么是AI的命门"></a>五、重点解读：HBM为什么是AI的命门</h3><p>普通内存条有个致命瓶颈：<strong>带宽不够</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">普通内存条：单通道带宽约 25GB&#x2F;s（DDR5-6400）</span><br><span class="line">AI训练需求：几百 GB&#x2F;s 甚至 TB&#x2F;s 级别</span><br></pre></td></tr></table></figure><p>GPU（如英伟达H100）算力极强，但数据喂不进去——传统内存条成了瓶颈。</p><p><strong>HBM的解决方案：垂直堆叠</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">传统内存条：</span><br><span class="line">┌───┐</span><br><span class="line">│芯片│ ← 平铺在PCB上，信号要走很远</span><br><span class="line">└───┘</span><br><span class="line"></span><br><span class="line">HBM：</span><br><span class="line">┌─────┐</span><br><span class="line">│芯片8│ ↑</span><br><span class="line">├─────┤ │ 垂直堆叠8-12层</span><br><span class="line">│芯片7│ │ 通过TSV（硅通孔）连接</span><br><span class="line">│ ... │ │ 路径极短，速度极快</span><br><span class="line">└──┬──┘ ↓</span><br><span class="line">   └── GPU芯片（紧邻封装）</span><br></pre></td></tr></table></figure><p><strong>核心技术</strong>：</p><div class="table-container"><table><thead><tr><th>技术</th><th>作用</th></tr></thead><tbody><tr><td><strong>TSV（硅通孔）</strong></td><td>在芯片上打微孔，垂直导通</td></tr><tr><td><strong>3D堆叠</strong></td><td>8层、12层DRAM芯片叠在一起</td></tr><tr><td><strong>CoWoS封装</strong></td><td>把HBM和GPU封装在同一块硅中介层上</td></tr></tbody></table></div><p><strong>性能对比</strong>：</p><div class="table-container"><table><thead><tr><th>类型</th><th>带宽</th><th>应用</th></tr></thead><tbody><tr><td>DDR5内存条</td><td>~25GB/s</td><td>电脑、服务器</td></tr><tr><td>HBM3</td><td>~1TB/s</td><td>AI训练GPU</td></tr><tr><td>HBM3e</td><td>~1.5TB/s+</td><td>最先进AI芯片</td></tr></tbody></table></div><p>这就是为什么英伟达H100价格3万美元起步——<strong>HBM成本占了很大比例</strong>。</p><h3 id="六、DRAM厂商格局"><a href="#六、DRAM厂商格局" class="headerlink" title="六、DRAM厂商格局"></a>六、DRAM厂商格局</h3><p><strong>全球市场份额（2025年Q1）</strong>：</p><div class="table-container"><table><thead><tr><th style="text-align:center">排名</th><th>厂商</th><th>国家</th><th style="text-align:center">份额</th><th style="text-align:center">趋势</th><th>技术水平</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td><strong>三星电子</strong></td><td>韩国</td><td style="text-align:center">~40-41%</td><td style="text-align:center">↓略降</td><td>最领先</td></tr><tr><td style="text-align:center">2</td><td><strong>SK海力士</strong></td><td>韩国</td><td style="text-align:center">~28-29%</td><td style="text-align:center">↑上升</td><td>HBM领先</td></tr><tr><td style="text-align:center">3</td><td><strong>美光科技</strong></td><td>美国</td><td style="text-align:center">~23-24%</td><td style="text-align:center">稳定</td><td>一流</td></tr><tr><td style="text-align:center">4</td><td><strong>长鑫存储</strong></td><td>中国</td><td style="text-align:center">~5-6%</td><td style="text-align:center">↑上升</td><td>追赶中</td></tr><tr><td style="text-align:center">5</td><td><strong>南亚科技</strong></td><td>中国台湾</td><td style="text-align:center">~2%</td><td style="text-align:center">稳定</td><td>中端</td></tr><tr><td style="text-align:center">6</td><td><strong>华邦电子</strong></td><td>中国台湾</td><td style="text-align:center">~1%</td><td style="text-align:center">稳定</td><td>中低端</td></tr><tr><td style="text-align:center">7</td><td><strong>力积电</strong></td><td>中国台湾</td><td style="text-align:center">&lt;1%</td><td style="text-align:center">稳定</td><td>代工</td></tr></tbody></table></div><blockquote><p><strong>三巨头控制93%+市场</strong></p></blockquote><p><strong>2025年关键变化</strong>：</p><ul><li><strong>海力士份额上升</strong>：HBM业务驱动，成英伟达最大HBM供应商</li><li><strong>长鑫份额上升</strong>：从3-5%提升到5-6%，国产替代加速</li><li><strong>三星份额略降</strong>：战略转向高利润HBM，减少低端产能</li></ul><p><strong>技术能力矩阵</strong>：</p><div class="table-container"><table><thead><tr><th>厂商</th><th style="text-align:center">DDR5</th><th style="text-align:center">LPDDR5X</th><th style="text-align:center">HBM3e</th><th style="text-align:center">GDDR7</th><th style="text-align:center">制程</th></tr></thead><tbody><tr><td>三星</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">12nm</td></tr><tr><td>海力士</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅领先</td><td style="text-align:center">✅</td><td style="text-align:center">12nm</td></tr><tr><td>美光</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">12nm</td></tr><tr><td>长鑫</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">17nm</td></tr><tr><td>南亚科</td><td style="text-align:center">✅</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">20nm</td></tr><tr><td>华邦</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">25nm</td></tr></tbody></table></div><p><strong>中国现状</strong>：长鑫存储是中国大陆唯一的DRAM厂商，2016年成立，填补了国内空白。目前主攻DDR4/DDR5、LPDDR4/LPDDR5，HBM仍在研发阶段。</p><p><strong>日本现状</strong>：日本已无DRAM厂商。曾经的霸主尔必达2012年破产被海力士收购，日本DRAM产业终结。</p><hr><h2 id="三、第二视角：NAND-Flash"><a href="#三、第二视角：NAND-Flash" class="headerlink" title="三、第二视角：NAND Flash"></a>三、第二视角：NAND Flash</h2><h3 id="一、NAND-Flash是什么？"><a href="#一、NAND-Flash是什么？" class="headerlink" title="一、NAND Flash是什么？"></a>一、NAND Flash是什么？</h3><p><strong>NAND Flash</strong>，中文叫”NAND闪存”，是一种<strong>非易失性存储器</strong>。</p><p>通俗理解：<strong>NAND Flash就是你电脑的”永久仓库”</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">你的手机：</span><br><span class="line">RAM（DRAM）8GB ── 运行APP时临时用，断电清空</span><br><span class="line">存储（NAND）128GB ── 保存照片、APP、文件，断电后还在</span><br><span class="line"></span><br><span class="line">你的电脑：</span><br><span class="line">内存条（DRAM）16GB ── 正在运行的程序</span><br><span class="line">固态硬盘（NAND）512GB ── 系统、软件、所有文件</span><br></pre></td></tr></table></figure><p><strong>核心特点</strong>：断电后数据<strong>不会丢失</strong>，可以长期保存。</p><h3 id="二、为什么叫”NAND”和”闪存”"><a href="#二、为什么叫”NAND”和”闪存”" class="headerlink" title="二、为什么叫”NAND”和”闪存”"></a>二、为什么叫”NAND”和”闪存”</h3><p><strong>NAND</strong>是逻辑门电路的名字（Not AND），用这种结构的晶体管阵列存储数据，所以叫NAND Flash。</p><p><strong>“闪存”的由来</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">传统EEPROM：擦除需要几秒</span><br><span class="line">NAND Flash：擦除只需几毫秒</span><br><span class="line"></span><br><span class="line">像&quot;闪光&quot;一样快 → Flash Memory（闪存）</span><br></pre></td></tr></table></figure><h3 id="三、工作原理"><a href="#三、工作原理" class="headerlink" title="三、工作原理"></a>三、工作原理</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">NAND存储单元 &#x3D; 浮栅晶体管</span><br><span class="line"></span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ 浮栅            │ ← 电子被困在这里</span><br><span class="line">│  有电子 &#x3D; 存储&quot;0&quot;│    不会跑掉</span><br><span class="line">│  无电子 &#x3D; 存储&quot;1&quot;│</span><br><span class="line">└─────────────────┘</span><br><span class="line"></span><br><span class="line">写入：把电子注入浮栅（高压）</span><br><span class="line">擦除：把电子从浮栅拉出来（更高电压）</span><br><span class="line">读取：检测浮栅是否有电子</span><br></pre></td></tr></table></figure><p><strong>关键点</strong>：电子被困在浮栅里，没有电源也能长期保存——这就是”非易失性”的原因。</p><h3 id="四、NAND-Flash的核心特点"><a href="#四、NAND-Flash的核心特点" class="headerlink" title="四、NAND Flash的核心特点"></a>四、NAND Flash的核心特点</h3><div class="table-container"><table><thead><tr><th>特点</th><th>说明</th></tr></thead><tbody><tr><td><strong>非易失性</strong></td><td>断电后数据保留</td></tr><tr><td><strong>不需要刷新</strong></td><td>与DRAM不同，写入后自然保持</td></tr><tr><td><strong>密度高</strong></td><td>单芯片容量远超DRAM</td></tr><tr><td><strong>有擦写寿命</strong></td><td>每个单元可擦写几千到几万次</td></tr><tr><td><strong>速度较慢</strong></td><td>比DRAM慢，但比机械硬盘快很多</td></tr></tbody></table></div><h3 id="五、NAND分类（按每单元存储位数）"><a href="#五、NAND分类（按每单元存储位数）" class="headerlink" title="五、NAND分类（按每单元存储位数）"></a>五、NAND分类（按每单元存储位数）</h3><div class="table-container"><table><thead><tr><th>类型</th><th>全称</th><th style="text-align:center">每单元存储</th><th>特点</th><th>寿命</th><th>应用</th></tr></thead><tbody><tr><td><strong>SLC</strong></td><td>Single-Level Cell</td><td style="text-align:center">1 bit</td><td>最快、最耐用、最贵</td><td>10万次</td><td>企业/军工</td></tr><tr><td><strong>MLC</strong></td><td>Multi-Level Cell</td><td style="text-align:center">2 bit</td><td>平衡性能与成本</td><td>3000-1万次</td><td>高端消费</td></tr><tr><td><strong>TLC</strong></td><td>Triple-Level Cell</td><td style="text-align:center">3 bit</td><td>主流选择，性价比高</td><td>500-3000次</td><td>消费级SSD</td></tr><tr><td><strong>QLC</strong></td><td>Quad-Level Cell</td><td style="text-align:center">4 bit</td><td>便宜但慢</td><td>100-1000次</td><td>大容量存储</td></tr></tbody></table></div><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">简单理解：</span><br><span class="line">SLC &#x3D; 每格只放1个东西，空间利用率低，但耐用快速</span><br><span class="line">TLC &#x3D; 每格塞3个东西，空间利用率高，但慢一些</span><br><span class="line">QLC &#x3D; 每格塞4个东西，最便宜，但寿命最短</span><br></pre></td></tr></table></figure><h3 id="六、3D-NAND：垂直堆叠技术"><a href="#六、3D-NAND：垂直堆叠技术" class="headerlink" title="六、3D NAND：垂直堆叠技术"></a>六、3D NAND：垂直堆叠技术</h3><p>传统NAND是平铺的，容量有限。现代技术把存储单元垂直堆叠：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">传统2D NAND：</span><br><span class="line">┌──┐ ┌──┐ ┌──┐ ┌──┐ ← 平铺在一层</span><br><span class="line"></span><br><span class="line">3D NAND：</span><br><span class="line">┌──┐</span><br><span class="line">│232│ ↑ 垂直堆叠</span><br><span class="line">├──┤ │ 像盖楼房一样</span><br><span class="line">│...│ │ 同样面积，容量翻倍</span><br><span class="line">└──┘ ↓</span><br></pre></td></tr></table></figure><p>主流3D NAND层数：</p><div class="table-container"><table><thead><tr><th>厂商</th><th style="text-align:center">最高层数</th></tr></thead><tbody><tr><td>三星</td><td style="text-align:center">236层</td></tr><tr><td>海力士</td><td style="text-align:center">238层</td></tr><tr><td>美光</td><td style="text-align:center">232层</td></tr><tr><td>长江存储</td><td style="text-align:center">232层（Xtacking技术）</td></tr></tbody></table></div><h3 id="七、长江存储的独创技术：Xtacking"><a href="#七、长江存储的独创技术：Xtacking" class="headerlink" title="七、长江存储的独创技术：Xtacking"></a>七、长江存储的独创技术：Xtacking</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">传统3D NAND：</span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ 存储单元+外围电路 │ ← 同一片晶圆上制造</span><br><span class="line">│    叠在一起      │    层数增加会互相干扰</span><br><span class="line">└─────────────────┘</span><br><span class="line"></span><br><span class="line">Xtacking架构：</span><br><span class="line">┌──────────┐   ┌──────────┐</span><br><span class="line">│ 存储单元  │ ←→ │ 外围电路  │ ← 两片晶圆分别制造</span><br><span class="line">│（垂直堆叠）│   │（高速逻辑）│   再键合在一起</span><br><span class="line">└──────────┘   └──────────┘</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：存储密度更高、I/O速度更快、制造效率更高。</p><h3 id="八、NAND-Flash产品家族"><a href="#八、NAND-Flash产品家族" class="headerlink" title="八、NAND Flash产品家族"></a>八、NAND Flash产品家族</h3><div class="table-container"><table><thead><tr><th>产品</th><th>特点</th><th>用途</th><th>速度</th></tr></thead><tbody><tr><td><strong>SSD固态硬盘</strong></td><td>大容量高速存储</td><td>电脑硬盘</td><td>3-7GB/s</td></tr><tr><td><strong>UFS</strong></td><td>高速嵌入式存储</td><td>中高端手机</td><td>~4GB/s</td></tr><tr><td><strong>eMMC</strong></td><td>集成控制器，成本低</td><td>低端手机/IoT</td><td>~400MB/s</td></tr><tr><td><strong>SD卡/TF卡</strong></td><td>可插拔便携</td><td>相机/无人机</td><td>~100MB/s</td></tr><tr><td><strong>USB闪存盘</strong></td><td>便携通用</td><td>数据传输</td><td>~100MB/s</td></tr></tbody></table></div><h3 id="九、NAND-Flash厂商格局"><a href="#九、NAND-Flash厂商格局" class="headerlink" title="九、NAND Flash厂商格局"></a>九、NAND Flash厂商格局</h3><p><strong>全球市场份额（2025年）</strong>：</p><div class="table-container"><table><thead><tr><th style="text-align:center">排名</th><th>厂商</th><th>国家</th><th style="text-align:center">份额</th><th style="text-align:center">趋势</th><th>技术水平</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td><strong>三星电子</strong></td><td>韩国</td><td style="text-align:center">~35-38%</td><td style="text-align:center">稳定</td><td>最领先</td></tr><tr><td style="text-align:center">2</td><td><strong>凯侠</strong></td><td>日本</td><td style="text-align:center">~15-18%</td><td style="text-align:center">稳定</td><td>一流（2024年IPO）</td></tr><tr><td style="text-align:center">3</td><td><strong>西部数据</strong></td><td>美国</td><td style="text-align:center">~12-15%</td><td style="text-align:center">稳定</td><td>一流（与凯侠合资）</td></tr><tr><td style="text-align:center">4</td><td><strong>SK海力士</strong></td><td>韩国</td><td style="text-align:center">~12-15%</td><td style="text-align:center">↑上升</td><td>一流（含Solidigm）</td></tr><tr><td style="text-align:center">5</td><td><strong>美光科技</strong></td><td>美国</td><td style="text-align:center">~10-12%</td><td style="text-align:center">稳定</td><td>一流</td></tr><tr><td style="text-align:center">6</td><td><strong>长江存储</strong></td><td>中国</td><td style="text-align:center">~5-8%</td><td style="text-align:center">受限</td><td>接近一流</td></tr></tbody></table></div><blockquote><p><strong>三星+凯侠+西数+海力士控制80%+市场</strong></p></blockquote><p><strong>2025年关键变化</strong>：</p><ul><li><strong>凯侠完成IPO</strong>：2024年底上市，获得资金扩张</li><li><strong>西部数据拆分</strong>：计划将SanDisk业务独立分拆</li><li><strong>长江存储受限</strong>：美国制裁持续，全球份额增长受阻，但国内市场稳步发展</li></ul><p><strong>中国现状</strong>：长江存储是中国最大的NAND Flash厂商，技术水平与国际差距较小，232层Xtacking技术已接近第一梯队。</p><p><strong>日本现状</strong>：凯侠（原东芝存储）专注NAND Flash，是日本唯一的存储芯片厂商。日本已无DRAM厂商。</p><hr><h2 id="四、两视角对比：DRAM-vs-NAND-Flash"><a href="#四、两视角对比：DRAM-vs-NAND-Flash" class="headerlink" title="四、两视角对比：DRAM vs NAND Flash"></a>四、两视角对比：DRAM vs NAND Flash</h2><h3 id="核心区别"><a href="#核心区别" class="headerlink" title="核心区别"></a>核心区别</h3><div class="table-container"><table><thead><tr><th>维度</th><th>DRAM</th><th>NAND Flash</th></tr></thead><tbody><tr><td><strong>断电后</strong></td><td>❌ 数据丢失</td><td>✅ 数据保留</td></tr><tr><td><strong>需要刷新</strong></td><td>✅ 必须周期刷新</td><td>❌ 不需要</td></tr><tr><td><strong>速度</strong></td><td>很快（几十GB/s）</td><td>较慢（几GB/s）</td></tr><tr><td><strong>容量</strong></td><td>小（8-16GB/芯片）</td><td>大（256GB-4TB/芯片）</td></tr><tr><td><strong>擦写寿命</strong></td><td>无限（理论上）</td><td>有（几千到几万次）</td></tr><tr><td><strong>成本/GB</strong></td><td>较贵</td><td>便宜</td></tr><tr><td><strong>典型产品</strong></td><td>内存条、HBM</td><td>固态硬盘、手机存储</td></tr><tr><td><strong>生活比喻</strong></td><td>“临时工作台”</td><td>“永久仓库”</td></tr></tbody></table></div><h3 id="用一个场景理解"><a href="#用一个场景理解" class="headerlink" title="用一个场景理解"></a>用一个场景理解</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">你打开一个大型游戏：</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ 固态硬盘（NAND）                     │</span><br><span class="line">│ 存着游戏的所有文件（50GB）           │</span><br><span class="line">│ 断电后还在                           │</span><br><span class="line">└─────────────────────────────────────┘</span><br><span class="line">         ↓ 启动游戏时加载</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ 内存条（DRAM）                       │</span><br><span class="line">│ 临时存放正在运行的游戏数据（8GB）     │</span><br><span class="line">│ 断电就没了                           │</span><br><span class="line">└─────────────────────────────────────┘</span><br><span class="line">         ↓ GPU随时读取渲染</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ GPU + HBM                            │</span><br><span class="line">│ AI时代：GPU算力强，HBM喂得快          │</span><br><span class="line">│ 传统内存条喂不饱GPU                  │</span><br><span class="line">└─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><hr><h2 id="五、中国存储产业现状"><a href="#五、中国存储产业现状" class="headerlink" title="五、中国存储产业现状"></a>五、中国存储产业现状</h2><h3 id="两家企业，两条路线"><a href="#两家企业，两条路线" class="headerlink" title="两家企业，两条路线"></a>两家企业，两条路线</h3><div class="table-container"><table><thead><tr><th>企业</th><th>技术领域</th><th>定位</th><th>国际差距</th></tr></thead><tbody><tr><td><strong>长鑫存储</strong></td><td>DRAM</td><td>中国唯一内存厂商</td><td>差距较大（落后约2代）</td></tr><tr><td><strong>长江存储</strong></td><td>NAND Flash</td><td>中国最大闪存厂商</td><td>差距较小（接近第一梯队）</td></tr></tbody></table></div><h3 id="为什么差距不同？"><a href="#为什么差距不同？" class="headerlink" title="为什么差距不同？"></a>为什么差距不同？</h3><p><strong>DRAM差距较大</strong>：</p><ul><li>三星、海力士、美光有40年技术积累</li><li>DRAM工艺极其复杂，专利壁垒高</li><li>设备（光刻机、刻蚀机）受国外限制</li></ul><p><strong>NAND差距较小</strong>：</p><ul><li>NAND技术路线相对灵活</li><li>长江存储Xtacking架构实现”弯道超车”</li><li>层数堆叠更多依赖工艺创新，而非单纯制程</li></ul><hr><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><div class="table-container"><table><thead><tr><th>新闻里提到的</th><th>属于哪个领域</th><th>哪家厂商在做</th></tr></thead><tbody><tr><td><strong>内存条涨价</strong></td><td>DRAM</td><td>三星/海力士/美光/长鑫</td></tr><tr><td><strong>HBM供不应求</strong></td><td>DRAM（高端）</td><td>三星/海力士/美光</td></tr><tr><td><strong>固态硬盘新品</strong></td><td>NAND Flash</td><td>三星/凯侠/西部数据/长江存储</td></tr><tr><td><strong>长鑫融资成功</strong></td><td>DRAM</td><td>中国唯一DRAM厂商</td></tr><tr><td><strong>长江存储突破</strong></td><td>NAND Flash</td><td>中国NAND厂商</td></tr></tbody></table></div><hr><p><strong>DRAM</strong>：三巨头（三星+海力士+美光）控制90%市场，长鑫是中国唯一希望，日本已无厂商。</p><p><strong>NAND Flash</strong>：六强格局（三星/海力士/凯侠/西数/美光/长江存储），长江存储技术水平接近一流。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、背景&quot;&gt;&lt;a href=&quot;#一、背景&quot; class=&quot;headerlink&quot; title=&quot;一、背景&quot;&gt;&lt;/a&gt;一、背景&lt;/h2&gt;&lt;p&gt;2026年，AI浪潮席卷全球，最近都被这些新闻刷频了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“三星市值突破新高，HBM订</summary>
      
    
    
    
    <category term="存储芯片" scheme="https://donehub.github.io/categories/%E5%AD%98%E5%82%A8%E8%8A%AF%E7%89%87/"/>
    
    
    <category term="概念解读" scheme="https://donehub.github.io/tags/%E6%A6%82%E5%BF%B5%E8%A7%A3%E8%AF%BB/"/>
    
  </entry>
  
  <entry>
    <title>多Agent系统的设计与评估</title>
    <link href="https://donehub.github.io/2026/04/30/multi-agent-decision-and-evaluation/"/>
    <id>https://donehub.github.io/2026/04/30/multi-agent-decision-and-evaluation/</id>
    <published>2026-04-29T16:00:00.000Z</published>
    <updated>2026-04-30T15:19:12.023Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>“任务越复杂越该用多Agent”——听起来好像没毛病，但这句话背后藏着一个巨大的陷阱。很多人一拍脑袋就上多Agent，结果延迟爆表、成本失控、日志一团浆糊，最后发现单Agent加个工具调用就能搞定。上一篇我们聊了 Multi-Agent 系统的设计原理，今天换个角度：什么时候该用多Agent？用完之后，怎么评判这套设计到底好不好？</p></blockquote><hr><h2 id="一、先破一个误区：复杂度不是决策标准"><a href="#一、先破一个误区：复杂度不是决策标准" class="headerlink" title="一、先破一个误区：复杂度不是决策标准"></a>一、先破一个误区：复杂度不是决策标准</h2><p>很多人一听到”多Agent”，脑子里的画面是这样的：一个主控 Agent 坐镇中央，下面挂着写代码的、做测试的、写文档的，各司其职，特别壮观。</p><p>但壮观不等于好用。</p><p><strong>复杂度从来不是选择多Agent的理由。</strong> 真正的决策标准只有两个维度：<strong>并发收益</strong>和<strong>上下文约束</strong>。</p><p>用一张表说清楚：</p><div class="table-container"><table><thead><tr><th>场景特征</th><th>单Agent够用吗</th><th>要不要上多Agent</th></tr></thead><tbody><tr><td>步骤严格串行，后一步依赖前一步结果</td><td>够用</td><td>不要，纯浪费</td></tr><tr><td>多个子任务互相独立，可以同时跑</td><td>不够</td><td>要，并行收益明显</td></tr><tr><td>单个任务的上下文撑爆模型窗口</td><td>不够</td><td>要，必须切分</td></tr><tr><td>子任务之间需要频繁交换中间状态</td><td>勉强</td><td>谨慎，通信开销可能吃掉收益</td></tr></tbody></table></div><p>但这张表太粗了，真实决策远比这复杂。下面我们拆开来讲。</p><hr><h2 id="二、决策框架：四把尺子量出答案"><a href="#二、决策框架：四把尺子量出答案" class="headerlink" title="二、决策框架：四把尺子量出答案"></a>二、决策框架：四把尺子量出答案</h2><h3 id="2-1-第一把尺子：任务依赖图是线性的还是扇出的？"><a href="#2-1-第一把尺子：任务依赖图是线性的还是扇出的？" class="headerlink" title="2.1 第一把尺子：任务依赖图是线性的还是扇出的？"></a>2.1 第一把尺子：任务依赖图是线性的还是扇出的？</h3><p>这是最核心的判断依据。</p><p><strong>线性依赖</strong>是指任务之间有严格的先后顺序：A 的输出是 B 的输入，B 的输出是 C 的输入。比如用户问”我的退款到哪一步了”——先识别意图，再查订单库，最后组织回复。这三步必须串行，中间插不进任何并行操作。</p><p><strong>扇出依赖</strong>是指一个任务可以拆成多个互不相关的子任务同时执行。比如”帮我审查这个代码仓库的安全性”——注入漏洞扫描、内存泄漏检测、代码风格检查，这三件事之间没有任何数据依赖，完全可以同时开工。</p><p>画成图就是这样的区别：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">线性依赖（单Agent）：</span><br><span class="line">意图识别 → 查数据库 → 组织回复</span><br><span class="line">  A    →    B     →    C</span><br><span class="line"></span><br><span class="line">扇出依赖（多Agent）：</span><br><span class="line">            ┌→ 安全检测 ──┐</span><br><span class="line">主控规划 ──→├→ 性能分析 ──├→ 汇总合并</span><br><span class="line">            └→ 规范检查 ──┘</span><br></pre></td></tr></table></figure><p><strong>判断标准</strong>：如果你的任务依赖图画出来是一条直线，用单Agent。如果画出来像一把扇子，考虑多Agent。</p><h3 id="2-2-第二把尺子：上下文窗口是不是硬瓶颈？"><a href="#2-2-第二把尺子：上下文窗口是不是硬瓶颈？" class="headerlink" title="2.2 第二把尺子：上下文窗口是不是硬瓶颈？"></a>2.2 第二把尺子：上下文窗口是不是硬瓶颈？</h3><p>有些任务看起来是线性的，但数据量大到单个模型根本吃不下。这时候即使任务是串行的，你也得想办法切分。</p><p>举个例子：给你一个 50 万行的代码仓库，要求生成完整的 API 文档。这不是并行任务——你需要理解全局架构才能写好文档。但问题是，50 万行代码塞不进任何模型的上下文窗口。</p><p>这时候多Agent的价值不是并行加速，而是<strong>上下文切片</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Agent-1：扫描 controller 层，提取接口定义</span><br><span class="line">Agent-2：扫描 service 层，提取业务逻辑</span><br><span class="line">Agent-3：扫描 entity 层，提取数据模型</span><br><span class="line">主控 Agent：拿到三份切片报告，合并生成文档</span><br></pre></td></tr></table></figure><p>每个 Agent 只需要处理自己那一层的代码，上下文压力骤降。最后主控 Agent 拿到的是三份精炼过的报告，而不是几十万行原始代码。</p><p><strong>判断标准</strong>：估算一下任务需要的上下文 token 量。如果超过模型窗口的 60%，就要考虑切分了。留 40% 的余量给系统 Prompt、工具调用和中间推理。</p><h3 id="2-3-第三把尺子：延迟预算够不够？"><a href="#2-3-第三把尺子：延迟预算够不够？" class="headerlink" title="2.3 第三把尺子：延迟预算够不够？"></a>2.3 第三把尺子：延迟预算够不够？</h3><p>这是最容易被忽略的一把尺子。</p><p>多Agent系统的延迟公式非常残酷：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">总延迟 &#x3D; max(各子Agent延迟) + 主控规划延迟 + 主控汇总延迟 + 通信开销</span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>max</code> 不是 <code>sum</code>——因为子 Agent 是并行的，总延迟取决于最慢的那个。听起来不错对吧？但别忘了后面还有三项固定开销。</p><p>在真实的线上环境里，大模型的一次调用延迟通常在 2-8 秒。一个三层架构的多Agent系统，光是规划和汇总就要各调一次模型，加上子 Agent 的执行时间，端到端延迟很容易突破 15 秒。</p><p>对比一下单Agent方案：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">单Agent：意图识别(2s) → 工具调用(1s) → 总结回复(2s) &#x3D; 5s</span><br><span class="line">多Agent：规划(3s) + max(子Agent×3)(4s) + 汇总(3s) + 通信(1s) &#x3D; 11s</span><br></pre></td></tr></table></figure><p><strong>判断标准</strong>：如果你的场景是面向用户的实时交互（聊天机器人、客服系统），延迟预算通常在 5 秒以内。这种场景下多Agent几乎一定会超时，单Agent + 工具调用是更务实的选择。如果是后台批处理任务（代码审计、报告生成），延迟预算宽松，多Agent才有发挥空间。</p><h3 id="2-4-第四把尺子：Token-预算能不能扛得住？"><a href="#2-4-第四把尺子：Token-预算能不能扛得住？" class="headerlink" title="2.4 第四把尺子：Token 预算能不能扛得住？"></a>2.4 第四把尺子：Token 预算能不能扛得住？</h3><p>多Agent系统的 Token 消耗不是线性增长，而是<strong>阶梯式跳涨</strong>。</p><p>每一次 Agent 调用，你都要支付：</p><ul><li>系统 Prompt（每次都一样，但每次都要算钱）</li><li>上下文注入（任务描述、历史信息）</li><li>模型推理（输出 token）</li></ul><p>假设你有一个主控 Agent + 5 个子 Agent，每个 Agent 的系统 Prompt 是 2000 token，任务描述平均 1000 token，输出平均 1500 token。那么一轮完整执行的 Token 消耗大约是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">主控：2000 + 1000 + 1500 &#x3D; 4500</span><br><span class="line">子Agent×5：(2000 + 1000 + 1500) × 5 &#x3D; 22500</span><br><span class="line">汇总：2000 + 3000(子Agent结果) + 2000 &#x3D; 7000</span><br><span class="line">总计：约 34000 token</span><br></pre></td></tr></table></figure><p>而单Agent完成同样任务，可能只需要 5000-8000 token。</p><p><strong>判断标准</strong>：如果你的系统要处理高并发请求（比如每秒几百个），Token 成本的差异会被放大几百倍。在做架构选型时，先算一笔账：多Agent方案的 Token 成本是单Agent的几倍？这个倍数你的预算扛不扛得住？</p><hr><h2 id="三、四个典型场景的决策推演"><a href="#三、四个典型场景的决策推演" class="headerlink" title="三、四个典型场景的决策推演"></a>三、四个典型场景的决策推演</h2><p>光讲理论不过瘾，我们拿四个真实场景来跑一遍决策流程。</p><h3 id="3-1-场景一：智能客服（结论：单Agent）"><a href="#3-1-场景一：智能客服（结论：单Agent）" class="headerlink" title="3.1 场景一：智能客服（结论：单Agent）"></a>3.1 场景一：智能客服（结论：单Agent）</h3><p>用户问：”我的订单 #12345 退款到哪一步了？”</p><p><strong>依赖图分析</strong>：识别意图 → 查订单 → 回复。严格线性，没有并行空间。</p><p><strong>上下文分析</strong>：一次对话的上下文很小，远不到窗口极限。</p><p><strong>延迟分析</strong>：用户在等回复，延迟预算 3 秒。</p><p><strong>Token 分析</strong>：高并发场景（成千上万用户同时咨询），成本敏感。</p><p>四把尺子量完，全部指向单Agent。上多Agent就是过度设计。</p><h3 id="3-2-场景二：企业级代码安全审计（结论：多Agent）"><a href="#3-2-场景二：企业级代码安全审计（结论：多Agent）" class="headerlink" title="3.2 场景二：企业级代码安全审计（结论：多Agent）"></a>3.2 场景二：企业级代码安全审计（结论：多Agent）</h3><p>需求：扫描一个 10 万行代码仓库，输出安全漏洞报告。</p><p><strong>依赖图分析</strong>：注入扫描、内存泄漏检测、依赖漏洞检查、代码规范审查——四个子任务完全独立。</p><p><strong>上下文分析</strong>：10 万行代码远超单模型窗口，必须切片。</p><p><strong>延迟分析</strong>：后台批处理，用户可以等几分钟，延迟预算宽松。</p><p><strong>Token 分析</strong>：低并发（一天可能就跑几次），成本可控。</p><p>四把尺子量完，全部指向多Agent。</p><h3 id="3-3-场景三：长文档摘要生成（结论：伪多Agent-单Agent-分段策略）"><a href="#3-3-场景三：长文档摘要生成（结论：伪多Agent-单Agent-分段策略）" class="headerlink" title="3.3 场景三：长文档摘要生成（结论：伪多Agent / 单Agent + 分段策略）"></a>3.3 场景三：长文档摘要生成（结论：伪多Agent / 单Agent + 分段策略）</h3><p>需求：把一份 200 页的技术文档浓缩成 5 页摘要。</p><p>乍一看，200 页文档肯定塞不进上下文，应该用多Agent切分对吧？</p><p>但仔细想想：文档摘要是<strong>有全局连贯性要求</strong>的。你不能让 Agent-A 总结第 1-50 页、Agent-B 总结第 51-100 页，然后拼在一起——这样出来的摘要会有大量重复、遗漏和逻辑断裂。</p><p>更好的方案是<strong>单Agent + 分段迭代策略</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">第一轮：单Agent处理第1-50页，产出中间摘要A</span><br><span class="line">第二轮：单Agent带着摘要A处理第51-100页，产出中间摘要B</span><br><span class="line">第三轮：单Agent带着摘要B处理第101-150页，产出中间摘要C</span><br><span class="line">第四轮：单Agent带着摘要C处理第151-200页，产出最终摘要</span><br></pre></td></tr></table></figure><p>每一轮都保留了前序上下文的精华，保证了连贯性。这比多Agent并行切分效果好得多。</p><p><strong>教训</strong>：上下文超限不等于必须上多Agent。有时候<strong>串行分段 + 状态传递</strong>比并行切分更适合有连贯性要求的任务。</p><h3 id="3-4-场景四：多语言翻译流水线（结论：看情况）"><a href="#3-4-场景四：多语言翻译流水线（结论：看情况）" class="headerlink" title="3.4 场景四：多语言翻译流水线（结论：看情况）"></a>3.4 场景四：多语言翻译流水线（结论：看情况）</h3><p>需求：把一份技术文档翻译成英、日、韩三种语言。</p><p><strong>表面分析</strong>：三种语言互不依赖，可以并行——看起来应该用多Agent。</p><p><strong>深层分析</strong>：翻译的前置步骤（术语提取、风格统一）是共享的。如果三个 Agent 各自提取术语，出来的译文风格会不一致。</p><p><strong>最优方案</strong>：混合架构。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">阶段一（单Agent）：提取术语表 + 定义翻译风格指南</span><br><span class="line">阶段二（多Agent并行）：三个Agent分别翻译三种语言，共享术语表</span><br><span class="line">阶段三（单Agent）：审校三份译文的一致性</span><br></pre></td></tr></table></figure><p><strong>教训</strong>：很多任务不是纯粹的”该用”或”不该用”多Agent，而是<strong>在流水线的某些阶段用、某些阶段不用</strong>。混合架构才是最常见的生产形态。</p><hr><h2 id="四、怎么评估一个多Agent系统设计得好不好？"><a href="#四、怎么评估一个多Agent系统设计得好不好？" class="headerlink" title="四、怎么评估一个多Agent系统设计得好不好？"></a>四、怎么评估一个多Agent系统设计得好不好？</h2><p>选对了场景只是第一步。就算你决策正确，多Agent系统仍然可能设计得很烂。下面是一套评估框架，从五个维度给系统打分。</p><h3 id="4-1-评估维度一：任务分解的合理性"><a href="#4-1-评估维度一：任务分解的合理性" class="headerlink" title="4.1 评估维度一：任务分解的合理性"></a>4.1 评估维度一：任务分解的合理性</h3><p>这是最基础也最关键的维度。分解不合理，后面的一切都是空中楼阁。</p><p><strong>好的分解</strong>有三个特征：</p><div class="table-container"><table><thead><tr><th>特征</th><th>说明</th><th>反例</th></tr></thead><tbody><tr><td>子任务之间低耦合</td><td>每个子Agent能独立完成自己的工作，不需要等别人的结果</td><td>Agent-A 需要 Agent-B 的输出才能开始</td></tr><tr><td>子任务粒度适中</td><td>不太粗（一个Agent干不完）也不太细（拆太碎通信开销吃掉收益）</td><td>把”查数据库”拆成”建立连接””发送SQL””解析结果”三个Agent</td></tr><tr><td>子任务边界清晰</td><td>每个Agent的职责范围明确，不会出现两个Agent干同一件事</td><td>安全Agent和性能Agent都在扫描同一段代码的同一个函数</td></tr></tbody></table></div><p><strong>快速检验法</strong>：拿一张纸，把每个子Agent的任务写下来。如果你发现两个子Agent的任务描述有超过 30% 的重叠，分解就有问题。</p><h3 id="4-2-评估维度二：通信开销占比"><a href="#4-2-评估维度二：通信开销占比" class="headerlink" title="4.2 评估维度二：通信开销占比"></a>4.2 评估维度二：通信开销占比</h3><p>多Agent系统的总成本 = 计算成本 + 通信成本。</p><p>通信成本包括：</p><ul><li>主控 Agent 向子 Agent 传递任务描述的 Token</li><li>子 Agent 向主控 Agent 返回结果的 Token</li><li>主控 Agent 汇总所有结果的 Token</li><li>如果子 Agent 之间需要通信（不推荐），还有交叉通信的 Token</li></ul><p><strong>健康的系统</strong>：通信开销占总 Token 消耗的 20% 以内。</p><p><strong>有问题的系统</strong>：通信开销超过 40%。这意味着你在花大量 Token 让 Agent 之间”传纸条”，而不是干实事。</p><p>计算公式：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">通信开销占比 &#x3D; (任务传递Token + 结果回传Token + 汇总Token) &#x2F; 总Token消耗 × 100%</span><br></pre></td></tr></table></figure><p>如果你算出来这个比例很高，说明要么子任务拆得太碎（太多小Agent在传话），要么结果压缩做得不好（子Agent带回来的信息太冗余）。</p><h3 id="4-3-评估维度三：并行效率"><a href="#4-3-评估维度三：并行效率" class="headerlink" title="4.3 评估维度三：并行效率"></a>4.3 评估维度三：并行效率</h3><p>多Agent的核心价值是并行。如果并行效率低，用多Agent就没有意义。</p><p><strong>并行效率</strong>的定义：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">并行效率 &#x3D; 单Agent串行完成时间 &#x2F; 多Agent并行完成时间</span><br></pre></td></tr></table></figure><p>理论上，N 个 Agent 并行，效率应该接近 N。但实际上很难达到，因为：</p><ul><li>子 Agent 的任务量不均衡（最慢的那个决定了总时间）</li><li>主控 Agent 的规划和汇总有固定延迟</li><li>通信有网络开销</li></ul><div class="table-container"><table><thead><tr><th>并行效率</th><th>评价</th><th>建议</th></tr></thead><tbody><tr><td>&gt; 2.0x</td><td>优秀</td><td>多Agent方案值得投入</td></tr><tr><td>1.5x - 2.0x</td><td>一般</td><td>看场景，如果成本敏感可以考虑单Agent</td></tr><tr><td>&lt; 1.5x</td><td>差</td><td>多Agent带来的收益不够覆盖额外开销，回退单Agent</td></tr></tbody></table></div><p><strong>提升并行效率的关键</strong>：让每个子 Agent 的工作量尽量均衡。如果一个子 Agent 1 秒就跑完了，另一个要跑 10 秒，那你的并行效率就被拖后腿了。</p><h3 id="4-4-评估维度四：容错与可观测性"><a href="#4-4-评估维度四：容错与可观测性" class="headerlink" title="4.4 评估维度四：容错与可观测性"></a>4.4 评估维度四：容错与可观测性</h3><p>这是工程落地时最容易翻车的维度。</p><p><strong>容错能力</strong>评估清单：</p><ul><li>一个子 Agent 超时或失败了，系统会怎样？是直接整体失败，还是能降级处理？</li><li>子 Agent 返回了错误结果（幻觉），主控 Agent 能不能识别出来？</li><li>有没有设置最大重试次数？重试的 Token 成本有没有上限？</li></ul><p><strong>可观测性</strong>评估清单：</p><ul><li>出了问题，你能在日志里定位到是哪个 Agent 在哪个环节出错的吗？</li><li>每个 Agent 的输入输出有没有完整记录？</li><li>你能不能回放一次完整的执行过程来做 Debug？</li></ul><p>一个残酷的现实：大部分多Agent系统的日志都是一团浆糊。主控 Agent 调了 5 个子 Agent，每个子 Agent 又调了若干工具，出了问题你只能看到一个笼统的”执行失败”，根本不知道是哪一步在胡说八道。</p><p><strong>好的设计</strong>：给每个 Agent 执行分配一个唯一的 trace ID，所有日志都带上这个 ID，方便链路追踪。</p><h3 id="4-5-评估维度五：边际收益递减点"><a href="#4-5-评估维度五：边际收益递减点" class="headerlink" title="4.5 评估维度五：边际收益递减点"></a>4.5 评估维度五：边际收益递减点</h3><p>这是最需要工程直觉的一个维度。</p><p>多Agent系统的子 Agent 数量不是越多越好。存在一个<strong>边际收益递减点</strong>——超过这个点，增加 Agent 带来的并行收益小于增加的通信开销和协调成本。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">    收益</span><br><span class="line">     ↑</span><br><span class="line">     │        ╭───────── 收益曲线</span><br><span class="line">     │      ╱</span><br><span class="line">     │    ╱</span><br><span class="line">     │  ╱</span><br><span class="line">     │╱</span><br><span class="line">     ├──────────────────→ Agent数量</span><br><span class="line">     ↑</span><br><span class="line">边际收益递减点</span><br></pre></td></tr></table></figure><p><strong>经验值</strong>：在大部分场景下，子 Agent 数量控制在 3-8 个是比较合理的区间。超过 10 个，协调成本会急剧上升。</p><p><strong>怎么找到这个点？</strong> 最靠谱的方法是跑基准测试：</p><ol><li>从 2 个子 Agent 开始，记录执行时间和 Token 消耗</li><li>逐步增加到 3、4、5… 个</li><li>画出”Agent数量 vs 端到端延迟”和”Agent数量 vs Token成本”两条曲线</li><li>找到延迟不再显著下降、但 Token 成本还在上升的那个拐点</li></ol><p>那个拐点就是你的最优 Agent 数量。</p><hr><h2 id="五、一个完整的评估-Checklist"><a href="#五、一个完整的评估-Checklist" class="headerlink" title="五、一个完整的评估 Checklist"></a>五、一个完整的评估 Checklist</h2><p>把上面五个维度浓缩成一份可执行的检查清单。拿到任何一套多Agent系统设计方案，对照这张表打分：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">任务分解合理性                          □ 通过  □ 不通过</span><br><span class="line">├─ 子任务之间是否低耦合？               □ 是    □ 否</span><br><span class="line">├─ 粒度是否适中？                       □ 是    □ 否</span><br><span class="line">└─ 边界是否清晰？                       □ 是    □ 否</span><br><span class="line"></span><br><span class="line">通信开销占比                            □ 通过  □ 不通过</span><br><span class="line">├─ 通信Token占比 &lt; 20%？               □ 是    □ 否</span><br><span class="line">└─ 子Agent结果是否经过压缩？            □ 是    □ 否</span><br><span class="line"></span><br><span class="line">并行效率                                □ 通过  □ 不通过</span><br><span class="line">├─ 并行加速比 &gt; 1.5x？                 □ 是    □ 否</span><br><span class="line">└─ 子Agent工作量是否均衡？              □ 是    □ 否</span><br><span class="line"></span><br><span class="line">容错与可观测性                          □ 通过  □ 不通过</span><br><span class="line">├─ 子Agent失败是否可降级？              □ 是    □ 否</span><br><span class="line">├─ 每个Agent是否有trace ID？            □ 是    □ 否</span><br><span class="line">└─ 能否回放完整执行过程？               □ 是    □ 否</span><br><span class="line"></span><br><span class="line">边际收益                                □ 通过  □ 不通过</span><br><span class="line">├─ 子Agent数量是否在合理区间(3-8)？     □ 是    □ 否</span><br><span class="line">└─ 是否做过基准测试找最优解？           □ 是    □ 否</span><br></pre></td></tr></table></figure><p>5 个维度全部通过，这是一套合格的多Agent系统设计。有 1-2 个不通过，需要针对性优化。3 个以上不通过——回去重新考虑一下，这个场景是不是真的需要多Agent。</p><hr><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>回到文章开头的问题：什么场景下用多Agent系统？</p><p>答案不是”任务越复杂越好”，而是<strong>当你同时撞上了两个天花板时</strong>：</p><ol><li><strong>单一模型的上下文天花板</strong>——数据量大到一个模型吃不下</li><li><strong>串行执行的延迟天花板</strong>——子任务之间没有依赖，串行跑太慢</li></ol><p>只撞上第一个天花板，用单Agent + 分段策略就能解决。只撞上第二个天花板，用单Agent + 异步并发工具调用也能凑合。两个同时撞上，才是多Agent真正不可替代的场景。</p><p>而在评估一套多Agent系统设计时，不要只看架构图好不好看，要拿数据说话：通信开销占比多少？并行效率多少？边际收益递减点在哪？</p><p><strong>用工程的刚性去约束架构的浪漫，这才是做系统设计的正确姿势。</strong></p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;“任务越复杂越该用多Agent”——听起来好像没毛病，但这句话背后藏着一个巨大的陷阱。很多人一拍脑袋就上多Agent，结果延迟爆表、成本失控、日志一团浆糊，最后发现单Agent加个工具调用就能搞定。上一篇我们聊了 Multi-Agent 系统的设计</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="Multi-Agent" scheme="https://donehub.github.io/tags/Multi-Agent/"/>
    
  </entry>
  
  <entry>
    <title>Agent 记忆系统设计规范</title>
    <link href="https://donehub.github.io/2026/04/27/agent-memory-what-to-store/"/>
    <id>https://donehub.github.io/2026/04/27/agent-memory-what-to-store/</id>
    <published>2026-04-26T16:00:00.000Z</published>
    <updated>2026-04-27T09:56:03.740Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>如果你在构建 Agent，一定想过这个问题：怎么让 Agent 跨会话记住用户的偏好、项目的背景、之前犯过的错误？大部分人的做法是搞一个文件，把所有「需要记住的东西」往里塞。文件越来越大，token 成本越来越高，而且大部分内容跟当前对话根本没关系。Claude Code 的记忆系统给出了一个反直觉的设计原则：工程量最大的部分不是「怎么存」，也不是「怎么取」，而是「什么该存、什么不该存」。</p></blockquote><hr><h2 id="一、核心哲学：记忆是代码的补集"><a href="#一、核心哲学：记忆是代码的补集" class="headerlink" title="一、核心哲学：记忆是代码的补集"></a>一、核心哲学：记忆是代码的补集</h2><p>这套记忆系统的设计哲学可以用一句话概括：</p><blockquote><p><strong>记忆是代码的补集。</strong></p></blockquote><p>听起来简单，但这是整个设计里最有启发性的原则。具体来说：</p><div class="table-container"><table><thead><tr><th>维度</th><th>代码/工具擅长</th><th>记忆系统擅长</th></tr></thead><tbody><tr><td>时效性</td><td>实时查询、权威来源</td><td>跨会话沉淀、经验积累</td></tr><tr><td>可变性</td><td>随代码更新自动失效</td><td>需要主动维护、可能过时</td></tr><tr><td>粒度</td><td>精确到函数/文件</td><td>模糊的意图、偏好、上下文</td></tr></tbody></table></div><p><strong>该存的</strong>：全部是「关于人和上下文」的信息——人的偏好、纠正、动机、外部资源指针。这些藏在代码之外，不查记忆就无从得知。</p><p><strong>不该存的</strong>：全部是「关于代码和项目状态」的信息——代码是实时的、可查的、权威的。代码能回答的问题，不要让记忆来回答。</p><p>这个分界线一旦清晰，你会发现很多之前觉得「应该记住」的东西，其实根本不该存。</p><hr><h2 id="二、四种该存的记忆"><a href="#二、四种该存的记忆" class="headerlink" title="二、四种该存的记忆"></a>二、四种该存的记忆</h2><h3 id="2-1-用户记忆：记用户是谁"><a href="#2-1-用户记忆：记用户是谁" class="headerlink" title="2.1 用户记忆：记用户是谁"></a>2.1 用户记忆：记用户是谁</h3><p>用户记忆记录的是用户的角色、技术背景、工作习惯、知识水平。</p><p><strong>好的例子</strong>：</p><blockquote><p>「这个用户是数据科学家，目前在做日志系统的调研。」</p><p>「这个用户写了十年 Go，但第一次碰 React。」</p></blockquote><p>这类记忆的设计意图是让 Agent 能调整沟通方式和工作策略。面对一个资深后端工程师，Agent 不需要解释基础概念，可以直接用技术术语；面对一个初学者，Agent 需要更耐心地铺垫背景。</p><p><strong>关键约束</strong>：记忆的目的是「怎么更好地帮这个人」，不是「给这个人画像」。不要记录对用户的负面评价，也不要记录跟工作无关的个人信息。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">❌ 不要记：「这个用户脾气不好，经常发火」</span><br><span class="line">❌ 不要记：「这个用户喜欢喝咖啡」</span><br><span class="line">✅ 要记：「这个用户偏好简洁回复，不需要末尾总结」</span><br></pre></td></tr></table></figure><h3 id="2-2-反馈记忆：记纠正和肯定"><a href="#2-2-反馈记忆：记纠正和肯定" class="headerlink" title="2.2 反馈记忆：记纠正和肯定"></a>2.2 反馈记忆：记纠正和肯定</h3><p>这是四种记忆里设计最精细的一种。源码里对它有三个关键要求。</p><p><strong>要求一：规则 + 原因 + 适用场景</strong></p><p>每条反馈记忆必须包含三个部分：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">规则是什么 → Why → 什么时候该应用</span><br></pre></td></tr></table></figure><p>举个实际例子。用户说：</p><blockquote><p>「测试不要 mock 数据库，上个季度我们就是因为 mock 测试通过了但生产环境迁移失败才出的事故。」</p></blockquote><p>如果只记「不要 mock 数据库」，Agent 在所有测试里都不敢 mock，包括那些跟数据库迁移完全无关的单元测试。</p><p>但如果它知道原因是「mock 和生产环境行为不一致导致迁移失败」，它就能判断：集成测试不该 mock，但纯逻辑的单元测试 mock 是没问题的。</p><p><strong>记原因，是为了让 Agent 能在新场景下做判断，而不是机械地执行规则。</strong></p><p><strong>要求二：不要只记纠正，也要记肯定</strong></p><p>源码注释里说得很直白：</p><blockquote><p>如果你只记录用户说「不要这样做」的时刻，它只知道什么是错的，不知道什么是对的。时间长了，它会回避一切不确定的做法，变得畏手畏脚。</p></blockquote><p>但肯定信号比纠正信号更难捕捉。用户说「不要这样做」很明显，但用户说「对，就是这样」或者默默接受了一个不寻常的方案，需要主动注意这些肯定信号。</p><p><strong>例子</strong>：</p><blockquote><p>用户说：「对，这次用一个大 PR 是对的，拆开反而是无意义的工作量。」</p></blockquote><p>这条记忆的价值是：下次遇到类似的重构场景，Agent 知道这个用户倾向于合并提交，而不是拆成很多小 PR。这不是纠正，是一个被验证过的判断。</p><p><strong>要求三：区分个人偏好和项目规范</strong></p><blockquote><p>「不要在回复末尾加总结」→ 个人偏好，只对这个用户有效</p><p>「集成测试必须用真实数据库」→ 项目规范，对所有协作者有效</p></blockquote><p>源码里用 scope 来区分这两种。个人偏好存在私有目录，项目规范存在团队共享目录。</p><h3 id="2-3-项目记忆：记正在发生什么"><a href="#2-3-项目记忆：记正在发生什么" class="headerlink" title="2.3 项目记忆：记正在发生什么"></a>2.3 项目记忆：记正在发生什么</h3><p>项目记忆记录的是当前项目里正在发生的事：谁在做什么、为什么要做、截止日期是什么。</p><p><strong>例子</strong>：</p><blockquote><p>「本周四之后冻结所有非关键合并，移动端团队要切发布分支。」</p><p>「正在重写认证中间件，原因是法务团队指出旧的 token 存储方式不符合合规要求，所以做决策的时候要优先考虑合规性而不是技术优雅。」</p></blockquote><p><strong>关键规则：相对日期必须转换成绝对日期</strong></p><p>用户说「周四冻结」，记忆里存的是具体的年月日，比如「2026-03-07」。</p><p>为什么？因为记忆是跨会话的。如果存「周四」，下周再看这条记忆就不知道是哪个周四了。</p><p><strong>另一个特点：衰减得很快</strong></p><p>一个月前的项目状态大概率已经过时了。所以源码要求项目记忆必须记录「为什么」。即使事实过时了，背后的动机仍然有参考价值。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">事实过时：「正在重写认证中间件」→ 可能已经写完了</span><br><span class="line">动机仍有效：「合规要求优先于技术优雅」→ 这个决策原则长期有效</span><br></pre></td></tr></table></figure><h3 id="2-4-引用记忆：记外部资源在哪里"><a href="#2-4-引用记忆：记外部资源在哪里" class="headerlink" title="2.4 引用记忆：记外部资源在哪里"></a>2.4 引用记忆：记外部资源在哪里</h3><p>引用记忆记录的是外部资源的位置：bug 在哪个系统里追踪、监控面板的地址是什么、设计文档在哪个平台。</p><p><strong>例子</strong>：</p><blockquote><p>「流水线相关的 bug 都在 Linear 的 INGEST 项目里追踪。」</p><p>「API 延迟的监控面板在 grafana.internal/d/api-latency，值班的时候看这个。」</p></blockquote><p>这是四种里最简单的，但也是最实用的。它本质上是一个「去哪里找信息」的索引。</p><hr><h2 id="三、五种不该存的东西"><a href="#三、五种不该存的东西" class="headerlink" title="三、五种不该存的东西"></a>三、五种不该存的东西</h2><p>这部分才是整个设计里最有启发性的。很多人做记忆系统的第一件事，就是把不该存的东西全存了。</p><h3 id="3-1-代码模式、架构、文件路径、项目结构"><a href="#3-1-代码模式、架构、文件路径、项目结构" class="headerlink" title="3.1 代码模式、架构、文件路径、项目结构"></a>3.1 代码模式、架构、文件路径、项目结构</h3><p>这是最反直觉的。很多人觉得 Agent 应该记住「项目用了什么框架、目录怎么组织、哪个文件负责什么」。</p><p><strong>Claude Code 说：不要存这些。</strong></p><p>为什么？因为这些信息可以直接从代码里读出来。Agent 随时可以通过读代码和搜索来获取当前的项目结构。</p><p>把这些存进记忆有两个问题：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">问题一：浪费空间</span><br><span class="line">每次对话都要加载一堆本来可以实时查的信息</span><br><span class="line"></span><br><span class="line">问题二：过时风险</span><br><span class="line">代码改了但记忆没更新 → Agent 基于过时信息决策 → 很难发现</span><br></pre></td></tr></table></figure><p><strong>背后的原则</strong>：如果一个信息可以从当前项目状态推导出来，就不要存进记忆。记忆只存那些「看代码看不出来」的东西。</p><h3 id="3-2-版本管理历史"><a href="#3-2-版本管理历史" class="headerlink" title="3.2 版本管理历史"></a>3.2 版本管理历史</h3><p>谁改了什么、最近的提交记录——这些用版本管理工具查就行了。</p><p>Git 是实时的、权威的，不需要记忆来存一份可能过时的副本。</p><h3 id="3-3-调试方案和修复方法"><a href="#3-3-调试方案和修复方法" class="headerlink" title="3.3 调试方案和修复方法"></a>3.3 调试方案和修复方法</h3><p>修复已经在代码里了，提交信息里有上下文。存「怎么修的」没有意义，因为代码本身就是最好的参考。</p><h3 id="3-4-配置文件里已经写过的东西"><a href="#3-4-配置文件里已经写过的东西" class="headerlink" title="3.4 配置文件里已经写过的东西"></a>3.4 配置文件里已经写过的东西</h3><p>如果你的项目里有 CLAUDE.md 或其他配置文件已经定义了编码规范，记忆系统不需要再存一份。</p><p>重复存储不仅浪费空间，还会在两份内容不一致的时候制造混乱。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">配置文件说：「使用 pnpm」</span><br><span class="line">记忆文件说：「使用 npm」</span><br><span class="line">→ Agent 该听谁的？</span><br></pre></td></tr></table></figure><h3 id="3-5-临时性的任务细节"><a href="#3-5-临时性的任务细节" class="headerlink" title="3.5 临时性的任务细节"></a>3.5 临时性的任务细节</h3><p>当前正在做什么、对话里的中间状态——这些是短期的，属于当前会话的上下文，不该进入长期记忆。</p><hr><h2 id="四、一条特别重要的规则"><a href="#四、一条特别重要的规则" class="headerlink" title="四、一条特别重要的规则"></a>四、一条特别重要的规则</h2><p>即使用户明确要求你记住某些东西，如果它属于上面五类，也不该记。</p><p><strong>例子</strong>：</p><blockquote><p>用户：「记住这周的 PR 列表」</p></blockquote><p>Agent 不应该直接存 PR 列表，而应该反问：</p><blockquote><p>「这些 PR 里有什么让你意外的或者不明显的？那个部分才值得记。」</p></blockquote><p><strong>活动日志不是记忆，从活动中提炼出的洞察才是。</strong></p><p>用户要求存不该存的内容时，正确的做法是提炼价值点。PR 列表本身不该存，但如果某个 PR 的处理方式让用户觉得「这样做是对的」，那个判断才值得存成反馈记忆。</p><hr><h2 id="五、信息来源决策树"><a href="#五、信息来源决策树" class="headerlink" title="五、信息来源决策树"></a>五、信息来源决策树</h2><p>把上面的原则整理成一个判断流程：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">这条信息能从代码&#x2F;工具实时获取吗？</span><br><span class="line">         │</span><br><span class="line">         ├── 能 → 不存，实时查</span><br><span class="line">         │</span><br><span class="line">         └── 不能 → 属于哪种类型？</span><br><span class="line">                         │</span><br><span class="line">                         ├── 用户特征 → 用户记忆</span><br><span class="line">                         ├── 纠正&#x2F;肯定 → 反馈记忆</span><br><span class="line">                         ├── 项目动态 → 项目记忆</span><br><span class="line">                         └── 外部资源 → 引用记忆</span><br></pre></td></tr></table></figure><hr><h2 id="六、实践中的常见错误"><a href="#六、实践中的常见错误" class="headerlink" title="六、实践中的常见错误"></a>六、实践中的常见错误</h2><h3 id="错误一：把记忆当成项目文档"><a href="#错误一：把记忆当成项目文档" class="headerlink" title="错误一：把记忆当成项目文档"></a>错误一：把记忆当成项目文档</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">❌ 存储：「src&#x2F;auth 目录负责认证逻辑，包含 middleware.ts 和 token.ts」</span><br><span class="line">✅ 实时查：Glob + Read 工具</span><br></pre></td></tr></table></figure><h3 id="错误二：把记忆当成聊天记录"><a href="#错误二：把记忆当成聊天记录" class="headerlink" title="错误二：把记忆当成聊天记录"></a>错误二：把记忆当成聊天记录</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">❌ 存储：「用户昨天问怎么配置 Redis，我回答了...」</span><br><span class="line">✅ 不存：这是会话上下文，不是长期记忆</span><br></pre></td></tr></table></figure><h3 id="错误三：记了规则但没记原因"><a href="#错误三：记了规则但没记原因" class="headerlink" title="错误三：记了规则但没记原因"></a>错误三：记了规则但没记原因</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">❌ 存储：「不要用 forEach」</span><br><span class="line">✅ 存储：「避免在 async 函数里用 forEach，原因是 forEach 不等待 Promise 完成，</span><br><span class="line">         曾导致批量写入只执行了一半，适用场景是异步批量操作」</span><br></pre></td></tr></table></figure><h3 id="错误四：只记纠正不记肯定"><a href="#错误四：只记纠正不记肯定" class="headerlink" title="错误四：只记纠正不记肯定"></a>错误四：只记纠正不记肯定</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">❌ 只存：「不要在回复末尾加总结」</span><br><span class="line">✅ 也要存：「用户确认：重构时用一个大PR是对的，拆开反而增加工作量」</span><br></pre></td></tr></table></figure><hr><h2 id="七、记忆系统的质量检验清单"><a href="#七、记忆系统的质量检验清单" class="headerlink" title="七、记忆系统的质量检验清单"></a>七、记忆系统的质量检验清单</h2><p>在写入一条记忆之前，问自己这些问题：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">□ 是否存了代码可查的内容？→ 删除或反问用户</span><br><span class="line">□ 是否包含「为什么」？→ 补充动机</span><br><span class="line">□ 是否指定适用范围？→ 补充项目&#x2F;模块边界</span><br><span class="line">□ 相对时间是否转绝对时间？→ 如「明天」→「2026-03-16」</span><br><span class="line">□ 是否存在相似记忆？→ 合并去重</span><br><span class="line">□ 敏感信息是否过滤？→ 拒绝存储密码、密钥、PII</span><br></pre></td></tr></table></figure><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Claude Code 记忆系统的核心设计可以概括为一句话：<strong>少而精</strong>。</p><p>记忆的价值不在于数量，而在于每条记忆都能在关键时刻减少认知负担。</p><p>如果你在给自己的 Agent 做记忆系统，这个分类框架可以直接拿来用：</p><p><strong>四种该存</strong>：</p><ul><li>用户是谁（角色、背景、习惯）</li><li>用户纠正和肯定过什么（含规则、原因、适用场景）</li><li>项目背后的动机和时间线（相对日期→绝对日期）</li><li>外部资源在哪里（索引而非内容）</li></ul><p><strong>五种不该存</strong>：</p><ul><li>代码能告诉你的一切</li><li>版本历史能告诉你的一切</li><li>提交记录能告诉你的一切</li><li>配置文件已经说过的一切</li><li>临时性的中间状态</li></ul><p>这样做的好处是：你的记忆文件会非常精简，每一条都是高价值的、代码里找不到的信息。模型每次加载记忆的时候，看到的全是有用的东西，没有噪声。</p><hr><p><strong>相关文章</strong>：</p><ul><li><a href="/claude-code-memory-system/">Memory 系统：跨会话持久化知识库</a> — Claude Code 记忆系统的技术实现细节</li><li><a href="/categories/Claude-Code/">Claude Code 源码深度解析系列</a> — 更多 Claude Code 架构分析</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;如果你在构建 Agent，一定想过这个问题：怎么让 Agent 跨会话记住用户的偏好、项目的背景、之前犯过的错误？大部分人的做法是搞一个文件，把所有「需要记住的东西」往里塞。文件越来越大，token 成本越来越高，而且大部分内容跟当前对话根本没关系</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="AI Agent Memeory" scheme="https://donehub.github.io/tags/AI-Agent-Memeory/"/>
    
  </entry>
  
  <entry>
    <title>DeepSeek V4 技术解读</title>
    <link href="https://donehub.github.io/2026/04/24/deepseek-v4-technical-review/"/>
    <id>https://donehub.github.io/2026/04/24/deepseek-v4-technical-review/</id>
    <published>2026-04-24T08:30:00.000Z</published>
    <updated>2026-04-24T08:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>2026年4月24日，DeepSeek 正式发布了 V4 系列模型。这不是一次普通的版本迭代——它解决了一个困扰 AI 行业多年的根本问题：<strong>长上下文的效率瓶颈</strong>。</p><p>本文将深入解读 DeepSeek V4 的核心技术创新，帮助你理解这次发布为何值得关注。</p><hr><h2 id="一、模型规格：更大但不更贵"><a href="#一、模型规格：更大但不更贵" class="headerlink" title="一、模型规格：更大但不更贵"></a>一、模型规格：更大但不更贵</h2><p>DeepSeek V4 发布了两个版本：</p><div class="table-container"><table><thead><tr><th>模型</th><th>总参数量</th><th>激活参数量</th><th>上下文长度</th></tr></thead><tbody><tr><td><strong>DeepSeek-V4-Pro</strong></td><td>1.6T</td><td>49B</td><td>100万 tokens</td></tr><tr><td><strong>DeepSeek-V4-Flash</strong></td><td>284B</td><td>13B</td><td>100万 tokens</td></tr></tbody></table></div><p>对比上一代 V3.2（671B 总参数，37B 激活），V4-Pro 参数量翻了 2.4 倍，但激活参数仅增加 32%。更重要的是，<strong>两者都原生支持 100万 token 上下文</strong>——这是之前任何开源模型都做不到的。</p><h3 id="为什么”更大但不更贵”？"><a href="#为什么”更大但不更贵”？" class="headerlink" title="为什么”更大但不更贵”？"></a>为什么”更大但不更贵”？</h3><p>得益于 MoE（Mixture-of-Experts）架构，每次推理只激活一小部分参数。V4-Pro 的激活率仅为 <strong>3%</strong>（49B/1.6T），这意味着：</p><ul><li>推理成本接近一个 50B 参数的稠密模型</li><li>但拥有 1.6T 参数的知识容量和表达能力</li></ul><p>这是 DeepSeek 从 V2 开始就坚持的技术路线，V4 把这个策略推向了新高度。</p><hr><h2 id="二、核心架构创新：打破-O-n²-的魔咒"><a href="#二、核心架构创新：打破-O-n²-的魔咒" class="headerlink" title="二、核心架构创新：打破 O(n²) 的魔咒"></a>二、核心架构创新：打破 O(n²) 的魔咒</h2><p>Transformer 的标准注意力机制计算复杂度是 O(n²)——序列长度翻倍，计算量翻四倍。当上下文达到百万级别时，这变成了不可承受之重。</p><p>DeepSeek V4 用<strong>混合注意力架构</strong>彻底解决了这个问题。</p><h3 id="2-1-CSA（Compressed-Sparse-Attention）"><a href="#2-1-CSA（Compressed-Sparse-Attention）" class="headerlink" title="2.1 CSA（Compressed Sparse Attention）"></a>2.1 CSA（Compressed Sparse Attention）</h3><p>CSA 的核心思路是：<strong>压缩 + 稀疏选择</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">原始序列：n 个 token</span><br><span class="line">     ↓ 压缩（每 m 个 token 合成一个 KV entry）</span><br><span class="line">压缩序列：n&#x2F;m 个 compressed KV entry</span><br><span class="line">     ↓ 稀疏选择（Lightning Indexer 选 top-k）</span><br><span class="line">参与计算的：k 个 compressed KV entry</span><br></pre></td></tr></table></figure><p>具体流程：</p><ol><li><strong>KV Cache 压缩</strong>：将每 m 个 token 的 KV entry 通过加权聚合压缩成一个条目，序列长度降到 1/m</li><li><strong>Lightning Indexer</strong>：为每个 query token 生成 indexer queries，与压缩后的 KV 偂相似度计算，选出 top-k 个最相关的压缩块</li><li><strong>Core Attention</strong>：只在选出的 k 个压缩块上做完整的 attention 计算</li></ol><p>关键参数（V4-Pro）：</p><ul><li>压缩率 m = 4（每 4 个 token 压缩成 1 个）</li><li>Indexer head 数 = 64，head 维度 = 128</li><li>Top-k = 1024（每个 query 只关注 1024 个压缩块）</li></ul><h3 id="2-2-HCA（Heavily-Compressed-Attention）"><a href="#2-2-HCA（Heavily-Compressed-Attention）" class="headerlink" title="2.2 HCA（Heavily Compressed Attention）"></a>2.2 HCA（Heavily Compressed Attention）</h3><p>HCA 是更激进的压缩策略，用于处理”不需要精细关注的历史信息”：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">压缩率 m&#39; &#x3D; 128（每 128 个 token 合成一个 KV entry）</span><br><span class="line">     ↓</span><br><span class="line">直接对压缩后的 KV 做完整 attention（不做稀疏选择）</span><br></pre></td></tr></table></figure><p>HCA 的哲学是：<strong>远处的信息可以”模糊处理”，近处的信息才需要精细关注</strong>。</p><h3 id="2-3-混合架构设计"><a href="#2-3-混合架构设计" class="headerlink" title="2.3 混合架构设计"></a>2.3 混合架构设计</h3><p>V4 不是全用 CSA 或全用 HCA，而是<strong>交替使用</strong>：</p><ul><li><strong>前 2 层</strong>：纯滑动窗口 attention（保留近期信息的精细度）</li><li><strong>后续层</strong>：CSA 和 HCA 交替，形成”粗细结合”的信息处理</li></ul><p>这种设计让模型既能高效处理长上下文，又能保持对关键信息的精确检索能力。</p><h3 id="2-4-效率提升有多夸张？"><a href="#2-4-效率提升有多夸张？" class="headerlink" title="2.4 效率提升有多夸张？"></a>2.4 效率提升有多夸张？</h3><p>官方给出了硬核数据（100万 token 上下文场景）：</p><div class="table-container"><table><thead><tr><th>指标</th><th>V4-Pro vs V3.2</th><th>V4-Flash vs V3.2</th></tr></thead><tbody><tr><td>单 token FLOPs</td><td><strong>27%</strong>（节省 3.7×）</td><td><strong>10%</strong>（节省 10×）</td></tr><tr><td>KV Cache 大小</td><td><strong>10%</strong>（节省 9.5×）</td><td><strong>7%</strong>（节省 13.7×）</td></tr></tbody></table></div><p>这意味着：以前跑不起的百万级上下文任务，现在<strong>可以在单卡上跑了</strong>。</p><hr><h2 id="三、mHC：残差连接的”数学升级版”"><a href="#三、mHC：残差连接的”数学升级版”" class="headerlink" title="三、mHC：残差连接的”数学升级版”"></a>三、mHC：残差连接的”数学升级版”</h2><p>残差连接 <code>x + F(x)</code> 是 Transformer 的基石，但深层堆叠时会遇到问题：</p><ul><li>信号可能逐层放大 → 数值爆炸</li><li>信号可能逐层衰减 →梯度消失</li></ul><p>DeepSeek V4 引入了 <strong>Manifold-Constrained Hyper-Connections (mHC)</strong>，用数学约束解决这个问题。</p><h3 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h3><p>传统残差连接：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">X_next &#x3D; X + F(X)  &#x2F;&#x2F; 简单加法</span><br></pre></td></tr></table></figure></p><p>mHC：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">X_next &#x3D; B·X + C·F(A·X)  &#x2F;&#x2F; A、B、C 是线性映射矩阵</span><br><span class="line">         ↑</span><br><span class="line">      B 约束在双随机矩阵流形上（行和&#x3D;1，列和&#x3D;1，元素≥0）</span><br></pre></td></tr></table></figure></p><p>关键约束：<strong>B 的谱范数 ≤ 1</strong>，这意味着信号传播是”非膨胀的”，不会爆炸。</p><h3 id="为什么叫”流形约束”？"><a href="#为什么叫”流形约束”？" class="headerlink" title="为什么叫”流形约束”？"></a>为什么叫”流形约束”？</h3><p>双随机矩阵构成的空间是一个<strong>流形（Manifold）</strong>——Birkhoff Polytope。mHC 通过 Sinkhorn-Knopp 算法，把矩阵 B 投影到这个流形上：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 对 B 取 exponential（保证正元素）</span><br><span class="line">2. 迭代做行归一化、列归一化</span><br><span class="line">3. 收敛到一个双随机矩阵</span><br></pre></td></tr></table></figure><p>这套数学确保了深层堆叠时的稳定性，同时保留了模型的表达能力。</p><hr><h2 id="四、Muon-优化器：万亿参数训练的新配方"><a href="#四、Muon-优化器：万亿参数训练的新配方" class="headerlink" title="四、Muon 优化器：万亿参数训练的新配方"></a>四、Muon 优化器：万亿参数训练的新配方</h2><p>训练万亿参数模型，AdamW 已经不够稳了。V4 引入了 <strong>Muon</strong> 优化器。</p><h3 id="核心算法"><a href="#核心算法" class="headerlink" title="核心算法"></a>核心算法</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">G &#x3D; gradient</span><br><span class="line">M &#x3D; momentum_buffer</span><br><span class="line">M &#x3D; μ·M + G  &#x2F;&#x2F; 动量累积</span><br><span class="line">O &#x3D; HybridNewtonSchulz(μ·M + G)  &#x2F;&#x2F; Nesterov trick + 正交化</span><br><span class="line">W &#x3D; W·(1 - ηλ) - η·O  &#x2F;&#x2F; weight decay + update</span><br></pre></td></tr></table></figure><p>关键步骤是 <strong>Hybrid Newton-Schulz 迭代</strong>，把梯度矩阵正交化：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 10 步迭代，分两阶段</span></span><br><span class="line"><span class="comment"># Stage 1（前 8 步）：快速收敛</span></span><br><span class="line">M_k = <span class="number">3.4445</span>·M_&#123;k<span class="number">-1</span>&#125; - <span class="number">4.7750</span>·(M·M^T)·M + <span class="number">2.0315</span>·(M·M^T)^<span class="number">2</span>·M</span><br><span class="line"></span><br><span class="line"><span class="comment"># Stage 2（后 2 步）：精确定位到正交矩阵</span></span><br><span class="line">M_k = <span class="number">2</span>·M_&#123;k<span class="number">-1</span>&#125; - <span class="number">1.5</span>·(M·M^T)·M + <span class="number">0.5</span>·(M·M^T)^<span class="number">2</span>·M</span><br></pre></td></tr></table></figure><p>正交化的好处：</p><ul><li>避免”跑偏”——梯度方向更明确</li><li>避免”数值爆炸”——矩阵谱范数被约束</li><li>收敛更快——不需要 Adam 的二阶矩估计</li></ul><h3 id="配合稳定性技术"><a href="#配合稳定性技术" class="headerlink" title="配合稳定性技术"></a>配合稳定性技术</h3><p>V4 还用了两招来防止 loss spike：</p><ol><li><strong>Anticipatory Routing</strong>：路由决策用”历史参数”而非”当前参数”，打破 MoE 路由的恶性循环</li><li><strong>SwiGLU Clamping</strong>：把 SwiGLU 的线性分量 clamp 到 [-10, 10]，直接压制异常值</li></ol><hr><h2 id="五、FP4-量化感知训练：天生适应低精度"><a href="#五、FP4-量化感知训练：天生适应低精度" class="headerlink" title="五、FP4 量化感知训练：天生适应低精度"></a>五、FP4 量化感知训练：天生适应低精度</h2><p>以往的量化是”训练后补救”——模型在高精度下训练，推理时强行降精度，性能必然下降。</p><p>V4 的创新：<strong>训练时就让模型适应 FP4</strong>。</p><h3 id="应用范围"><a href="#应用范围" class="headerlink" title="应用范围"></a>应用范围</h3><ul><li><strong>MoE 专家权重</strong>：占模型大部分参数，FP4 压缩节省大量显存</li><li><strong>QK 路径</strong>（Lightning Indexer 的 indexer 部分）：长上下文检索的核心计算，FP4 加速</li></ul><h3 id="关键技术点"><a href="#关键技术点" class="headerlink" title="关键技术点"></a>关键技术点</h3><p><strong>FP4 → FP8 的无损反量化</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">FP4 (E2M1) → FP8 (E4M3)</span><br><span class="line">          ↑</span><br><span class="line">FP8 多 2 个 exponent bit，动态范围更大</span><br><span class="line">只要 block 内的 scale factor 差异不超过阈值，信息完全保留</span><br></pre></td></tr></table></figure><p>这意味着：</p><ul><li>训练时用 FP8 做计算（模拟 FP4）</li><li>推理时直接用 FP4 权重，零性能损失</li><li>整个 pipeline 可以复用现有的 FP8 训练框架</li></ul><hr><h2 id="六、训练基础设施：工程硬核"><a href="#六、训练基础设施：工程硬核" class="headerlink" title="六、训练基础设施：工程硬核"></a>六、训练基础设施：工程硬核</h2><p>V4 的基础设施投入展现了”长期主义”的工程思维。</p><h3 id="6-1-TileLang：Kernel-开发的-DSL"><a href="#6-1-TileLang：Kernel-开发的-DSL" class="headerlink" title="6.1 TileLang：Kernel 开发的 DSL"></a>6.1 TileLang：Kernel 开发的 DSL</h3><p>传统 CUDA Kernel 开发效率低、难迭代。V4 用 <strong>TileLang</strong> 这个 DSL：</p><ul><li>用声明式语法描述 Kernel 逻辑</li><li>Z3 SMT Solver 做形式化分析（证明正确性）</li><li>自动生成高性能 CUDA 代码</li></ul><p>开发效率 + 运行效率，两者兼得。</p><h3 id="6-2-确定性训练"><a href="#6-2-确定性训练" class="headerlink" title="6.2 确定性训练"></a>6.2 确定性训练</h3><p>V4 的 Kernel 全程<strong>批不变（Batch-Invariant）</strong>：</p><ul><li>同一 token 无论在 batch 哪个位置，输出 bitwise 一致</li><li>用特殊设计避免了原子加法带来的不确定性</li><li>训练过程可复现，调试有据可查</li></ul><p>这对大规模训练调试、定位问题至关重要。</p><h3 id="6-3-MoE-EP-的细粒度重叠"><a href="#6-3-MoE-EP-的细粒度重叠" class="headerlink" title="6.3 MoE EP 的细粒度重叠"></a>6.3 MoE EP 的细粒度重叠</h3><p>Expert Parallelism 的通信开销大。V4 把 MoE 层拆成 <strong>4 个阶段</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Dispatch (通信) → Linear-1 (计算) → Activation → Linear-2 (计算) → Combine (通信)</span><br></pre></td></tr></table></figure><p>关键洞察：<strong>计算时间 &gt; 通信时间</strong>，所以通信可以被计算掩盖。</p><p>V4 把专家分成”wave”，每个 wave 的通信和计算流水线化，实现 <strong>1.5-1.96× 加速</strong>。</p><hr><h2 id="七、性能基准：开源模型的新标杆"><a href="#七、性能基准：开源模型的新标杆" class="headerlink" title="七、性能基准：开源模型的新标杆"></a>七、性能基准：开源模型的新标杆</h2><h3 id="知识任务"><a href="#知识任务" class="headerlink" title="知识任务"></a>知识任务</h3><div class="table-container"><table><thead><tr><th>Benchmark</th><th>V4-Pro-Max</th><th>K2.6</th><th>GLM-5.1</th><th>Gemini 3.1 Pro</th></tr></thead><tbody><tr><td>SimpleQA Verified</td><td><strong>57.9</strong></td><td>36.9</td><td>38.1</td><td>75.6</td></tr><tr><td>Chinese-SimpleQA</td><td><strong>84.4</strong></td><td>75.9</td><td>75.0</td><td>85.9</td></tr></tbody></table></div><p>V4-Pro-Max 在知识任务上<strong>领先开源对手 20+ 百分点</strong>，但距离 Gemini 3.1 Pro 还有一段差距。</p><h3 id="Agent-能力：开源最佳"><a href="#Agent-能力：开源最佳" class="headerlink" title="Agent 能力：开源最佳"></a>Agent 能力：开源最佳</h3><p>这是 V4 最重要的能力跃升之一。官方披露：</p><ul><li><strong>Agentic Coding</strong>：V4-Pro 达到当前开源模型最佳水平</li><li><strong>内部实测</strong>：已成为 DeepSeek 公司内部员工使用的 Agentic Coding 首选模型</li><li><strong>体验对比</strong>：优于 Claude Sonnet 4.5，交付质量接近 Claude Opus 4.6 非思考模式</li></ul><p>V4 针对 <strong>Claude Code、OpenClaw、OpenCode、CodeBuddy</strong> 等主流 Agent 产品进行了专项适配优化，在代码任务、文档生成等场景表现显著提升。</p><h3 id="推理与代码"><a href="#推理与代码" class="headerlink" title="推理与代码"></a>推理与代码</h3><div class="table-container"><table><thead><tr><th>Benchmark</th><th>V4-Pro-Max</th><th>GPT-5.4</th><th>Gemini 3.1 Pro</th></tr></thead><tbody><tr><td>Codeforces Rating</td><td><strong>3206</strong></td><td>3168</td><td>3052</td></tr><tr><td>Apex Shortlist</td><td><strong>90.2</strong></td><td>78.1</td><td>89.1</td></tr></tbody></table></div><p><strong>这是开源模型首次在代码竞赛上追平闭源模型</strong>。V4-Pro-Max 在 Codeforces 排名第 23 位（人类选手中）。</p><h3 id="长上下文"><a href="#长上下文" class="headerlink" title="长上下文"></a>长上下文</h3><div class="table-container"><table><thead><tr><th>Benchmark</th><th>V4-Pro-Max</th><th>Claude Opus 4.6</th><th>Gemini 3.1 Pro</th></tr></thead><tbody><tr><td>MRCR 1M (MMR)</td><td>83.5</td><td><strong>92.9</strong></td><td>76.3</td></tr><tr><td>CorpusQA 1M</td><td><strong>62.0</strong></td><td>71.7</td><td>53.8</td></tr></tbody></table></div><p>V4-Pro 在真实场景的 CorpusQA 上超越 Gemini 3.1 Pro，在 MRCR 上接近 Claude Opus 4.6。</p><hr><h2 id="八、V4-Flash：经济高效的选择"><a href="#八、V4-Flash：经济高效的选择" class="headerlink" title="八、V4-Flash：经济高效的选择"></a>八、V4-Flash：经济高效的选择</h2><p>V4-Flash 是一个重要的补充版本，让不同需求的用户都能找到合适的方案。</p><h3 id="与-V4-Pro-的对比"><a href="#与-V4-Pro-的对比" class="headerlink" title="与 V4-Pro 的对比"></a>与 V4-Pro 的对比</h3><div class="table-container"><table><thead><tr><th>维度</th><th>V4-Flash</th><th>V4-Pro</th></tr></thead><tbody><tr><td>激活参数</td><td>13B</td><td>49B</td></tr><tr><td>推理速度</td><td>更快</td><td>较慢</td></tr><tr><td>API 成本</td><td>更低</td><td>较高</td></tr><tr><td>世界知识</td><td>稍逊</td><td>大幅领先开源</td></tr><tr><td>推理能力</td><td>接近 Pro</td><td>开源最佳</td></tr><tr><td>Agent 简单任务</td><td>旗鼓相当</td><td>更优</td></tr><tr><td>Agent 高难度任务</td><td>有差距</td><td>最佳</td></tr></tbody></table></div><p><strong>适用场景</strong>：</p><ul><li><strong>V4-Flash</strong>：日常对话、简单代码任务、成本敏感场景</li><li><strong>V4-Pro</strong>：复杂 Agent 任务、深度推理、高质量输出需求</li></ul><hr><h2 id="九、三种推理模式：灵活的推理成本"><a href="#九、三种推理模式：灵活的推理成本" class="headerlink" title="九、三种推理模式：灵活的推理成本"></a>九、三种推理模式：灵活的推理成本</h2><p>V4 支持三种推理模式，让用户按需求选择成本：</p><div class="table-container"><table><thead><tr><th>模式</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>Non-Think</strong></td><td>快速直觉响应，无 thinking tokens</td><td>日常对话、低风险决策</td></tr><tr><td><strong>Think</strong></td><td>逻辑分析，中等 thinking budget</td><td>复杂问题、规划任务</td></tr><tr><td><strong>Think Max</strong></td><td>极限推理，长 thinking budget</td><td>数学证明、高难度任务</td></tr></tbody></table></div><p>Think Max 模式会在系统 prompt 里注入特殊指令：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Reasoning Effort: Absolute maximum with no shortcuts permitted.</span><br><span class="line">You MUST be very thorough in your thinking...</span><br></pre></td></tr></table></figure><p>这让模型”把推理推到极限”，在 HLE、IMO 等高难度任务上表现最优。</p><hr><h2 id="十、API-使用指南"><a href="#十、API-使用指南" class="headerlink" title="十、API 使用指南"></a>十、API 使用指南</h2><h3 id="模型调用"><a href="#模型调用" class="headerlink" title="模型调用"></a>模型调用</h3><p>DeepSeek API 已同步上线 V4-Pro 与 V4-Flash，支持 OpenAI ChatCompletions 接口与 Anthropic 接口：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># OpenAI 格式</span></span><br><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> OpenAI</span><br><span class="line"></span><br><span class="line">client = OpenAI(</span><br><span class="line">    api_key=<span class="string">"your-api-key"</span>,</span><br><span class="line">    base_url=<span class="string">"https://api.deepseek.com"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">response = client.chat.completions.create(</span><br><span class="line">    model=<span class="string">"deepseek-v4-pro"</span>,  <span class="comment"># 或 deepseek-v4-flash</span></span><br><span class="line">    messages=[&#123;<span class="string">"role"</span>: <span class="string">"user"</span>, <span class="string">"content"</span>: <span class="string">"你好"</span>&#125;]</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h3 id="关键参数"><a href="#关键参数" class="headerlink" title="关键参数"></a>关键参数</h3><div class="table-container"><table><thead><tr><th>参数</th><th>说明</th></tr></thead><tbody><tr><td><code>model</code></td><td><code>deepseek-v4-pro</code> 或 <code>deepseek-v4-flash</code></td></tr><tr><td><code>max_tokens</code></td><td>最大输出长度，默认 8K</td></tr><tr><td><code>reasoning_effort</code></td><td>思考强度：<code>high</code> 或 <code>max</code>（仅思考模式）</td></tr></tbody></table></div><h3 id="思考模式"><a href="#思考模式" class="headerlink" title="思考模式"></a>思考模式</h3><p>对于复杂的 Agent 场景，建议使用思考模式并设置强度为 <code>max</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">response = client.chat.completions.create(</span><br><span class="line">    model=<span class="string">"deepseek-v4-pro"</span>,</span><br><span class="line">    messages=[&#123;<span class="string">"role"</span>: <span class="string">"user"</span>, <span class="string">"content"</span>: <span class="string">"复杂任务..."</span>&#125;],</span><br><span class="line">    reasoning_effort=<span class="string">"max"</span>  <span class="comment"># 极限推理</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><h3 id="⚠️-重要提示"><a href="#⚠️-重要提示" class="headerlink" title="⚠️ 重要提示"></a>⚠️ 重要提示</h3><p>旧 API 模型名 <code>deepseek-chat</code> 和 <code>deepseek-reasoner</code> 将于 <strong>2026年7月24日</strong> 停止使用：</p><ul><li>当前阶段 <code>deepseek-chat</code> → 指向 V4-Flash 非思考模式</li><li>当前阶段 <code>deepseek-reasoner</code> → 指向 V4-Flash 思考模式</li></ul><p>请尽快迁移到新的模型名称。</p><hr><h2 id="十一、开源与本地部署"><a href="#十一、开源与本地部署" class="headerlink" title="十一、开源与本地部署"></a>十一、开源与本地部署</h2><h3 id="权重下载"><a href="#权重下载" class="headerlink" title="权重下载"></a>权重下载</h3><div class="table-container"><table><thead><tr><th>平台</th><th>链接</th></tr></thead><tbody><tr><td>HuggingFace</td><td><a href="https://huggingface.co/collections/deepseek-ai/deepseek-v4" target="_blank" rel="noopener">https://huggingface.co/collections/deepseek-ai/deepseek-v4</a></td></tr><tr><td>ModelScope</td><td><a href="https://modelscope.cn/collections/deepseek-ai/DeepSeek-V4" target="_blank" rel="noopener">https://modelscope.cn/collections/deepseek-ai/DeepSeek-V4</a></td></tr></tbody></table></div><h3 id="本地部署建议"><a href="#本地部署建议" class="headerlink" title="本地部署建议"></a>本地部署建议</h3><p>由于 V4-Pro 参数量达 1.6T，本地部署需要：</p><ul><li><strong>多卡推理</strong>：至少 8× A100 80GB 或同等显存</li><li><strong>量化推理</strong>：FP4 量化后可显著降低显存需求</li><li><strong>V4-Flash</strong>：单卡 A100 80GB 可运行</li></ul><h3 id="技术报告"><a href="#技术报告" class="headerlink" title="技术报告"></a>技术报告</h3><p>完整技术细节请参考官方技术报告：</p><ul><li><a href="https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf" target="_blank" rel="noopener">DeepSeek V4 技术报告（PDF）</a></li></ul><hr><h2 id="十二、行业启示：V4-带来的新范式"><a href="#十二、行业启示：V4-带来的新范式" class="headerlink" title="十二、行业启示：V4 带来的新范式"></a>十二、行业启示：V4 带来的新范式</h2><h3 id="12-1-长上下文不再是奢侈品"><a href="#12-1-长上下文不再是奢侈品" class="headerlink" title="12.1 长上下文不再是奢侈品"></a>12.1 长上下文不再是奢侈品</h3><p>以前，百万级上下文是”理论上可行但经济上不行”。V4 把成本降到 <strong>原来的 10-30%</strong>，让以下场景变得可行：</p><ul><li><strong>Test-time Scaling</strong>：推理阶段可以长时间思考，不受上下文限制</li><li><strong>长 horizon Agent</strong>：复杂多轮任务（如软件工程流水线）有足够”记忆空间”</li><li><strong>在线学习</strong>：持续吸收新信息，无需全量重训练</li></ul><h3 id="12-2-开源-vs-闭源的格局变化"><a href="#12-2-开源-vs-闭源的格局变化" class="headerlink" title="12.2 开源 vs 闭源的格局变化"></a>12.2 开源 vs 闭源的格局变化</h3><p>V4 是一个信号：<strong>开源模型不仅追上了能力，还追上了效率性价比</strong>。</p><ul><li>V4-Flash 用 13B 激活参数，就能达到接近 GPT-5.2 的推理水平</li><li>在代码任务上，开源首次追平闭源</li></ul><p>这意味着闭源模型的”护城河”正在缩小。</p><h3 id="12-3-架构创新的长期价值"><a href="#12-3-架构创新的长期价值" class="headerlink" title="12.3 架构创新的长期价值"></a>12.3 架构创新的长期价值</h3><p>V4 的创新不是”刷榜技巧”，而是<strong>架构层面的根本改进</strong>：</p><ul><li>CSA/HCA 解决了 Transformer 的 O(n²) 瓶颈</li><li>mHC 让残差连接更稳定、可堆叠更深</li><li>Muon 优化器可能成为万亿参数训练的新标配</li></ul><p>这些创新会启发更多研究，推动整个行业向前。</p><hr><h2 id="十三、局限与展望"><a href="#十三、局限与展望" class="headerlink" title="十三、局限与展望"></a>十三、局限与展望</h2><p>官方坦承了几个局限：</p><ol><li><strong>架构相对复杂</strong>：为了降低风险，保留了 V3 的很多验证过的组件，未来会精简</li><li><strong>训练稳定性原理未完全理解</strong>：Anticipatory Routing 和 SwiGLU Clamping 有效，但数学原理还在探索</li><li><strong>多模态尚未集成</strong>：未来版本会加入视觉能力</li></ol><p>展望方向：</p><ul><li>进一步的稀疏化探索（如稀疏 embedding）</li><li>低延迟架构优化（让长上下文交互更流畅）</li><li>长 horizon Agent 的深度优化</li></ul><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>DeepSeek V4 的意义不在于某个具体指标的提升，而在于它<strong>解决了长上下文效率这个根本问题</strong>。</p><p>通过 CSA/HCA 混合注意力、mHC 残差升级、Muon 优化器、FP4 量化训练等一系列创新，V4 让百万级上下文从”理论上可行”变成”经济上可行”。</p><p>这为 AI 的下一阶段——更深的 test-time scaling、更长的 Agent 任务、更灵活的在线学习——铺好了基础设施。</p><p>开源模型第一次在效率和能力的综合维度上，追上了闭源前沿。这是整个行业值得关注的里程碑。</p><hr><p><strong>参考资源</strong>：</p><ul><li><a href="https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf" target="_blank" rel="noopener">DeepSeek V4 技术报告（PDF）</a></li><li><a href="https://huggingface.co/collections/deepseek-ai/deepseek-v4" target="_blank" rel="noopener">模型权重（HuggingFace）</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;2026年4月24日，DeepSeek 正式发布了 V4 系列模型。这不是一次普通的版本迭代——它解决了一个困扰 AI 行业多年的根本问题：</summary>
      
    
    
    
    <category term="DeepSeek" scheme="https://donehub.github.io/categories/DeepSeek/"/>
    
    
    <category term="AI" scheme="https://donehub.github.io/tags/AI/"/>
    
  </entry>
  
  <entry>
    <title>奇技淫巧：Java / Python 应用调用阿里百炼 Coding Plan 服务</title>
    <link href="https://donehub.github.io/2026/04/19/bailian_coding_plan_usage_java_or_python/"/>
    <id>https://donehub.github.io/2026/04/19/bailian_coding_plan_usage_java_or_python/</id>
    <published>2026-04-18T16:00:00.000Z</published>
    <updated>2026-04-19T01:37:15.224Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>阿里云百炼 Coding Plan 官方宣称”仅限编程工具使用”，但实际上其 endpoint 基于 OpenAI 兼容协议，Java/Python 应用完全可以调用。本文分享如何用 LangChain4j 和 OpenAI SDK 突破这一限制，直接消耗 Coding Plan 额度。</p></blockquote><hr><h2 id="一、背景：一个被”误解”的服务"><a href="#一、背景：一个被”误解”的服务" class="headerlink" title="一、背景：一个被”误解”的服务"></a>一、背景：一个被”误解”的服务</h2><p>阿里云百炼的 <strong>Coding Plan</strong> 是一项面向 AI 编程助手的服务套餐，提供专门的模型调用额度。官方客服的说法是：</p><blockquote><p>“Coding Plan 的专属 API Key（格式为 <code>sk-sp-xxxxx</code>）仅限在支持的编程工具（如 Claude Code、OpenClaw 等）中使用，不能用于 Java 应用直接调用大模型。若您的 Java 应用需要调用百炼大模型，请使用百炼通用 API Key（格式为 <code>sk-xxxxx</code>），该 Key 支持调用包括 Coding 模型在内的所有百炼模型，并按量计费。”</p></blockquote><p>这意味着如果你想在 Java 应用中使用百炼大模型，需要：</p><ol><li>额外开通百炼通用 API Key（格式为 <code>sk-xxxxx</code>）</li><li>按量付费，产生额外费用</li></ol><p><strong>但实际上，Coding Plan 的额度完全可以在 Java/Python 应用中使用！</strong> 本文将分享这个”奇技淫巧”。</p><hr><h2 id="二、问题发现：为什么-Coding-Plan-Key-在-Java-中”失效”？"><a href="#二、问题发现：为什么-Coding-Plan-Key-在-Java-中”失效”？" class="headerlink" title="二、问题发现：为什么 Coding Plan Key 在 Java 中”失效”？"></a>二、问题发现：为什么 Coding Plan Key 在 Java 中”失效”？</h2><h3 id="2-1-错误的调用方式"><a href="#2-1-错误的调用方式" class="headerlink" title="2.1 错误的调用方式"></a>2.1 错误的调用方式</h3><p>很多开发者（包括我）最初使用阿里云官方的 <code>dashscope-sdk-java</code> 调用百炼：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// pom.xml</span></span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.alibaba&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;dashscope-sdk-java&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.22.15&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Java 代码</span></span><br><span class="line">Generation gen = <span class="keyword">new</span> Generation();</span><br><span class="line">GenerationParam param = GenerationParam.builder()</span><br><span class="line">    .apiKey(<span class="string">"sk-sp-xxxxx"</span>)  <span class="comment">// Coding Plan API Key</span></span><br><span class="line">    .model(<span class="string">"qwen-plus"</span>)</span><br><span class="line">    .messages(messages)</span><br><span class="line">    .build();</span><br><span class="line">GenerationResult result = gen.call(param);</span><br></pre></td></tr></table></figure><p><strong>结果：API 返回 InvalidApiKey 错误，或者即使调通了，消耗的是通用额度而非 Coding Plan 额度！</strong></p><h3 id="2-2-根本原因分析"><a href="#2-2-根本原因分析" class="headerlink" title="2.2 根本原因分析"></a>2.2 根本原因分析</h3><p>Coding Plan 的 API Key 使用的是 <strong>OpenAI 兼容协议</strong>，endpoint 地址不同于通用百炼服务：</p><div class="table-container"><table><thead><tr><th>服务类型</th><th>API Key 格式</th><th>Endpoint</th><th>模型名称</th></tr></thead><tbody><tr><td><strong>通用百炼</strong></td><td><code>sk-xxxxx</code></td><td><code>https://dashscope.aliyuncs.com/compatible-mode/v1</code></td><td><code>qwen-plus</code>, <code>qwen-max</code></td></tr><tr><td><strong>Coding Plan</strong></td><td><code>sk-sp-xxxxx</code></td><td><code>https://coding.dashscope.aliyuncs.com/v1</code></td><td><code>多个模型</code> 等</td></tr></tbody></table></div><p>官方的 <code>dashscope-sdk-java</code> 只支持通用百炼 endpoint，无法连接 Coding Plan 的 endpoint！</p><hr><h2 id="三、解决方案：使用-OpenAI-兼容模式"><a href="#三、解决方案：使用-OpenAI-兼容模式" class="headerlink" title="三、解决方案：使用 OpenAI 兼容模式"></a>三、解决方案：使用 OpenAI 兼容模式</h2><h3 id="3-1-技术原理"><a href="#3-1-技术原理" class="headerlink" title="3.1 技术原理"></a>3.1 技术原理</h3><p>Coding Plan 的 endpoint 基于 <strong>OpenAI API 兼容协议</strong>，任何支持 OpenAI API 的客户端都可以调用：</p><ol><li><strong>Python</strong>: 使用 <code>openai</code> SDK</li><li><strong>Java</strong>: 使用 LangChain4j 的 <code>langchain4j-open-ai</code> 模块</li></ol><p>只要将 <code>base_url</code> 指向 Coding Plan 的 endpoint，API Key 就能正常工作！</p><hr><h2 id="四、Java-实现：LangChain4j-Coding-Plan"><a href="#四、Java-实现：LangChain4j-Coding-Plan" class="headerlink" title="四、Java 实现：LangChain4j + Coding Plan"></a>四、Java 实现：LangChain4j + Coding Plan</h2><h3 id="4-1-添加依赖"><a href="#4-1-添加依赖" class="headerlink" title="4.1 添加依赖"></a>4.1 添加依赖</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- pom.xml --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">langchain4j.version</span>&gt;</span>0.35.0<span class="tag">&lt;/<span class="name">langchain4j.version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- LangChain4j OpenAI 兼容模块 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>dev.langchain4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>langchain4j<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;langchain4j.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>dev.langchain4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>langchain4j-open-ai<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;langchain4j.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="4-2-配置文件"><a href="#4-2-配置文件" class="headerlink" title="4.2 配置文件"></a>4.2 配置文件</h3><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># application.properties - Coding Plan 配置</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.base-url</span>=<span class="string">https://coding.dashscope.aliyuncs.com/v1</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.api-key</span>=<span class="string">sk-sp-xxxxx</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.model-name</span>=<span class="string">kimi-k2.5</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.temperature</span>=<span class="string">0.3</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.max-tokens</span>=<span class="string">4096</span></span><br></pre></td></tr></table></figure><p><strong>关键点：<code>base-url</code> 必须是 <code>coding.dashscope.aliyuncs.com/v1</code>，不是通用的 <code>dashscope.aliyuncs.com</code>！</strong></p><h3 id="4-3-Java-代码实现"><a href="#4-3-Java-代码实现" class="headerlink" title="4.3 Java 代码实现"></a>4.3 Java 代码实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.AiMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.ChatMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.SystemMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.UserMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.chat.ChatLanguageModel;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.openai.OpenAiChatModel;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.output.Response;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.time.Duration;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CodingPlanExample</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CodingPlanExample</span><span class="params">(String baseUrl, String apiKey, String modelName)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 使用 OpenAI 兼容模式构建 ChatModel</span></span><br><span class="line">        <span class="keyword">this</span>.chatModel = OpenAiChatModel.builder()</span><br><span class="line">                .baseUrl(baseUrl)  <span class="comment">// Coding Plan endpoint</span></span><br><span class="line">                .apiKey(apiKey)    <span class="comment">// Coding Plan API Key</span></span><br><span class="line">                .modelName(modelName)</span><br><span class="line">                .temperature(<span class="number">0.3</span>)</span><br><span class="line">                .maxTokens(<span class="number">4096</span>)</span><br><span class="line">                .timeout(Duration.ofSeconds(<span class="number">60</span>))</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">chat</span><span class="params">(String systemPrompt, String userMessage)</span> </span>&#123;</span><br><span class="line">        List&lt;ChatMessage&gt; messages = Arrays.asList(</span><br><span class="line">                SystemMessage.from(systemPrompt),</span><br><span class="line">                UserMessage.from(userMessage)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        Response&lt;AiMessage&gt; response = chatModel.generate(messages);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (response == <span class="keyword">null</span> || response.content() == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"模型返回结果为空"</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> response.content().text();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        CodingPlanExample example = <span class="keyword">new</span> CodingPlanExample(</span><br><span class="line">                <span class="string">"https://coding.dashscope.aliyuncs.com/v1"</span>,</span><br><span class="line">                <span class="string">"sk-sp-xxxxx"</span>,</span><br><span class="line">                <span class="string">"kimi-k2.5"</span></span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        String result = example.chat(</span><br><span class="line">                <span class="string">"你是一位专业的翻译助手"</span>,</span><br><span class="line">                <span class="string">"将以下内容翻译为英文：你好世界"</span></span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        System.out.println(result);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-4-Spring-Boot-集成示例"><a href="#4-4-Spring-Boot-集成示例" class="headerlink" title="4.4 Spring Boot 集成示例"></a>4.4 Spring Boot 集成示例</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LangChain4jConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;langchain4j.open-ai.chat-model.base-url&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String baseUrl;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;langchain4j.open-ai.chat-model.api-key&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String apiKey;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;langchain4j.open-ai.chat-model.model-name&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String modelName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ChatLanguageModel <span class="title">chatLanguageModel</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> OpenAiChatModel.builder()</span><br><span class="line">                .baseUrl(baseUrl)</span><br><span class="line">                .apiKey(apiKey)</span><br><span class="line">                .modelName(modelName)</span><br><span class="line">                .timeout(Duration.ofSeconds(<span class="number">60</span>))</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TranslationService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">TranslationService</span><span class="params">(ChatLanguageModel chatModel)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.chatModel = chatModel;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">translate</span><span class="params">(String content, String sourceLang, String targetLang)</span> </span>&#123;</span><br><span class="line">        String systemPrompt = String.format(</span><br><span class="line">                <span class="string">"你是翻译专家，将内容从%s翻译为%s，直接输出结果"</span>,</span><br><span class="line">                sourceLang, targetLang</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        List&lt;ChatMessage&gt; messages = Arrays.asList(</span><br><span class="line">                SystemMessage.from(systemPrompt),</span><br><span class="line">                UserMessage.from(content)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> chatModel.generate(messages).content().text();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="五、Python-实现：OpenAI-SDK-Coding-Plan"><a href="#五、Python-实现：OpenAI-SDK-Coding-Plan" class="headerlink" title="五、Python 实现：OpenAI SDK + Coding Plan"></a>五、Python 实现：OpenAI SDK + Coding Plan</h2><h3 id="5-1-安装依赖"><a href="#5-1-安装依赖" class="headerlink" title="5.1 安装依赖"></a>5.1 安装依赖</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install openai</span><br></pre></td></tr></table></figure><h3 id="5-2-Python-代码"><a href="#5-2-Python-代码" class="headerlink" title="5.2 Python 代码"></a>5.2 Python 代码</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> OpenAI</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Coding Plan endpoint</span></span><br><span class="line">client = OpenAI(</span><br><span class="line">    base_url=<span class="string">"https://coding.dashscope.aliyuncs.com/v1"</span>,</span><br><span class="line">    api_key=<span class="string">"sk-sp-xxxxx"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">response = client.chat.completions.create(</span><br><span class="line">    model=<span class="string">"kimi-k2.5"</span>,</span><br><span class="line">    messages=[</span><br><span class="line">        &#123;<span class="string">"role"</span>: <span class="string">"system"</span>, <span class="string">"content"</span>: <span class="string">"你是一位专业的翻译助手"</span>&#125;,</span><br><span class="line">        &#123;<span class="string">"role"</span>: <span class="string">"user"</span>, <span class="string">"content"</span>: <span class="string">"将以下内容翻译为英文：你好世界"</span>&#125;</span><br><span class="line">    ],</span><br><span class="line">    temperature=<span class="number">0.3</span>,</span><br><span class="line">    max_tokens=<span class="number">4096</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">print(response.choices[<span class="number">0</span>].message.content)</span><br></pre></td></tr></table></figure><h3 id="5-3-异步调用示例"><a href="#5-3-异步调用示例" class="headerlink" title="5.3 异步调用示例"></a>5.3 异步调用示例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> AsyncOpenAI</span><br><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line">async_client = AsyncOpenAI(</span><br><span class="line">    base_url=<span class="string">"https://coding.dashscope.aliyuncs.com/v1"</span>,</span><br><span class="line">    api_key=<span class="string">"sk-sp-xxxxx"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">translate_async</span><span class="params">(content: str)</span> -&gt; str:</span></span><br><span class="line">    response = <span class="keyword">await</span> async_client.chat.completions.create(</span><br><span class="line">        model=<span class="string">"kimi-k2.5"</span>,</span><br><span class="line">        messages=[</span><br><span class="line">            &#123;<span class="string">"role"</span>: <span class="string">"system"</span>, <span class="string">"content"</span>: <span class="string">"你是翻译专家"</span>&#125;,</span><br><span class="line">            &#123;<span class="string">"role"</span>: <span class="string">"user"</span>, <span class="string">"content"</span>: content&#125;</span><br><span class="line">        ]</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> response.choices[<span class="number">0</span>].message.content</span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量翻译</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">batch_translate</span><span class="params">(contents: list[str])</span> -&gt; list[str]:</span></span><br><span class="line">    tasks = [translate_async(c) <span class="keyword">for</span> c <span class="keyword">in</span> contents]</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> asyncio.gather(*tasks)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行示例</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line">    results = <span class="keyword">await</span> batch_translate([<span class="string">"你好世界"</span>, <span class="string">"人工智能"</span>])</span><br><span class="line">    print(results)</span><br><span class="line"></span><br><span class="line">asyncio.run(main())</span><br></pre></td></tr></table></figure><hr><h2 id="六、实际应用场景"><a href="#六、实际应用场景" class="headerlink" title="六、实际应用场景"></a>六、实际应用场景</h2><h3 id="6-1-职位内容翻译"><a href="#6-1-职位内容翻译" class="headerlink" title="6.1 职位内容翻译"></a>6.1 职位内容翻译</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JobTranslationService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">translateJobDescription</span><span class="params">(String content, String sourceLang, String targetLang)</span> </span>&#123;</span><br><span class="line">        String systemPrompt = buildTranslationPrompt(sourceLang, targetLang);</span><br><span class="line"></span><br><span class="line">        List&lt;ChatMessage&gt; messages = Arrays.asList(</span><br><span class="line">                SystemMessage.from(systemPrompt),</span><br><span class="line">                UserMessage.from(content)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> chatModel.generate(messages).content().text();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> String <span class="title">buildTranslationPrompt</span><span class="params">(String sourceLang, String targetLang)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> String.format(<span class="string">""</span><span class="string">"</span></span><br><span class="line"><span class="string">            你是一位专业的职位内容翻译专家。</span></span><br><span class="line"><span class="string">            请将用户输入的职位描述从%s翻译为%s。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">            翻译要求：</span></span><br><span class="line"><span class="string">            1. 保持原文的段落结构和格式</span></span><br><span class="line"><span class="string">            2. 专业术语使用行业标准翻译</span></span><br><span class="line"><span class="string">            3. 直接输出翻译结果</span></span><br><span class="line"><span class="string">            "</span><span class="string">""</span>, sourceLang, targetLang);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-文档内容生成"><a href="#6-2-文档内容生成" class="headerlink" title="6.2 文档内容生成"></a>6.2 文档内容生成</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">generateDocumentOutline</span><span class="params">(String topic)</span> </span>&#123;</span><br><span class="line">    String prompt = <span class="string">""</span><span class="string">"</span></span><br><span class="line"><span class="string">        根据以下主题，生成一份技术文档大纲：</span></span><br><span class="line"><span class="string">        主题：%s</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        要求：</span></span><br><span class="line"><span class="string">        1. 结构清晰，层次分明</span></span><br><span class="line"><span class="string">        2. 每个章节要有简要说明</span></span><br><span class="line"><span class="string">        3. 使用 Markdown 格式输出</span></span><br><span class="line"><span class="string">        "</span><span class="string">""</span>.formatted(topic);</span><br><span class="line"></span><br><span class="line">    List&lt;ChatMessage&gt; messages = Arrays.asList(</span><br><span class="line">        SystemMessage.from(<span class="string">"你是技术文档撰写专家"</span>),</span><br><span class="line">        UserMessage.from(prompt)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> chatModel.generate(messages).content().text();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-3-智能客服对话"><a href="#6-3-智能客服对话" class="headerlink" title="6.3 智能客服对话"></a>6.3 智能客服对话</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CustomerServiceBot</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">handleUserMessage</span><span class="params">(String userMessage, List&lt;String&gt; history)</span> </span>&#123;</span><br><span class="line">        List&lt;ChatMessage&gt; messages = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 系统提示词</span></span><br><span class="line">        messages.add(SystemMessage.from(<span class="string">""</span><span class="string">"</span></span><br><span class="line"><span class="string">            你是专业的客服助手，帮助用户解答产品相关问题。</span></span><br><span class="line"><span class="string">            回答要求：</span></span><br><span class="line"><span class="string">            1. 语气友好专业</span></span><br><span class="line"><span class="string">            2. 回答简洁明了</span></span><br><span class="line"><span class="string">            3. 如果无法回答，引导用户联系人工客服</span></span><br><span class="line"><span class="string">            "</span><span class="string">""</span>));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 添加历史对话</span></span><br><span class="line">        <span class="keyword">for</span> (String h : history) &#123;</span><br><span class="line">            messages.add(UserMessage.from(h));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 当前消息</span></span><br><span class="line">        messages.add(UserMessage.from(userMessage));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> chatModel.generate(messages).content().text();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、关键注意事项"><a href="#七、关键注意事项" class="headerlink" title="七、关键注意事项"></a>七、关键注意事项</h2><h3 id="7-1-Endpoint-不要混用"><a href="#7-1-Endpoint-不要混用" class="headerlink" title="7.1 Endpoint 不要混用"></a>7.1 Endpoint 不要混用</h3><div class="table-container"><table><thead><tr><th>Key 类型</th><th>正确 Endpoint</th><th>错误 Endpoint</th></tr></thead><tbody><tr><td><code>sk-sp-xxxxx</code> (Coding Plan)</td><td><code>coding.dashscope.aliyuncs.com/v1</code></td><td><code>dashscope.aliyuncs.com</code></td></tr><tr><td><code>sk-xxxxx</code> (通用)</td><td><code>dashscope.aliyuncs.com/compatible-mode/v1</code></td><td><code>coding.dashscope.aliyuncs.com</code></td></tr></tbody></table></div><p>混用会导致 <code>InvalidApiKey</code> 错误或消耗错误的额度！</p><h3 id="7-2-模型名称差异"><a href="#7-2-模型名称差异" class="headerlink" title="7.2 模型名称差异"></a>7.2 模型名称差异</h3><p>Coding Plan 支持的模型可能与通用百炼不同：</p><ul><li>Coding Plan: <code>kimi-k2.5</code> 等</li><li>通用百炼: <code>qwen-plus</code>, <code>qwen-max</code>, <code>qwen-turbo</code></li></ul><p>请根据实际账号支持的模型选择。</p><h3 id="7-3-Token-消耗监控"><a href="#7-3-Token-消耗监控" class="headerlink" title="7.3 Token 消耗监控"></a>7.3 Token 消耗监控</h3><p>虽然使用了 Coding Plan 额度，但仍需关注：</p><ul><li>单次调用 Token 数量</li><li>Coding Plan 额度剩余</li><li>设置合理的 <code>max_tokens</code> 防止超限</li></ul><h3 id="7-4-超时设置"><a href="#7-4-超时设置" class="headerlink" title="7.4 超时设置"></a>7.4 超时设置</h3><p>Coding Plan 响应时间可能与通用服务不同，建议设置较长超时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.timeout(Duration.ofSeconds(<span class="number">60</span>))  <span class="comment">// 或更长</span></span><br></pre></td></tr></table></figure><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><h3 id="8-1-官方说法-vs-实际实践"><a href="#8-1-官方说法-vs-实际实践" class="headerlink" title="8.1 官方说法 vs 实际实践"></a>8.1 官方说法 vs 实际实践</h3><div class="table-container"><table><thead><tr><th>官方说法</th><th>实际实践</th></tr></thead><tbody><tr><td>Coding Plan Key 仅限编程工具使用</td><td>Java/Python 应用可正常使用</td></tr><tr><td>需要额外开通通用 Key</td><td>无需额外开通</td></tr><tr><td>按量付费产生额外费用</td><td>直接消耗 Coding Plan 额度</td></tr><tr><td>dashscope-sdk-java 不支持</td><td>LangChain4j/OpenAI SDK 完美支持</td></tr></tbody></table></div><h3 id="8-2-核心原理"><a href="#8-2-核心原理" class="headerlink" title="8.2 核心原理"></a>8.2 核心原理</h3><p>Coding Plan 的 endpoint 基于 <strong>OpenAI API 兼容协议</strong>，这是业界通用的 LLM API 标准。任何支持 OpenAI 协议的客户端都可以调用，不受编程工具限制。</p><h3 id="8-3-适用场景"><a href="#8-3-适用场景" class="headerlink" title="8.3 适用场景"></a>8.3 适用场景</h3><ul><li>✅ Java 后端服务调用 LLM</li><li>✅ Python 应用调用 LLM</li><li>✅ Spring Boot / LangChain4j 集成</li><li>✅ 翻译、生成、对话等各类 NLP 任务</li><li>❌ 直接使用 dashscope-sdk-java（不支持 Coding Plan endpoint）</li></ul><hr><h2 id="九、参考资料"><a href="#九、参考资料" class="headerlink" title="九、参考资料"></a>九、参考资料</h2><ul><li><a href="https://docs.langchain4j.dev/" target="_blank" rel="noopener">LangChain4j 官方文档</a></li><li><a href="https://platform.openai.com/docs/api-reference" target="_blank" rel="noopener">OpenAI API 兼容协议</a></li><li><a href="https://bailian.console.aliyun.com/" target="_blank" rel="noopener">阿里百炼 Coding Plan</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;阿里云百炼 Coding Plan 官方宣称”仅限编程工具使用”，但实际上其 endpoint 基于 OpenAI 兼容协议，Java/Python 应用完全可以调用。本文分享如何用 LangChain4j 和 OpenAI SDK 突破这一限制，</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="Coding Plan" scheme="https://donehub.github.io/tags/Coding-Plan/"/>
    
  </entry>
  
  <entry>
    <title>Hermes Agent：一个会&quot;记住你&quot;的 AI 助手</title>
    <link href="https://donehub.github.io/2026/04/17/hermes-agent-introduction/"/>
    <id>https://donehub.github.io/2026/04/17/hermes-agent-introduction/</id>
    <published>2026-04-16T16:00:00.000Z</published>
    <updated>2026-04-18T10:04:02.750Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>如果你最近关注 AI Agent 领域，可能会注意到一个新名字——Hermes Agent。它来自 Nous Research，在短短两个月内从一个小型内部项目成长为功能完备的 AI Agent 平台。这篇文章聊聊它到底有什么不一样，以及为什么值得你花时间了解。</p></blockquote><hr><h2 id="一、Hermes-Agent-是什么？"><a href="#一、Hermes-Agent-是什么？" class="headerlink" title="一、Hermes Agent 是什么？"></a>一、Hermes Agent 是什么？</h2><p>简单说，它是一个<strong>可以自我进化的 AI Agent 框架</strong>。</p><p>市面上大多数 Agent 工具，你用完一次，下次还要重新教它。你说”帮我整理今天的 Git 提交记录”，它执行了。第二天你再说同样的话，它又从头开始理解。对话结束后，一切归零。</p><p>Hermes Agent 不一样。它有个叫”技能系统”的东西。第一次你教它做某件事，完成后它会问自己：这件事我以后是不是经常要做？如果答案是肯定的，它会把整个过程打包成一个技能。下次你只需要说”整理提交”，它就能直接调用这个技能。</p><p>这不是预设好的模板，是 Agent 自己判断、自己创建、自己优化的。用久了，它会越来越懂你的工作习惯。</p><hr><h2 id="二、为什么突然火了？"><a href="#二、为什么突然火了？" class="headerlink" title="二、为什么突然火了？"></a>二、为什么突然火了？</h2><p>翻一下 Hermes Agent 的版本历史，你会发现一个有意思的时间线：</p><div class="table-container"><table><thead><tr><th>版本</th><th>发布日期</th><th>说明</th></tr></thead><tbody><tr><td>v0.1.0</td><td>2026年2月底</td><td>内部预发布版本</td></tr><tr><td>v0.2.0</td><td>3月12日</td><td>首个公开版本，216个PR，63位贡献者</td></tr><tr><td>v0.3.0</td><td>3月17日</td><td>流式输出、插件架构、Honcho记忆</td></tr><tr><td>v0.4.0</td><td>3月23日</td><td>6个新消息平台、4个新推理提供商</td></tr><tr><td>v0.9.0</td><td>4月13日</td><td>Android支持、iMessage、微信接入</td></tr><tr><td>v0.10.0</td><td>4月16日</td><td>Nous工具网关，订阅用户零额外API</td></tr></tbody></table></div><p>从2月底到4月中旬，不到两个月，发布了10个大版本。平均每三四天一个版本。这不是营销驱动的节奏，是真实需求驱动的迭代速度。</p><p>看看 v0.2.0 的发布说明：”In just over two weeks, Hermes Agent went from a small internal project to a full-featured AI agent platform — thanks to an explosion of community contributions.”</p><p>这句话翻译过来：两个星期，从内部小项目变成完整平台，原因是社区贡献爆发。</p><p>为什么爆发？因为 Hermes Agent 解决了一个长期痛点——Agent 的记忆和学习能力。之前大家做 Agent，要么接受”每次对话归零”的现实，要么自己写一套复杂的持久化逻辑。Hermes Agent 把这套逻辑内置了，而且是真正意义上的”学习”，不只是”存储”。</p><hr><h2 id="三、核心特质"><a href="#三、核心特质" class="headerlink" title="三、核心特质"></a>三、核心特质</h2><h3 id="3-1-闭环学习，不是单次执行"><a href="#3-1-闭环学习，不是单次执行" class="headerlink" title="3.1 闭环学习，不是单次执行"></a>3.1 闭环学习，不是单次执行</h3><p>这点前面说了，展开讲一下细节。</p><p>Hermes Agent 的学习机制包含几个层次：</p><p><strong>技能自动创建</strong>：完成复杂任务后，Agent 会分析这个任务是否有重复价值。有，就创建技能。技能里包含了执行步骤、需要的工具、注意事项。</p><p><strong>技能自我改进</strong>：你用某个技能的时候如果给了反馈，比如”这次格式不对”或”下次加上这个字段”，Agent 会把这些反馈写进技能描述里。下一次执行会自动应用。</p><p><strong>定期提醒</strong>：Agent 有个机制叫”periodic nudges”，会周期性地提醒自己把重要信息持久化。不是被动等待你要求，是主动思考”这个信息值得记住吗”。</p><p><strong>跨会话搜索</strong>：你问”上次我们讨论的那个方案是什么”，它会搜索历史对话，用 LLM 做摘要，然后告诉你。这不是简单的关键词搜索，是语义层面的召回。</p><h3 id="3-2-Honcho-用户建模"><a href="#3-2-Honcho-用户建模" class="headerlink" title="3.2 Honcho 用户建模"></a>3.2 Honcho 用户建模</h3><p>Hermes Agent 内置了一个叫 Honcho 的用户建模系统。这个名字来自 plastic-labs 的 Honcho 项目，是一个专门做”AI 理解用户”的框架。</p><p>它的作用是：Agent 会持续观察你的偏好、习惯、工作方式，然后建立一个用户模型。你喜欢简洁回复，它记住；你讨厌某种操作方式，它记住；你对某个项目有特殊约定，它跨会话保持。</p><p>这不是简单的”记住你说过的话”，是”理解你是什么样的人”。</p><h3 id="3-3-多平台统一接入"><a href="#3-3-多平台统一接入" class="headerlink" title="3.3 多平台统一接入"></a>3.3 多平台统一接入</h3><p>这点对实际使用很重要。</p><p>你可以在 Telegram、Discord、Slack、WhatsApp、Signal 这些平台跟 Hermes Agent 对话，也可以在终端用 CLI。同一个 Agent，不同入口，记忆和技能是共享的。</p><p>这意味着你早上在公司电脑用 CLI 让它整理日报，晚上回家用 Telegram 继续讨论，它记得你白天说了什么。</p><h3 id="3-4-定时任务，原生支持"><a href="#3-4-定时任务，原生支持" class="headerlink" title="3.4 定时任务，原生支持"></a>3.4 定时任务，原生支持</h3><p>大多数 Agent 框架没有内置的定时任务系统。你想让 Agent 每天早上自动发日报，要么写外部脚本触发，要么依赖某个外部调度器。</p><p>Hermes Agent 内置了 cron 调度。你用自然语言描述：”每天早上9点，汇总昨天的 Git 提交并发到 Telegram”，它会自动解析、创建任务、按时执行。</p><p>这对于”Agent 作为助手”的场景很重要。真正的助手不只是你叫它才动，是会主动做事情。</p><h3 id="3-5-云端部署，不是本地绑定"><a href="#3-5-云端部署，不是本地绑定" class="headerlink" title="3.5 云端部署，不是本地绑定"></a>3.5 云端部署，不是本地绑定</h3><p>这点是 Hermes Agent 相比很多同类产品的优势。</p><p>它支持六种终端后端：本地、Docker、SSH、Daytona、Singularity、Modal。其中 Modal 和 Daytona 是”无服务器”模式——你的 Agent 环境在云端，空闲时几乎不花钱，有请求时自动唤醒。</p><p>这意味着你可以把 Hermes Agent 部署到云端，然后从 Telegram 发消息触发。不在电脑前的时候，Agent 依然在工作。这对于”随时随地操作”的需求很关键。</p><h3 id="3-6-多模型，随时切换"><a href="#3-6-多模型，随时切换" class="headerlink" title="3.6 多模型，随时切换"></a>3.6 多模型，随时切换</h3><p>Hermes Agent 支持大量 LLM 提供商：</p><ul><li>Nous Portal（官方订阅服务）</li><li>OpenRouter（200+模型）</li><li>Anthropic（Claude 系列）</li><li>OpenAI（GPT 系列）</li><li>Google AI Studio（Gemini）</li><li>阿里云百炼（DashScope）</li><li>智谱 AI（GLM）</li><li>Moonshot（Kimi）</li><li>MiniMax</li><li>小米 MiMo</li><li>NVIDIA NIM</li><li>DeepSeek</li><li>xAI（Grok）</li><li>Hugging Face</li><li>AWS Bedrock</li><li>还有更多…</li></ul><p>切换模型用一个命令：<code>hermes model</code>。不需要改代码，不需要重新部署，运行时切换。</p><p>这对于实际使用很重要。不同的任务适合不同的模型，你可能写代码用 Claude，快速问答用 GPT-mini，中文内容用 Qwen。Hermes Agent 让这种切换变得零成本。</p><hr><h2 id="四、对-OpenClaw-用户的意义"><a href="#四、对-OpenClaw-用户的意义" class="headerlink" title="四、对 OpenClaw 用户的意义"></a>四、对 OpenClaw 用户的意义</h2><p>如果你正在用 OpenClaw，听到 Hermes Agent 可能会想：又一个类似的工具，有必要换吗？</p><p>这里有个事实你可能不知道：<strong>Hermes Agent 是 OpenClaw 的官方进化版本</strong>。</p><p>翻 Hermes Agent 的文档，你会发现专门的迁移章节：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">## Migrating from OpenClaw</span><br><span class="line"></span><br><span class="line">If you&#39;re coming from OpenClaw, Hermes can automatically import your settings, memories, skills, and API keys.</span><br></pre></td></tr></table></figure><p>迁移命令很简单：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hermes claw migrate --dry-run    <span class="comment"># 先预览会迁移什么</span></span><br><span class="line">hermes claw migrate              <span class="comment"># 执行迁移</span></span><br></pre></td></tr></table></figure><p>迁移内容包括：</p><ul><li>SOUL.md（人格设定）</li><li>已有技能</li><li>命令白名单</li><li>消息平台配置</li><li>API 密钥（Telegram、OpenRouter、OpenAI、Anthropic 等）</li><li>工作空间说明（AGENTS.md）</li></ul><p>这说明 Hermes Agent 的开发团队明确知道 OpenClaw 用户群体，并且专门做了兼容路径。</p><p>那为什么要从 OpenClaw 换到 Hermes Agent？几个实际理由：</p><div class="table-container"><table><thead><tr><th>功能</th><th>OpenClaw</th><th>Hermes Agent</th></tr></thead><tbody><tr><td>技能系统</td><td>有，但不自改进</td><td>有，且会自我优化</td></tr><tr><td>定时任务</td><td>无</td><td>内置 cron</td></tr><tr><td>云端部署</td><td>本地运行</td><td>Modal/Daytona 无服务器</td></tr><tr><td>用户建模</td><td>会话级</td><td>Honcho 深度建模</td></tr><tr><td>MCP 协议</td><td>无</td><td>支持</td></tr><tr><td>消息平台</td><td>Telegram、飞书等</td><td>Telegram、Discord、Slack、WhatsApp、Signal</td></tr></tbody></table></div><p>如果你需要定时任务、云端部署、Agent 学习能力，Hermes Agent 提供了这些 OpenClaw 没有的东西。</p><hr><h2 id="五、安装和使用指南"><a href="#五、安装和使用指南" class="headerlink" title="五、安装和使用指南"></a>五、安装和使用指南</h2><h3 id="5-1-Mac-用户"><a href="#5-1-Mac-用户" class="headerlink" title="5.1 Mac 用户"></a>5.1 Mac 用户</h3><p>Mac 上安装最简单，一行命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash</span><br></pre></td></tr></table></figure><p>安装完成后：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> ~/.zshrc   <span class="comment"># 或者 source ~/.bashrc</span></span><br><span class="line">hermes            <span class="comment"># 启动</span></span><br></pre></td></tr></table></figure><p>首次运行会引导你配置。按照提示选择 LLM 提供商、设置 API Key 就可以开始使用。</p><h3 id="5-2-Windows-用户"><a href="#5-2-Windows-用户" class="headerlink" title="5.2 Windows 用户"></a>5.2 Windows 用户</h3><p>Windows 原生不支持，需要通过 WSL2。</p><p>先安装 WSL2：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl -<span class="literal">-install</span></span><br></pre></td></tr></table></figure><p>然后进入 WSL2 的 Linux 环境，运行和 Mac 一样的安装命令。</p><p>这步对不熟悉 Linux 的用户可能有点门槛，但设置好后使用体验和 Mac 一样。</p><h3 id="5-3-常用命令"><a href="#5-3-常用命令" class="headerlink" title="5.3 常用命令"></a>5.3 常用命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">hermes              <span class="comment"># 启动交互式 CLI</span></span><br><span class="line">hermes model        <span class="comment"># 选择模型提供商和具体模型</span></span><br><span class="line">hermes tools        <span class="comment"># 配置启用的工具</span></span><br><span class="line">hermes gateway      <span class="comment"># 启动消息平台网关（Telegram、Discord 等）</span></span><br><span class="line">hermes setup        <span class="comment"># 完整设置向导</span></span><br><span class="line">hermes doctor       <span class="comment"># 检查配置是否有问题</span></span><br><span class="line">hermes update       <span class="comment"># 更新到最新版本</span></span><br></pre></td></tr></table></figure><p>在对话中使用的斜杠命令：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;new              # 开始新对话</span><br><span class="line">&#x2F;model            # 切换模型</span><br><span class="line">&#x2F;skills           # 浏览可用技能</span><br><span class="line">&#x2F;retry            # 重试上一轮</span><br><span class="line">&#x2F;undo             #撤销上一轮</span><br><span class="line">&#x2F;compress         # 压缩上下文</span><br><span class="line">&#x2F;usage            # 查看用量</span><br></pre></td></tr></table></figure><h3 id="5-4-配置阿里云百炼"><a href="#5-4-配置阿里云百炼" class="headerlink" title="5.4 配置阿里云百炼"></a>5.4 配置阿里云百炼</h3><p>如果你之前用 OpenClaw 配了阿里云百炼的 Coding Plan，在 Hermes Agent 里可以直接用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置环境变量</span></span><br><span class="line"><span class="built_in">export</span> DASHSCOPE_API_KEY=你的API密钥</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果用国内版，额外设置</span></span><br><span class="line"><span class="built_in">export</span> DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1</span><br></pre></td></tr></table></figure><p>或者在 <code>~/.hermes/.env</code> 文件里写：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DASHSCOPE_API_KEY&#x3D;sk-xxxxxxxx</span><br></pre></td></tr></table></figure><p>然后用命令切换提供商：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hermes model alibaba</span><br><span class="line">hermes model qwen3-coder-plus</span><br></pre></td></tr></table></figure><hr><h2 id="六、OpenClaw-用户迁移指南"><a href="#六、OpenClaw-用户迁移指南" class="headerlink" title="六、OpenClaw 用户迁移指南"></a>六、OpenClaw 用户迁移指南</h2><p>如果你已经有 OpenClaw 的配置，迁移步骤：</p><p><strong>1. 安装 Hermes Agent</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash</span><br><span class="line"><span class="built_in">source</span> ~/.zshrc</span><br></pre></td></tr></table></figure><p><strong>2. 运行迁移命令</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes setup</span><br></pre></td></tr></table></figure><p>Setup 向导会自动检测 <code>~/.openclaw</code> 目录，提示你是否迁移。</p><p>或者任何时候手动运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hermes claw migrate --dry-run    <span class="comment"># 预览会迁移什么</span></span><br><span class="line">hermes claw migrate              <span class="comment"># 执行迁移</span></span><br></pre></td></tr></table></figure><p><strong>3. 检查迁移结果</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes doctor    <span class="comment"># 检查配置是否正确</span></span><br></pre></td></tr></table></figure><p><strong>4. 开始使用</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes    <span class="comment"># 启动 CLI</span></span><br></pre></td></tr></table></figure><p>迁移后，你的 SOUL.md（人格设定）、已有技能、命令白名单、消息平台配置都会保留。API 密钥会自动迁移到 <code>~/.hermes/.env</code>。</p><p><strong>注意</strong>：OpenClaw 支持 Telegram、飞书等平台，这些配置会直接迁移。</p><hr><h2 id="七、适合什么人用？"><a href="#七、适合什么人用？" class="headerlink" title="七、适合什么人用？"></a>七、适合什么人用？</h2><p>如果你的需求是：</p><ul><li><strong>写代码为主</strong> → 继续用 Claude Code，它在代码理解上更强</li><li><strong>操作电脑、整理文件</strong> → Hermes Agent 或 OpenClaw 都能胜任</li><li><strong>重复性任务多</strong> → Hermes Agent，技能系统会帮你省时间</li><li><strong>需要定时自动化</strong> → Hermes Agent，内置 cron</li><li><strong>离开电脑时也想用</strong> → Hermes Agent，云端部署 + Telegram</li><li><strong>飞书/Telegram 是核心场景</strong> → Hermes Agent 或 OpenClaw 都能胜任</li></ul><p>如果你已经在用 OpenClaw，不需要立即换。两个工具核心功能重叠约 70%，Hermes Agent 新增的是学习能力、定时任务和云端部署。这些对你有没有价值，看你的实际需求。</p><p>但如果你想尝试 Hermes Agent，迁移成本很低。一条命令就能把 OpenClaw 的配置全部导过去，不存在”从头设置”的问题。</p><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Hermes Agent 的价值不在”功能更多”，在”设计思路不同”。</p><p>大多数 Agent 工具的设计假设是：用户发起对话 → Agent 执行 → 结束。下一次对话从零开始。</p><p>Hermes Agent 的设计假设是：Agent 和用户是长期关系，Agent 应该越来越懂用户，而不是每次都从陌生人开始。</p><p>这个假设的差异，导致了功能设计的差异：技能自动创建、技能自我改进、Honcho 用户建模、跨会话搜索、定期提醒持久化。</p><p>这些功能单独看都不复杂，组合起来形成一个闭环：Agent 做事 → Agent 学习 → Agent 下次做得更好。</p><p>这个闭环是 Hermes Agent 和其他 Agent 工具的本质区别。</p><p>如果你对”Agent 可以学习和进化”这个概念感兴趣，值得花半小时安装试试。不需要完全替换你现有的工具，先体验一下它的学习机制，看看是否符合你的预期。</p><hr><h2 id="九、相关资源"><a href="#九、相关资源" class="headerlink" title="九、相关资源"></a>九、相关资源</h2><ul><li>Hermes Agent GitHub：<a href="https://github.com/NousResearch/hermes-agent" target="_blank" rel="noopener">https://github.com/NousResearch/hermes-agent</a></li><li>官方文档：<a href="https://hermes-agent.nousresearch.com/docs/" target="_blank" rel="noopener">https://hermes-agent.nousresearch.com/docs/</a></li><li>Skills Hub：<a href="https://agentskills.io" target="_blank" rel="noopener">https://agentskills.io</a></li><li>Nous Research Discord：<a href="https://discord.gg/NousResearch" target="_blank" rel="noopener">https://discord.gg/NousResearch</a></li><li>HermesClaw（微信桥接）：<a href="https://github.com/AaronWong1999/hermesclaw" target="_blank" rel="noopener">https://github.com/AaronWong1999/hermesclaw</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;如果你最近关注 AI Agent 领域，可能会注意到一个新名字——Hermes Agent。它来自 Nous Research，在短短两个月内从一个小型内部项目成长为功能完备的 AI Agent 平台。这篇文章聊聊它到底有什么不一样，以及为什么值得</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="AI Agent" scheme="https://donehub.github.io/tags/AI-Agent/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code 为什么不用 LangChain/LangGraph：自研架构的深层逻辑</title>
    <link href="https://donehub.github.io/2026/04/15/claude-code-why-no-langchain/"/>
    <id>https://donehub.github.io/2026/04/15/claude-code-why-no-langchain/</id>
    <published>2026-04-14T16:00:00.000Z</published>
    <updated>2026-04-13T01:35:20.033Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>LangChain 和 LangGraph 是当下最流行的 Agent 开发框架，但 Anthropic 的 Claude Code 却完全不用它们。这不是傲慢，而是基于技术本质、产品体验、API 特性和工程可控性的四重考量。本文从多个层面剖析 Claude Code 的自研架构选择，以及它用什么技术替代了 LangChain/LangGraph 的能力。</p></blockquote><hr><h2 id="一、先说结论"><a href="#一、先说结论" class="headerlink" title="一、先说结论"></a>一、先说结论</h2><p>Claude Code 不用 LangChain/LangGraph，原因有四个：</p><div class="table-container"><table><thead><tr><th>层面</th><th>LangChain/LangGraph 的限制</th><th>Claude Code 的选择</th></tr></thead><tbody><tr><td><strong>架构层面</strong></td><td>ReAct 模式的串行瓶颈</td><td>Async Generator 状态机</td></tr><tr><td><strong>API 层面</strong></td><td>无法充分利用 Anthropic API 特性</td><td>原生 SDK 直接集成</td></tr><tr><td><strong>性能层面</strong></td><td>抽象层增加延迟</td><td>零抽象，直接流式处理</td></tr><tr><td><strong>可控层面</strong></td><td>框架黑盒，难以定制</td><td>全栈自研，精准控制</td></tr></tbody></table></div><p><strong>一句话概括</strong>：LangChain/LangGraph 是”通用框架”，Claude Code 是”专用系统”。通用框架追求易用，专用系统追求极致体验。</p><hr><h2 id="二、架构层面：为什么放弃-ReAct"><a href="#二、架构层面：为什么放弃-ReAct" class="headerlink" title="二、架构层面：为什么放弃 ReAct"></a>二、架构层面：为什么放弃 ReAct</h2><h3 id="2-1-ReAct-模式的根本缺陷"><a href="#2-1-ReAct-模式的根本缺陷" class="headerlink" title="2.1 ReAct 模式的根本缺陷"></a>2.1 ReAct 模式的根本缺陷</h3><p>LangChain 和 LangGraph 的核心都是 <strong>ReAct 模式</strong>（Reasoning + Acting）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">思考(Thought) → 行动(Action) → 观察(Observation) → 思考 → ...</span><br></pre></td></tr></table></figure><p>这个模式直观易懂，但存在三个根本缺陷：</p><p><strong>缺陷一：串行瓶颈</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    ReAct 串行流程                            │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  用户输入 → 等待完整响应 → 解析工具调用 → 执行工具 → 等待 →  │</span><br><span class="line">│            └───────────────────────────────────┘            │</span><br><span class="line">│                        用户感知到的延迟                       │</span><br><span class="line">│                                                             │</span><br><span class="line">│  问题：用户要等模型生成完整响应后才能看到工具执行             │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>在 CLI 交互场景中，这种延迟是致命的——用户盯着屏幕等待，不知道发生了什么。</p><p><strong>缺陷二：无法利用流式传输</strong></p><p>现代 LLM API 都支持流式输出（SSE），但 ReAct 模式下流式的价值被大大削弱：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangChain 的 Agent 执行</span></span><br><span class="line">agent.run(<span class="string">"帮我分析这个项目"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 内部流程：</span></span><br><span class="line"><span class="comment"># 1. LLM 生成完整响应（即使流式，也要等 action 完整）</span></span><br><span class="line"><span class="comment"># 2. OutputParser 解析响应文本</span></span><br><span class="line"><span class="comment"># 3. 提取工具名称和参数</span></span><br><span class="line"><span class="comment"># 4. 执行工具</span></span><br><span class="line"><span class="comment"># 5. 工具结果返回给 LLM</span></span><br><span class="line"><span class="comment"># 6. 重复...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 流式输出的价值：实时看到模型"在想什么"</span></span><br><span class="line"><span class="comment"># 但工具执行：必须等完整响应后才能开始</span></span><br><span class="line"><span class="comment"># 两者冲突，流式体验被割裂</span></span><br></pre></td></tr></table></figure><p><strong>缺陷三：状态恢复困难</strong></p><p>ReAct 模式没有统一的状态表示，每一步都是独立的：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Step 1: Thought → Action → Observation  (无状态记忆)</span><br><span class="line">Step 2: Thought → Action → Observation  (重新开始)</span><br><span class="line">Step 3: ...</span><br><span class="line"></span><br><span class="line">当 API 超时、Token 溢出时：</span><br><span class="line">- LangChain：抛出异常，用户需手动处理</span><br><span class="line">- LangGraph：需要显式定义 checkpoint，复杂度高</span><br><span class="line">- Claude Code：State 对象统一承载，自动恢复</span><br></pre></td></tr></table></figure><h3 id="2-2-Claude-Code-的替代方案：Async-Generator-状态机"><a href="#2-2-Claude-Code-的替代方案：Async-Generator-状态机" class="headerlink" title="2.2 Claude Code 的替代方案：Async Generator 状态机"></a>2.2 Claude Code 的替代方案：Async Generator 状态机</h3><p>Claude Code 用一个 <strong>while(true) 循环 + State 赋值</strong> 替代 ReAct：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/query.ts 核心（简化版）</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span>* <span class="title">query</span>(<span class="params">params: QueryParams</span>): <span class="title">AsyncGenerator</span>&lt;<span class="title">QueryUpdate</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> state: State = &#123;</span><br><span class="line">    messages: [...],</span><br><span class="line">    toolUseContext: &#123;...&#125;,</span><br><span class="line">    turnCount: <span class="number">0</span>,</span><br><span class="line">    transition: <span class="literal">undefined</span>,</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 阶段1: 消息压缩（自动处理 Token 溢出）</span></span><br><span class="line">    <span class="comment">// 阶段2: 流式 API 调用（工具即时执行）</span></span><br><span class="line">    <span class="comment">// 阶段3: 决策点（继续还是结束）</span></span><br><span class="line">    <span class="comment">// 阶段4: 工具编排（并行只读，串行写入）</span></span><br><span class="line">    <span class="comment">// 阶段5: 状态更新</span></span><br><span class="line">    </span><br><span class="line">    state = next  <span class="comment">// 通过赋值驱动循环</span></span><br><span class="line">    <span class="keyword">continue</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>核心优势对比</strong>：</p><div class="table-container"><table><thead><tr><th>维度</th><th>ReAct</th><th>Async Generator 状态机</th></tr></thead><tbody><tr><td>执行方式</td><td>串行，等待完整响应</td><td>流式，工具即时执行</td></tr><tr><td>状态管理</td><td>无统一状态</td><td>State 对象承载所有信息</td></tr><tr><td>错误恢复</td><td>手动处理</td><td>6 种内置恢复策略</td></tr><tr><td>内存安全</td><td>可能递归溢出</td><td>状态赋值，无递归风险</td></tr><tr><td>可观测性</td><td>需要额外追踪</td><td>transition 字段记录转换原因</td></tr></tbody></table></div><h3 id="2-3-流式即时执行：StreamingToolExecutor"><a href="#2-3-流式即时执行：StreamingToolExecutor" class="headerlink" title="2.3 流式即时执行：StreamingToolExecutor"></a>2.3 流式即时执行：StreamingToolExecutor</h3><p>Claude Code 的关键创新是 <strong>工具在模型生成过程中就开始执行</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                 Claude Code 流式执行流程                      │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  模型流式输出：                                              │</span><br><span class="line">│    &quot;我来帮你分析这个项目...&quot;                                 │</span><br><span class="line">│    &quot;首先读取 README...&quot;                                     │</span><br><span class="line">│    [生成 tool_use 块: Read &#123; path: &quot;README.md&quot; &#125;]           │</span><br><span class="line">│                                                             │</span><br><span class="line">│                    ↓ 立即执行                                │</span><br><span class="line">│                                                             │</span><br><span class="line">│  StreamingToolExecutor:                                     │</span><br><span class="line">│    检测到 tool_use → 立即调用 Read 工具                     │</span><br><span class="line">│    工具结果实时返回                                          │</span><br><span class="line">│                                                             │</span><br><span class="line">│  用户感知：                                                  │</span><br><span class="line">│    实时看到模型思考                                          │</span><br><span class="line">│    实时看到工具执行                                          │</span><br><span class="line">│    无需等待完整响应                                          │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p><strong>对比 LangChain</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">LangChain 流程：</span><br><span class="line">  用户输入 → 等待(模型完整响应) → 解析 → 执行工具 → 等待 → ...</span><br><span class="line">  总延迟 &#x3D; 模型生成时间 + 解析时间 + 工具执行时间</span><br><span class="line"></span><br><span class="line">Claude Code 流程：</span><br><span class="line">  用户输入 → 流式生成(工具即时执行) → 流式输出 → ...</span><br><span class="line">  总延迟 &#x3D; max(模型生成时间, 工具执行时间)</span><br></pre></td></tr></table></figure><hr><h2 id="三、API-层面：原生特性的充分利用"><a href="#三、API-层面：原生特性的充分利用" class="headerlink" title="三、API 层面：原生特性的充分利用"></a>三、API 层面：原生特性的充分利用</h2><h3 id="3-1-LangChain-的”框架税”"><a href="#3-1-LangChain-的”框架税”" class="headerlink" title="3.1 LangChain 的”框架税”"></a>3.1 LangChain 的”框架税”</h3><p>LangChain 作为通用框架，需要在多种模型 API 之间保持一致性。这意味着：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Anthropic API 特性          → LangChain 抽象层 → 用户代码</span><br><span class="line">                                    ↓</span><br><span class="line">                          被抹平或延迟支持</span><br></pre></td></tr></table></figure><p><strong>Anthropic 独有的 API 特性</strong>：</p><div class="table-container"><table><thead><tr><th>特性</th><th>说明</th><th>LangChain 支持情况</th></tr></thead><tbody><tr><td><strong>Prompt Caching</strong></td><td>提示词缓存，降本 90%</td><td>2024 年后才支持，使用复杂</td></tr><tr><td><strong>Extended Thinking</strong></td><td>思维链输出，推理透明</td><td>LangChain 无原生支持</td></tr><tr><td><strong>Computer Use</strong></td><td>屏幕操作能力</td><td>LangChain 无原生支持</td></tr><tr><td><strong>原生 tool_use</strong></td><td>结构化工具调用块</td><td>LangChain 用 OutputParser 解析文本</td></tr><tr><td><strong>原生流式 tool_use</strong></td><td>流式传输中工具即时触发</td><td>LangChain 需等待完整响应</td></tr></tbody></table></div><h3 id="3-2-Claude-Code-的原生集成"><a href="#3-2-Claude-Code-的原生集成" class="headerlink" title="3.2 Claude Code 的原生集成"></a>3.2 Claude Code 的原生集成</h3><p>Claude Code 直接使用 Anthropic SDK，充分利用所有原生特性：</p><p><strong>示例：Prompt Caching 的利用</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code 的提示词组装（src/constants/prompts.ts）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 静态可缓存区域（scope: 'global'）</span></span><br><span class="line"><span class="keyword">const</span> systemPrompt = &#123;</span><br><span class="line">  <span class="keyword">type</span>: <span class="string">'text'</span>,</span><br><span class="line">  text: <span class="string">`</span></span><br><span class="line"><span class="string">    ## 角色定义</span></span><br><span class="line"><span class="string">    Claude Code 是一个...</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    ## 系统规则</span></span><br><span class="line"><span class="string">    你必须遵守...</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    ## 工具说明</span></span><br><span class="line"><span class="string">    以下工具可用...</span></span><br><span class="line"><span class="string">  `</span>,</span><br><span class="line">  cache_control: &#123; <span class="keyword">type</span>: <span class="string">'ephemeral'</span> &#125;  <span class="comment">// 缓存标记</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 动态不可缓存区域（scope: 'ephemeral'）</span></span><br><span class="line"><span class="keyword">const</span> dynamicPrompt = &#123;</span><br><span class="line">  <span class="keyword">type</span>: <span class="string">'text'</span>,</span><br><span class="line">  text: <span class="string">`</span></span><br><span class="line"><span class="string">    ## 当前环境</span></span><br><span class="line"><span class="string">    工作目录: <span class="subst">$&#123;cwd&#125;</span></span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    ## 用户记忆</span></span><br><span class="line"><span class="string">    <span class="subst">$&#123;claudeMdContent&#125;</span></span></span><br><span class="line"><span class="string">  `</span>,</span><br><span class="line">  cache_control: &#123; <span class="keyword">type</span>: <span class="string">'ephemeral'</span> &#125;  <span class="comment">// 独立缓存</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>缓存效果</strong>：</p><ul><li>第一次调用：完整 token 计费</li><li>后续调用：静态部分缓存命中，降本约 <strong>90%</strong></li></ul><p>LangChain 也支持 Prompt Caching，但需要用户手动配置，且无法像 Claude Code 这样精细划分缓存边界。</p><p><strong>示例：原生 tool_use 块</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Anthropic API 响应格式</span></span><br><span class="line">&#123;</span><br><span class="line">  content: [</span><br><span class="line">    &#123; <span class="keyword">type</span>: <span class="string">'text'</span>, text: <span class="string">'我来帮你...'</span> &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">type</span>: <span class="string">'tool_use'</span>,</span><br><span class="line">      id: <span class="string">'toolu_01...'</span>,</span><br><span class="line">      name: <span class="string">'Read'</span>,</span><br><span class="line">      input: &#123; file_path: <span class="string">'/path/to/file'</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Claude Code 直接处理</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> block of response.content) &#123;</span><br><span class="line">  <span class="keyword">if</span> (block.type === <span class="string">'tool_use'</span>) &#123;</span><br><span class="line">    <span class="comment">// 立即执行，无需解析文本</span></span><br><span class="line">    <span class="keyword">await</span> executeTool(block.name, block.input)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>对比 LangChain</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangChain 的工具调用</span></span><br><span class="line">response = llm.invoke(prompt)</span><br><span class="line"></span><br><span class="line"><span class="comment"># OutputParser 解析文本</span></span><br><span class="line">parsed = output_parser.parse(response.content)</span><br><span class="line"><span class="comment"># 解析可能失败，格式不固定</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> parsed[<span class="string">'action'</span>]:</span><br><span class="line">    tool_name = parsed[<span class="string">'action'</span>][<span class="string">'tool'</span>]</span><br><span class="line">    tool_input = parsed[<span class="string">'action'</span>][<span class="string">'input'</span>]</span><br><span class="line">    result = tools[tool_name].run(tool_input)</span><br></pre></td></tr></table></figure><p>LangChain 需要 OutputParser 解析模型输出的文本，这是脆弱的——模型格式不固定时解析会失败。</p><hr><h2 id="四、性能层面：零抽象的流式优先"><a href="#四、性能层面：零抽象的流式优先" class="headerlink" title="四、性能层面：零抽象的流式优先"></a>四、性能层面：零抽象的流式优先</h2><h3 id="4-1-LangChain-的抽象层堆叠"><a href="#4-1-LangChain-的抽象层堆叠" class="headerlink" title="4.1 LangChain 的抽象层堆叠"></a>4.1 LangChain 的抽象层堆叠</h3><p>LangChain 的抽象层结构：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">用户代码</span><br><span class="line">  → Chain</span><br><span class="line">    → AgentExecutor</span><br><span class="line">      → LLM</span><br><span class="line">        → Memory</span><br><span class="line">          → Tools</span><br><span class="line">            → OutputParser</span><br><span class="line">              → 实际 API 调用</span><br></pre></td></tr></table></figure><p>每一层都增加处理开销。对于 Web 应用，这些开销可以忽略；但对于 <strong>CLI 交互工具</strong>，延迟是致命的。</p><h3 id="4-2-Claude-Code-的零抽象设计"><a href="#4-2-Claude-Code-的零抽象设计" class="headerlink" title="4.2 Claude Code 的零抽象设计"></a>4.2 Claude Code 的零抽象设计</h3><p>Claude Code 的结构：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">用户输入</span><br><span class="line">  → query() AsyncGenerator</span><br><span class="line">    → Anthropic SDK（直接调用）</span><br><span class="line">      → 工具执行（流式即时）</span><br></pre></td></tr></table></figure><p><strong>没有中间抽象层</strong>，API 响应直接流式传递给用户。</p><h3 id="4-3-工具编排的性能优化"><a href="#4-3-工具编排的性能优化" class="headerlink" title="4.3 工具编排的性能优化"></a>4.3 工具编排的性能优化</h3><p>Claude Code 的工具编排策略：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">工具调用列表</span><br><span class="line">  │</span><br><span class="line">  ├─ 分类：只读 vs 写入</span><br><span class="line">  │</span><br><span class="line">  ├─ 只读工具 ──→ 并行执行（最多 10 个并发）</span><br><span class="line">  │   ├─ Read      ──→ 同时开始</span><br><span class="line">  │   ├─ Grep      ──→ 同时开始</span><br><span class="line">  │   ├─ Glob      ──→ 同时开始</span><br><span class="line">  │   └─ WebFetch  ──→ 同时开始</span><br><span class="line">  │</span><br><span class="line">  └─ 写入工具 ──→ 串行执行（保证顺序）</span><br><span class="line">      ├─ FileEdit  ──→ 等待上一个完成</span><br><span class="line">      ├─ Write     ──→ 等待上一个完成</span><br><span class="line">      └─ Bash      ──→ 等待上一个完成</span><br></pre></td></tr></table></figure><p><strong>LangChain 的工具执行</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangChain Agent 默认串行执行</span></span><br><span class="line"><span class="keyword">for</span> tool_call <span class="keyword">in</span> parsed_tool_calls:</span><br><span class="line">    result = tool.run(tool_call.input)  <span class="comment"># 一个一个执行</span></span><br></pre></td></tr></table></figure><p>LangChain 需要显式配置并行，且配置复杂；Claude Code 自动分析工具性质，智能编排。</p><hr><h2 id="五、可控层面：全栈自研的精准控制"><a href="#五、可控层面：全栈自研的精准控制" class="headerlink" title="五、可控层面：全栈自研的精准控制"></a>五、可控层面：全栈自研的精准控制</h2><h3 id="5-1-框架黑盒问题"><a href="#5-1-框架黑盒问题" class="headerlink" title="5.1 框架黑盒问题"></a>5.1 框架黑盒问题</h3><p>使用 LangChain/LangGraph 时，你无法精准控制：</p><div class="table-container"><table><thead><tr><th>场景</th><th>LangChain 行为</th><th>你的控制力</th></tr></thead><tbody><tr><td>工具执行顺序</td><td>默认串行</td><td>需要显式配置</td></tr><tr><td>错误恢复</td><td>抛出异常</td><td>需要自己处理</td></tr><tr><td>Token 溢出</td><td>截断或报错</td><td>需要自己检测</td></tr><tr><td>提示词组装</td><td>模板拼接</td><td>无法精细控制</td></tr><tr><td>流式输出</td><td>部分支持</td><td>需要适配框架</td></tr></tbody></table></div><h3 id="5-2-Claude-Code-的精准控制"><a href="#5-2-Claude-Code-的精准控制" class="headerlink" title="5.2 Claude Code 的精准控制"></a>5.2 Claude Code 的精准控制</h3><p>Claude Code 自研每一层，可以精确控制：</p><p><strong>控制一：工具执行权限</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code 的权限系统（src/utils/permissions）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> PermissionResult = &#123;</span><br><span class="line">  behavior: <span class="string">'allow'</span> | <span class="string">'deny'</span> | <span class="string">'ask'</span></span><br><span class="line">  message?: <span class="built_in">string</span></span><br><span class="line">  suggestions?: <span class="built_in">string</span>[]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 精细的权限检查</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">checkPermissions</span>(<span class="params">tool, input, context</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 1. deny 规则最高优先级</span></span><br><span class="line">  <span class="keyword">if</span> (matchesDenyRule(tool.name)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'deny'</span>, message: <span class="string">'Blocked by deny rule'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 工具自定义检查</span></span><br><span class="line">  <span class="keyword">if</span> (tool.checkPermissions) &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> tool.checkPermissions(input, context)</span><br><span class="line">    <span class="keyword">if</span> (result.behavior !== <span class="string">'passthrough'</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> result</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. allow 规则</span></span><br><span class="line">  <span class="keyword">if</span> (matchesAllowRule(tool.name, input)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 默认询问用户</span></span><br><span class="line">  <span class="keyword">return</span> &#123; behavior: <span class="string">'ask'</span>, message: <span class="string">'Do you want to allow?'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>控制二：自动压缩策略</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code 的四级压缩（src/query）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Level 1: Snip — 删除旧消息中的冗余 token</span></span><br><span class="line">messages = snipMessages(messages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Level 2: Micro — 修改已缓存消息的内容</span></span><br><span class="line">messages = microCompact(messages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Level 3: Collapse — 分阶段摘要历史消息</span></span><br><span class="line">messages = collapseMessages(messages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Level 4: Auto Compact — 通过 Claude 生成完整摘要</span></span><br><span class="line">messages = <span class="keyword">await</span> autoCompact(messages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// LangChain 的处理方式：</span></span><br><span class="line"><span class="comment">// messages = messages.slice(-max_tokens)  // 简单截断</span></span><br></pre></td></tr></table></figure><p><strong>控制三：钩子扩展系统</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// settings.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">"hooks"</span>: &#123;</span><br><span class="line">    <span class="attr">"PreToolUse"</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">"matcher"</span>: <span class="string">"Bash"</span>,</span><br><span class="line">        <span class="attr">"hooks"</span>: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">"type"</span>: <span class="string">"command"</span>,</span><br><span class="line">            <span class="attr">"command"</span>: <span class="string">"security-check.sh"</span></span><br><span class="line">          &#125;</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">"PostToolUse"</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">"matcher"</span>: <span class="string">"FileEdit"</span>,</span><br><span class="line">        <span class="attr">"hooks"</span>: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">"type"</span>: <span class="string">"command"</span>,</span><br><span class="line">            <span class="attr">"command"</span>: <span class="string">"run-tests.sh"</span></span><br><span class="line">          &#125;</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用户可以在工具执行的任意阶段注入自定义逻辑，LangChain 需要继承类或修改源码才能实现类似功能。</p><hr><h2 id="六、能力映射：Claude-Code-用什么替代-LangChain-LangGraph"><a href="#六、能力映射：Claude-Code-用什么替代-LangChain-LangGraph" class="headerlink" title="六、能力映射：Claude Code 用什么替代 LangChain/LangGraph"></a>六、能力映射：Claude Code 用什么替代 LangChain/LangGraph</h2><h3 id="6-1-LangChain-能力-→-Claude-Code-替代方案"><a href="#6-1-LangChain-能力-→-Claude-Code-替代方案" class="headerlink" title="6.1 LangChain 能力 → Claude Code 替代方案"></a>6.1 LangChain 能力 → Claude Code 替代方案</h3><div class="table-container"><table><thead><tr><th>LangChain 能力</th><th>Claude Code 替代方案</th><th>实现文件</th></tr></thead><tbody><tr><td><strong>LLM 调用</strong></td><td>Anthropic SDK 直接集成</td><td><code>src/query.ts</code></td></tr><tr><td><strong>工具定义</strong></td><td><code>Tool</code> 类型 + <code>buildTool()</code></td><td><code>src/Tool.ts</code></td></tr><tr><td><strong>工具注册</strong></td><td>三阶段流水线注册</td><td><code>src/tools.ts</code></td></tr><tr><td><strong>Agent 循环</strong></td><td><code>while(true)</code> 状态机</td><td><code>src/query.ts</code></td></tr><tr><td><strong>Memory</strong></td><td>Channel 系统 + 文件记忆</td><td><code>src/state/</code>, <code>src/memdir/</code></td></tr><tr><td><strong>RAG</strong></td><td>文件工具 + 向量工具（可选 MCP）</td><td><code>src/tools/</code></td></tr><tr><td><strong>OutputParser</strong></td><td>原生 <code>tool_use</code> 块解析</td><td>无需解析</td></tr><tr><td><strong>Callbacks</strong></td><td>钩子系统</td><td><code>src/hooks/</code></td></tr></tbody></table></div><h3 id="6-2-LangGraph-能力-→-Claude-Code-替代方案"><a href="#6-2-LangGraph-能力-→-Claude-Code-替代方案" class="headerlink" title="6.2 LangGraph 能力 → Claude Code 替代方案"></a>6.2 LangGraph 能力 → Claude Code 替代方案</h3><div class="table-container"><table><thead><tr><th>LangGraph 能力</th><th>Claude Code 替代方案</th><th>实现文件</th></tr></thead><tbody><tr><td><strong>StateGraph</strong></td><td><code>State</code> 对象 + 状态赋值</td><td><code>src/query.ts</code></td></tr><tr><td><strong>节点定义</strong></td><td><code>while</code> 循环的阶段划分</td><td><code>src/query.ts:307-1728</code></td></tr><tr><td><strong>边流转</strong></td><td><code>transition</code> + <code>continue</code></td><td><code>src/query/transitions.ts</code></td></tr><tr><td><strong>条件分支</strong></td><td><code>if/switch</code> + <code>state.transition</code></td><td><code>src/query.ts</code></td></tr><tr><td><strong>Checkpoint</strong></td><td>消息历史 + 文件系统</td><td><code>src/assistant/</code></td></tr><tr><td><strong>多 Agent</strong></td><td><code>AgentTool</code> + 子代理系统</td><td><code>src/tools/AgentTool/</code></td></tr><tr><td><strong>可视化调试</strong></td><td><code>transition</code> 字段追踪</td><td>可观测性设计</td></tr></tbody></table></div><h3 id="6-3-核心代码映射"><a href="#6-3-核心代码映射" class="headerlink" title="6.3 核心代码映射"></a>6.3 核心代码映射</h3><p><strong>LangChain Agent → Claude Code query()</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangChain</span></span><br><span class="line">agent = AgentExecutor.from_agent_and_tools(agent, tools)</span><br><span class="line">result = agent.invoke(&#123;<span class="string">"input"</span>: <span class="string">"do something"</span>&#125;)</span><br></pre></td></tr></table></figure><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code</span></span><br><span class="line"><span class="keyword">for</span> <span class="keyword">await</span> (<span class="keyword">const</span> update of query(&#123; messages, tools, systemPrompt &#125;)) &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(update)  <span class="comment">// 实时输出</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>LangGraph StateGraph → Claude Code while 循环</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangGraph</span></span><br><span class="line">graph = StateGraph(AgentState)</span><br><span class="line">graph.add_node(<span class="string">"agent"</span>, agent_node)</span><br><span class="line">graph.add_node(<span class="string">"tool"</span>, tool_node)</span><br><span class="line">graph.add_conditional_edges(<span class="string">"agent"</span>, should_continue, </span><br><span class="line">    &#123;<span class="string">"continue"</span>: <span class="string">"tool"</span>, <span class="string">"end"</span>: END&#125;)</span><br><span class="line">app = graph.compile()</span><br></pre></td></tr></table></figure><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code（src/query.ts）</span></span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">  <span class="comment">// 阶段 2: 流式 API 调用</span></span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> callModel(state)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 阶段 3: 决策点（条件分支）</span></span><br><span class="line">  <span class="keyword">if</span> (hasToolUse(response)) &#123;</span><br><span class="line">    <span class="comment">// 阶段 4: 工具执行</span></span><br><span class="line">    <span class="keyword">const</span> results = <span class="keyword">await</span> executeTools(response.tool_use_blocks)</span><br><span class="line">    state = &#123; ...state, messages: [...messages, results] &#125;</span><br><span class="line">    <span class="keyword">continue</span>  <span class="comment">// 继续循环</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 结束</span></span><br><span class="line">    <span class="keyword">yield</span> finalResult</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、什么时候该用-LangChain-LangGraph"><a href="#七、什么时候该用-LangChain-LangGraph" class="headerlink" title="七、什么时候该用 LangChain/LangGraph"></a>七、什么时候该用 LangChain/LangGraph</h2><p>Claude Code 的自研架构不是所有人的最优解。它们的选择基于：</p><ol><li><strong>顶级工程团队</strong>：有能力自研高性能架构</li><li><strong>单一模型依赖</strong>：只需要支持 Anthropic API</li><li><strong>极致体验追求</strong>：CLI 交互需要零延迟感知</li><li><strong>深度定制需求</strong>：权限、压缩、钩子都需要精准控制</li></ol><p><strong>如果你不具备这些条件，LangChain/LangGraph 仍然是好选择</strong>：</p><div class="table-container"><table><thead><tr><th>你的情况</th><th>推荐</th></tr></thead><tbody><tr><td>小团队，快速验证想法</td><td>LangChain</td></tr><tr><td>需要支持多种模型</td><td>LangChain</td></tr><tr><td>需要可视化 Agent 流程</td><td>LangGraph</td></tr><tr><td>需要多 Agent 协作且不想自研</td><td>LangGraph 或 CrewAI</td></tr><tr><td>Web 应用，延迟不敏感</td><td>LangChain/LangGraph</td></tr><tr><td>企业级系统，有专业团队</td><td>LangGraph 或自研</td></tr></tbody></table></div><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Claude Code 不用 LangChain/LangGraph，不是傲慢，而是<strong>基于产品定位的理性选择</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">LangChain&#x2F;LangGraph 定位：通用框架</span><br><span class="line">  → 易用性优先</span><br><span class="line">  → 支持多种模型</span><br><span class="line">  → 抽象层统一</span><br><span class="line">  → 适合快速原型和通用应用</span><br><span class="line"></span><br><span class="line">Claude Code 定位：专用系统</span><br><span class="line">  → 性能优先</span><br><span class="line">  → 单一模型极致利用</span><br><span class="line">  → 零抽象流式处理</span><br><span class="line">  → 适合 CLI 交互和专业场景</span><br></pre></td></tr></table></figure><p><strong>Claude Code 用什么替代了 LangChain/LangGraph</strong>：</p><div class="table-container"><table><thead><tr><th>替代</th><th>技术</th></tr></thead><tbody><tr><td>ReAct 循环</td><td>Async Generator 状态机</td></tr><tr><td>工具定义</td><td>Tool 类型 + buildTool()</td></tr><tr><td>工具执行管道</td><td>七步执行管道</td></tr><tr><td>状态管理</td><td>State 对象 + 状态赋值</td></tr><tr><td>错误恢复</td><td>6 种内置恢复策略</td></tr><tr><td>扩展机制</td><td>钩子系统 + MCP 协议</td></tr></tbody></table></div><p><strong>核心启示</strong>：框架不是必须的，适合自己的才是最好的。LangChain/LangGraph 解决了”怎么快速搭建 Agent”的问题；Claude Code 解决了”怎么搭建极致体验的 Agent”的问题。</p><hr><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="/claude-code-architecture-overview/">Claude Code 源码揭秘：整体架构概览</a></li><li><a href="/claude-code-async-generator-state-machine/">打破 ReAct 迷思：Async Generator 状态机</a></li><li><a href="/claude-code-tool-system/">工具系统设计：从定义到执行的七步管道</a></li><li><a href="https://docs.anthropic.com/" target="_blank" rel="noopener">Anthropic API 文档</a></li><li><a href="https://python.langchain.com/docs/" target="_blank" rel="noopener">LangChain 官方文档</a></li><li><a href="https://langchain-ai.github.io/langgraph/" target="_blank" rel="noopener">LangGraph 官方文档</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;LangChain 和 LangGraph 是当下最流行的 Agent 开发框架，但 Anthropic 的 Claude Code 却完全不用它们。这不是傲慢，而是基于技术本质、产品体验、API 特性和工程可控性的四重考量。本文从多个层面剖析 C</summary>
      
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Architecture" scheme="https://donehub.github.io/tags/Architecture/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code 源码揭秘：整体架构概览</title>
    <link href="https://donehub.github.io/2026/04/07/claude-code-architecture-overview/"/>
    <id>https://donehub.github.io/2026/04/07/claude-code-architecture-overview/</id>
    <published>2026-04-06T16:00:00.000Z</published>
    <updated>2026-04-13T15:36:45.228Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2026年3月31日，Anthropic 的 Claude Code 源码意外泄露。这个全球最流行的 AI 编程助手，其背后的架构设计远超外界想象——它不是简单的 Prompt 包装，而是一个精心设计的流式状态机系统。本文将从整体架构视角，为你揭开 Claude Code 的神秘面纱。</p></blockquote><a id="more"></a><h2 id="导读：一个根本性的问题"><a href="#导读：一个根本性的问题" class="headerlink" title="导读：一个根本性的问题"></a>导读：一个根本性的问题</h2><p>在深入源码之前，我们需要回答一个根本性的问题：<strong>Claude Code 到底是什么？</strong></p><p>很多人的第一反应是：”不就是一个调用 Claude API 的 CLI 工具吗？加了一些 Prompt，让模型能读写文件、执行命令。”</p><p>这种理解大大低估了 Claude Code 的复杂度。当你打开源码，会发现：</p><ul><li><strong>1356 个 TypeScript 文件</strong></li><li><strong>48+ 个内置工具</strong>，每个工具都有完整的生命周期管理</li><li><strong>4 种 Agent 类型</strong>，支持复杂的协作编排</li><li><strong>4 级上下文压缩</strong>，实现”无限对话”</li><li><strong>6 种故障恢复策略</strong>，确保用户体验的稳定性</li><li><strong>3 级提示词缓存</strong>，大幅降低成本和延迟</li></ul><p>这不是一个”简单的 Prompt 工具”，而是一个<strong>深度集成的 AI 编程环境</strong>。</p><hr><h2 id="一、技术栈与项目结构"><a href="#一、技术栈与项目结构" class="headerlink" title="一、技术栈与项目结构"></a>一、技术栈与项目结构</h2><h3 id="1-1-核心技术栈"><a href="#1-1-核心技术栈" class="headerlink" title="1.1 核心技术栈"></a>1.1 核心技术栈</h3><p>Claude Code 的技术选型非常精简但高效：</p><div class="table-container"><table><thead><tr><th>类别</th><th>技术</th><th>说明</th></tr></thead><tbody><tr><td>运行时</td><td><a href="https://bun.sh" target="_blank" rel="noopener">Bun</a></td><td>高性能 JavaScript 运行时</td></tr><tr><td>语言</td><td>TypeScript</td><td>类型安全</td></tr><tr><td>终端 UI</td><td>React + <a href="https://github.com/vadimdemedes/ink" target="_blank" rel="noopener">Ink</a></td><td>React 语法写终端应用</td></tr><tr><td>CLI 解析</td><td>Commander.js</td><td>命令行参数处理</td></tr><tr><td>API</td><td>Anthropic SDK</td><td>原生 API 集成</td></tr><tr><td>协议</td><td>MCP, LSP</td><td>模型上下文协议、语言服务器协议</td></tr></tbody></table></div><h3 id="1-2-目录结构概览"><a href="#1-2-目录结构概览" class="headerlink" title="1.2 目录结构概览"></a>1.2 目录结构概览</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">src&#x2F;</span><br><span class="line">├── assistant&#x2F;          # 会话历史管理</span><br><span class="line">├── bootstrap&#x2F;          # 启动初始化、全局状态</span><br><span class="line">├── bridge&#x2F;             # 远程桥接系统（Bridge）</span><br><span class="line">├── buddy&#x2F;              # 交互伴侣（动画、观察者）</span><br><span class="line">├── cli&#x2F;                # CLI 入口、传输层</span><br><span class="line">├── commands&#x2F;           # 60+ 斜杠命令</span><br><span class="line">├── components&#x2F;         # React UI 组件</span><br><span class="line">├── constants&#x2F;          # 系统提示词、常量</span><br><span class="line">├── context&#x2F;            # 上下文管理</span><br><span class="line">├── coordinator&#x2F;        # 协调器模式</span><br><span class="line">├── entrypoints&#x2F;        # 入口文件（CLI、SDK）</span><br><span class="line">├── hooks&#x2F;              # React Hooks</span><br><span class="line">├── ink&#x2F;                # Ink 框架扩展</span><br><span class="line">├── memdir&#x2F;             # 记忆系统</span><br><span class="line">├── migrations&#x2F;         # 数据迁移</span><br><span class="line">├── native-ts&#x2F;          # 原生模块（Yoga 布局等）</span><br><span class="line">├── outputStyles&#x2F;       # 输出样式配置</span><br><span class="line">├── plugins&#x2F;            # 插件系统</span><br><span class="line">├── proactive&#x2F;          # 主动模式</span><br><span class="line">├── query&#x2F;              # 查询循环核心</span><br><span class="line">├── remote&#x2F;             # 远程会话管理</span><br><span class="line">├── schemas&#x2F;            # JSON Schema 定义</span><br><span class="line">├── screens&#x2F;            # 全屏页面</span><br><span class="line">├── server&#x2F;             # 内置服务器</span><br><span class="line">├── services&#x2F;           # 核心服务层</span><br><span class="line">├── skills&#x2F;             # Skills 系统</span><br><span class="line">├── state&#x2F;              # 状态管理</span><br><span class="line">├── tasks&#x2F;              # 后台任务系统</span><br><span class="line">├── tools&#x2F;              # 48+ 内置工具</span><br><span class="line">├── types&#x2F;              # TypeScript 类型定义</span><br><span class="line">├── utils&#x2F;              # 工具函数</span><br><span class="line">├── vendor&#x2F;             # 第三方集成（Computer Use）</span><br><span class="line">├── vim&#x2F;                # Vim 模式</span><br><span class="line">└── voice&#x2F;              # 语音模式</span><br></pre></td></tr></table></figure><p>这个结构体现了<strong>模块化设计</strong>的精髓：每个目录职责清晰，边界明确。</p><hr><h2 id="二、核心架构设计"><a href="#二、核心架构设计" class="headerlink" title="二、核心架构设计"></a>二、核心架构设计</h2><h3 id="2-1-整体架构图"><a href="#2-1-整体架构图" class="headerlink" title="2.1 整体架构图"></a>2.1 整体架构图</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                      用户输入                                │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line">                              │</span><br><span class="line">                              ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                   QueryEngine (入口)                         │</span><br><span class="line">│  - 构建系统提示词 (prompts.ts + context.ts + claudemd.ts)   │</span><br><span class="line">│  - 组装工具池 (tools.ts + MCP)                              │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line">                              │</span><br><span class="line">                              ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│              query() AsyncGenerator 循环                    │</span><br><span class="line">│  ┌──────────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 阶段1: 消息压缩 (snip → micro → collapse → compact)  │  │</span><br><span class="line">│  │ 阶段2: 流式 API 调用 (callModel + StreamingToolExec) │  │</span><br><span class="line">│  │ 阶段3: 决策点 (继续 or 完成)                          │  │</span><br><span class="line">│  │ 阶段4: 工具编排 (并行只读 + 串行写入)                 │  │</span><br><span class="line">│  │ 阶段5: 状态更新 (state &#x3D; next → continue)            │  │</span><br><span class="line">│  └──────────────────────────────────────────────────────┘  │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line">                              │</span><br><span class="line">          ┌───────────────────┼───────────────────┐</span><br><span class="line">          ▼                   ▼                   ▼</span><br><span class="line">┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐</span><br><span class="line">│    工具系统      │  │   多 Agent 系统  │  │   扩展生态      │</span><br><span class="line">│  48+ 内置工具    │  │  Subagent       │  │  Skills         │</span><br><span class="line">│  MCP 动态工具    │  │  Fork           │  │  Plugins        │</span><br><span class="line">│  三层过滤机制    │  │  Teammate       │  │  Hooks           │</span><br><span class="line">│  7步执行管道     │  │  Remote         │  │  MCP 协议        │</span><br><span class="line">└─────────────────┘  └─────────────────┘  └─────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-2-核心设计理念"><a href="#2-2-核心设计理念" class="headerlink" title="2.2 核心设计理念"></a>2.2 核心设计理念</h3><p>Claude Code 的架构体现了三个核心理念：</p><h4 id="理念一：流式优先（Streaming-First）"><a href="#理念一：流式优先（Streaming-First）" class="headerlink" title="理念一：流式优先（Streaming First）"></a>理念一：流式优先（Streaming First）</h4><p>整个架构围绕 <code>AsyncGenerator</code> 设计，一切都是流式的：</p><ul><li>模型响应是流式的</li><li>工具在模型生成过程中就开始执行</li><li>进度实时更新</li><li>压缩策略是渐进式的</li></ul><p>这意味着用户<strong>永远不需要等待</strong>——看到模型在思考、工具在执行、结果在产出。</p><h4 id="理念二：工具驱动（Tool-Driven）"><a href="#理念二：工具驱动（Tool-Driven）" class="headerlink" title="理念二：工具驱动（Tool-Driven）"></a>理念二：工具驱动（Tool-Driven）</h4><p>Claude Code 的哲学是：<strong>Agent 的能力等于其工具的能力</strong>。</p><ul><li>子代理生成？是一个工具（<code>AgentTool</code>）</li><li>团队管理？是一个工具（<code>TeamCreate</code>/<code>SendMessage</code>）</li><li>文件编辑？是一个工具（<code>FileEdit</code>）</li><li>技能执行？是一个工具（<code>SkillTool</code>）</li></ul><p>这意味着<strong>所有能力都通过统一的工具接口暴露</strong>，模型通过自然语言推理来决定使用哪个工具。不需要显式的编排逻辑——模型本身就是编排器。</p><h4 id="理念三：优雅降级（Graceful-Degradation）"><a href="#理念三：优雅降级（Graceful-Degradation）" class="headerlink" title="理念三：优雅降级（Graceful Degradation）"></a>理念三：优雅降级（Graceful Degradation）</h4><p>6 种恢复策略确保 Claude Code <strong>几乎不会因为技术问题中断用户的工作流</strong>：</p><ul><li>Token 超限？自动压缩</li><li>API 超时？自动重试</li><li>模型失败？降级到备用模型</li><li>工具失败？记录错误，继续对话</li></ul><hr><h2 id="三、核心模块解析"><a href="#三、核心模块解析" class="headerlink" title="三、核心模块解析"></a>三、核心模块解析</h2><h3 id="3-1-query-ts-Agent-的心脏"><a href="#3-1-query-ts-Agent-的心脏" class="headerlink" title="3.1 query.ts - Agent 的心脏"></a>3.1 query.ts - Agent 的心脏</h3><p><code>src/query.ts</code> 是整个 Agent 的核心，约 1730 行。它不是简单的”想-做-看”循环，而是一个<strong>流式状态机</strong>：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span>* <span class="title">query</span>(<span class="params">params: QueryParams</span>): <span class="title">AsyncGenerator</span>&lt;...&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> state: State = &#123;</span><br><span class="line">    messages,</span><br><span class="line">    toolUseContext,</span><br><span class="line">    autoCompactTracking,</span><br><span class="line">    maxOutputTokensRecoveryCount,</span><br><span class="line">    hasAttemptedReactiveCompact,</span><br><span class="line">    maxOutputTokensOverride,</span><br><span class="line">    pendingToolUseSummary,</span><br><span class="line">    stopHookActive,</span><br><span class="line">    turnCount,</span><br><span class="line">    transition,</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 阶段1: 消息压缩</span></span><br><span class="line">    <span class="comment">// 阶段2: 流式 API 调用</span></span><br><span class="line">    <span class="comment">// 阶段3: 决策点</span></span><br><span class="line">    <span class="comment">// 阶段4: 工具执行</span></span><br><span class="line">    <span class="comment">// 阶段5: 状态更新</span></span><br><span class="line">    state = next  <span class="comment">// 通过赋值而非递归驱动循环</span></span><br><span class="line">    <span class="keyword">continue</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键设计</strong>：通过 <code>state = next</code> 赋值驱动循环，而非递归调用。这保证了：</p><ul><li><strong>内存稳定</strong>：不会因为深度递归导致栈溢出</li><li><strong>状态可追溯</strong>：每一轮的状态转换原因都被记录</li><li><strong>恢复可控</strong>：任何阶段的错误都可以通过修改 state 来恢复</li></ul><h3 id="3-2-Tool-ts-工具的定义"><a href="#3-2-Tool-ts-工具的定义" class="headerlink" title="3.2 Tool.ts - 工具的定义"></a>3.2 Tool.ts - 工具的定义</h3><p><code>src/Tool.ts</code> 定义了工具的完整接口（约 792 行）：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Tool&lt;Input, Output&gt; = &#123;</span><br><span class="line">  <span class="comment">// 身份</span></span><br><span class="line">  name: <span class="built_in">string</span></span><br><span class="line">  aliases?: <span class="built_in">string</span>[]        <span class="comment">// 向后兼容的旧名称</span></span><br><span class="line">  searchHint?: <span class="built_in">string</span>       <span class="comment">// ToolSearch 关键词匹配</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 能力声明</span></span><br><span class="line">  isEnabled(): <span class="built_in">boolean</span></span><br><span class="line">  isConcurrencySafe(input): <span class="built_in">boolean</span>   <span class="comment">// 是否可并行</span></span><br><span class="line">  isReadOnly(input): <span class="built_in">boolean</span>          <span class="comment">// 是否只读</span></span><br><span class="line">  isDestructive(input): <span class="built_in">boolean</span>       <span class="comment">// 是否破坏性</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 生命周期</span></span><br><span class="line">  validateInput(input, context)       <span class="comment">// 输入验证</span></span><br><span class="line">  checkPermissions(input, context)    <span class="comment">// 权限检查</span></span><br><span class="line">  call(input, context, ...)           <span class="comment">// 实际执行</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 输出与渲染</span></span><br><span class="line">  renderToolUseMessage(input)         <span class="comment">// 渲染调用信息</span></span><br><span class="line">  renderToolResultMessage(content)    <span class="comment">// 渲染结果信息</span></span><br><span class="line">  mapToolResultToToolResultBlockParam()  <span class="comment">// 映射为 API 格式</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 智能特性</span></span><br><span class="line">  inputSchema: Zod schema             <span class="comment">// Zod 类型验证</span></span><br><span class="line">  maxResultSizeChars: <span class="built_in">number</span>           <span class="comment">// 结果大小阈值</span></span><br><span class="line">  getToolUseSummary?(input): <span class="built_in">string</span>    <span class="comment">// 工具使用摘要</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种设计使得每个工具都是<strong>自描述、自验证、自渲染</strong>的——框架不需要了解工具的内部逻辑，只需调用标准接口。</p><h3 id="3-3-系统提示词组装"><a href="#3-3-系统提示词组装" class="headerlink" title="3.3 系统提示词组装"></a>3.3 系统提示词组装</h3><p><code>src/constants/prompts.ts</code>（约 577 行）实现了<strong>分层管道</strong>动态组装系统提示词：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    静态可缓存区域                              │</span><br><span class="line">│  ┌───────────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 角色定义  │  系统规则  │  任务指导  │  工具说明  │  风格  │  │</span><br><span class="line">│  └───────────────────────────────────────────────────────┘  │</span><br><span class="line">├─────────────────────── 缓存边界 ────────────────────────────┤</span><br><span class="line">│                    动态可变区域                                │</span><br><span class="line">│  ┌───────────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 会话指引 │ 记忆系统 │ 环境信息 │ MCP 指令 │ Token 预算 │  │</span><br><span class="line">│  └───────────────────────────────────────────────────────┘  │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p><strong>缓存边界</strong>是一个关键设计：</p><ul><li><strong>边界之上</strong>：跨用户、跨组织通用的内容，使用 <code>scope: &#39;global&#39;</code> 缓存</li><li><strong>边界之下</strong>：用户/会话特定的内容，使用 <code>scope: &#39;ephemeral&#39;</code> 缓存</li></ul><hr><h2 id="四、与-LangChain-ReAct-的本质区别"><a href="#四、与-LangChain-ReAct-的本质区别" class="headerlink" title="四、与 LangChain/ReAct 的本质区别"></a>四、与 LangChain/ReAct 的本质区别</h2><p>这是理解 Claude Code 架构的关键。大多数人认为 Claude Code 使用的是经典的 <strong>ReAct</strong> 模式：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">思考(Thought) → 行动(Action) → 观察(Observation) → 思考 → ...</span><br></pre></td></tr></table></figure><p><strong>实际上，Claude Code 没有采用这个模式。</strong></p><h3 id="4-1-架构范式对比"><a href="#4-1-架构范式对比" class="headerlink" title="4.1 架构范式对比"></a>4.1 架构范式对比</h3><div class="table-container"><table><thead><tr><th>维度</th><th>LangChain</th><th>Claude Code</th></tr></thead><tbody><tr><td><strong>核心模式</strong></td><td>ReAct（Think→Act→Observe）</td><td>Async Generator 状态机</td></tr><tr><td><strong>执行模型</strong></td><td>同步阻塞</td><td>流式非阻塞</td></tr><tr><td><strong>工具执行</strong></td><td>等待模型完整响应后执行</td><td>流式传输中即时执行</td></tr><tr><td><strong>状态管理</strong></td><td>外部 Memory 对象</td><td>内置状态赋值 + 循环</td></tr><tr><td><strong>错误恢复</strong></td><td>需要手动编排</td><td>6 种内置恢复策略</td></tr><tr><td><strong>上下文压缩</strong></td><td>简单截断或摘要</td><td>四级渐进式压缩</td></tr><tr><td><strong>多 Agent</strong></td><td>Chain/Graph 显式编排</td><td>统一工具接口 + 状态机</td></tr><tr><td><strong>扩展机制</strong></td><td>Python 类继承</td><td>技能 + 插件 + 钩子 + MCP</td></tr><tr><td><strong>缓存策略</strong></td><td>无</td><td>全局/会话/按轮三级缓存</td></tr></tbody></table></div><h3 id="4-2-为什么不用-ReAct？"><a href="#4-2-为什么不用-ReAct？" class="headerlink" title="4.2 为什么不用 ReAct？"></a>4.2 为什么不用 ReAct？</h3><p>ReAct 模式有几个固有限制：</p><ol><li><strong>串行瓶颈</strong>：每一步必须等待完整的”思考→行动→观察”循环</li><li><strong>无流式能力</strong>：模型生成完整响应后才能开始执行工具</li><li><strong>恢复困难</strong>：没有统一的状态表示，难以实现自动恢复</li><li><strong>缓存不友好</strong>：每次循环的 prompt 结构变化大，难以利用缓存</li></ol><p>Claude Code 的 Async Generator 模式解决了所有这些问题：</p><ul><li><strong>流式执行</strong>：工具在模型生成过程中就开始运行</li><li><strong>状态可控</strong>：<code>State</code> 对象包含所有需要的信息，恢复只需修改状态</li><li><strong>缓存优化</strong>：静态提示词全局缓存，动态部分最小化</li><li><strong>并行能力</strong>：只读工具自动并行，写入工具串行保序</li></ul><hr><h2 id="五、关键源文件索引"><a href="#五、关键源文件索引" class="headerlink" title="五、关键源文件索引"></a>五、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>组件</th><th>文件路径</th><th>行数</th><th>说明</th></tr></thead><tbody><tr><td>核心循环</td><td><code>src/query.ts</code></td><td>~1730</td><td>Agent 主循环</td></tr><tr><td>查询引擎</td><td><code>src/QueryEngine.ts</code></td><td>~687</td><td>高层封装</td></tr><tr><td>工具定义</td><td><code>src/Tool.ts</code></td><td>~792</td><td>Tool 类型系统</td></tr><tr><td>工具注册</td><td><code>src/tools.ts</code></td><td>~389</td><td>工具发现和注册</td></tr><tr><td>系统提示词</td><td><code>src/constants/prompts.ts</code></td><td>~577</td><td>提示词组装</td></tr><tr><td>上下文管理</td><td><code>src/context.ts</code></td><td>~300</td><td>系统/用户上下文</td></tr><tr><td>Agent 生成</td><td><code>src/tools/AgentTool/AgentTool.tsx</code></td><td>~600</td><td>Agent 工具入口</td></tr><tr><td>技能系统</td><td><code>src/skills/bundledSkills.ts</code></td><td>~300</td><td>技能注册与管理</td></tr><tr><td>权限系统</td><td><code>src/utils/permissions/permissions.ts</code></td><td>~500</td><td>权限检查</td></tr><tr><td>状态管理</td><td><code>src/state/AppStateStore.ts</code></td><td>~400</td><td>全局状态</td></tr></tbody></table></div><hr><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>Claude Code 的架构设计体现了<strong>简洁与强大的平衡</strong>：</p><ol><li><strong>一个循环</strong>：<code>while (true)</code> 驱动的状态机</li><li><strong>一个状态</strong>：<code>State</code> 对象承载所有上下文</li><li><strong>一个接口</strong>：<code>Tool</code> 类型统一所有能力</li></ol><p>没有 Agent → AgentExecutor → Chain → Memory → Callback 的嵌套抽象层，这使得代码<strong>易于理解、调试和扩展</strong>。</p><p>在接下来的系列文章中，我们将深入每个模块，揭示更多设计细节。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>下一篇：<a href="/claude-code-async-generator-state-machine/">打破 ReAct 迷思：Async Generator 状态机</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;2026年3月31日，Anthropic 的 Claude Code 源码意外泄露。这个全球最流行的 AI 编程助手，其背后的架构设计远超外界想象——它不是简单的 Prompt 包装，而是一个精心设计的流式状态机系统。本文将从整体架构视角，为你揭开 Claude Code 的神秘面纱。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Architecture" scheme="https://donehub.github.io/tags/Architecture/"/>
    
  </entry>
  
  <entry>
    <title>打破 ReAct 迷思：Async Generator 状态机</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-async-generator-state-machine/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-async-generator-state-machine/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:21.137Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>当大多数人谈论 AI Agent 架构时，ReAct（Reasoning + Acting）几乎是唯一的答案。但 Claude Code 选择了一条不同的路——Async Generator 状态机。这个设计决策背后有着深刻的思考，它解决了 ReAct 的根本性限制，为流式交互和优雅恢复奠定了基础。</p></blockquote><a id="more"></a><h2 id="导读：ReAct-的困境"><a href="#导读：ReAct-的困境" class="headerlink" title="导读：ReAct 的困境"></a>导读：ReAct 的困境</h2><p>如果你熟悉 AI Agent 开发，一定对 ReAct 模式不陌生：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">思考(Thought) → 行动(Action) → 观察(Observation) → 思考 → ...</span><br></pre></td></tr></table></figure><p>这个模式直观且易于理解，已经成为 LangChain、AutoGPT 等框架的标配。但当你深入使用时，会发现它有几个难以回避的问题：</p><p><strong>问题一：串行瓶颈</strong><br>每一轮”思考”必须等待模型生成完整响应后才能开始执行工具。用户盯着屏幕等待，体验割裂。</p><p><strong>问题二：无法利用流式传输</strong><br>模型支持流式输出，但 ReAct 模式下，流式传输的价值被大大削弱——你必须等待完整的 action 才能执行。</p><p><strong>问题三：恢复困难</strong><br>当 API 超时、Token 溢出或工具失败时，ReAct 没有统一的状态表示来支持自动恢复。</p><p>Claude Code 的解决方案是：<strong>放弃 ReAct，使用 Async Generator 状态机</strong>。</p><hr><h2 id="一、状态机的核心设计"><a href="#一、状态机的核心设计" class="headerlink" title="一、状态机的核心设计"></a>一、状态机的核心设计</h2><h3 id="1-1-State-数据结构"><a href="#1-1-State-数据结构" class="headerlink" title="1.1 State 数据结构"></a>1.1 State 数据结构</h3><p><code>src/query.ts</code> 定义了状态机的核心状态（第 204-217 行）：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> State = &#123;</span><br><span class="line">  messages: Message[]                    <span class="comment">// 完整对话历史</span></span><br><span class="line">  toolUseContext: ToolUseContext          <span class="comment">// 工具执行上下文</span></span><br><span class="line">  autoCompactTracking: AutoCompactTracking  <span class="comment">// 自动压缩追踪</span></span><br><span class="line">  maxOutputTokensRecoveryCount: <span class="built_in">number</span>   <span class="comment">// 输出恢复计数</span></span><br><span class="line">  hasAttemptedReactiveCompact: <span class="built_in">boolean</span>   <span class="comment">// 是否已尝试反应式压缩</span></span><br><span class="line">  maxOutputTokensOverride: <span class="built_in">number</span>        <span class="comment">// 输出 token 覆盖值</span></span><br><span class="line">  pendingToolUseSummary: <span class="built_in">Promise</span>&lt;...&gt;    <span class="comment">// 待处理的工具摘要</span></span><br><span class="line">  stopHookActive: <span class="built_in">boolean</span>               <span class="comment">// 停止钩子状态</span></span><br><span class="line">  turnCount: <span class="built_in">number</span>                      <span class="comment">// 对话轮数</span></span><br><span class="line">  transition: Continue | <span class="literal">undefined</span>       <span class="comment">// 状态转换原因</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键洞察</strong>：<code>transition</code> 字段记录了每一轮状态转换的原因。这使得调试和测试变得非常清晰——你可以精确知道为什么 Agent 从一个状态跳转到另一个状态。</p><h3 id="1-2-核心循环：五个阶段"><a href="#1-2-核心循环：五个阶段" class="headerlink" title="1.2 核心循环：五个阶段"></a>1.2 核心循环：五个阶段</h3><p>整个 <code>while (true)</code> 循环（第 307-1728 行）分为五个阶段：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                      while (true)                           │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段1: 消息准备与智能压缩（第 365-543 行）                  │</span><br><span class="line">│    ├─ Snip 压缩：智能删除旧消息中的冗余 token               │</span><br><span class="line">│    ├─ Micro 压缩：修改已缓存消息的内容                      │</span><br><span class="line">│    ├─ 上下文折叠：分阶段摘要历史消息                        │</span><br><span class="line">│    └─ Auto Compact：通过 Claude 生成完整摘要                │</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段2: 流式 API 调用（第 652-954 行）                       │</span><br><span class="line">│    ├─ 构建 API 请求（含 CacheSafeParams）                   │</span><br><span class="line">│    ├─ 流式处理响应                                          │</span><br><span class="line">│    ├─ StreamingToolExecutor 即时执行工具                    │</span><br><span class="line">│    └─ 累积 usage 指标                                       │</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段3: 决策点（第 1062-1358 行）                            │</span><br><span class="line">│    ├─ 有工具调用？→ 继续循环（阶段 4）                      │</span><br><span class="line">│    └─ 无工具调用？→ 运行 Stop 钩子 → 返回结果               │</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段4: 工具编排执行（第 1363-1409 行）                      │</span><br><span class="line">│    ├─ 分区：只读 vs 写入                                    │</span><br><span class="line">│    ├─ 只读工具 → 并行执行（最多 10 个并发）                 │</span><br><span class="line">│    └─ 写入工具 → 串行执行（防止竞态条件）                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段5: 状态更新与循环（第 1704-1728 行）                    │</span><br><span class="line">│    └─ state &#x3D; next → continue                              │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-3-状态更新的优雅之处"><a href="#1-3-状态更新的优雅之处" class="headerlink" title="1.3 状态更新的优雅之处"></a>1.3 状态更新的优雅之处</h3><p>这是整个设计最优雅的部分——<strong>通过状态赋值而非递归调用驱动循环</strong>：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/query.ts:1715-1728</span></span><br><span class="line"><span class="keyword">const</span> next: State = &#123;</span><br><span class="line">  messages: [...messagesForQuery, ...assistantMessages, ...toolResults],</span><br><span class="line">  toolUseContext: toolUseContextWithQueryTracking,</span><br><span class="line">  autoCompactTracking: tracking,</span><br><span class="line">  turnCount: nextTurnCount,</span><br><span class="line">  transition: &#123; reason: <span class="string">'next_turn'</span> &#125;,</span><br><span class="line">&#125;</span><br><span class="line">state = next</span><br><span class="line"><span class="comment">// 回到 while(true) 循环顶部</span></span><br></pre></td></tr></table></figure><p>没有递归，没有回调地狱，只是简单的 <code>state = next</code> 然后 <code>continue</code>。</p><p><strong>为什么这很重要？</strong></p><ol><li><strong>内存稳定</strong>：不会因为深度递归导致栈溢出</li><li><strong>状态可追溯</strong>：每一轮的状态转换原因都被记录</li><li><strong>恢复可控</strong>：任何阶段的错误都可以通过修改 state 来恢复</li></ol><hr><h2 id="二、流式优先的执行模型"><a href="#二、流式优先的执行模型" class="headerlink" title="二、流式优先的执行模型"></a>二、流式优先的执行模型</h2><h3 id="2-1-StreamingToolExecutor-的设计"><a href="#2-1-StreamingToolExecutor-的设计" class="headerlink" title="2.1 StreamingToolExecutor 的设计"></a>2.1 StreamingToolExecutor 的设计</h3><p>Claude Code 的一个关键创新是 <code>StreamingToolExecutor</code>——当模型生成 <code>tool_use</code> 块时，工具<strong>立即</strong>开始运行，而不是等模型生成完整响应。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/tools/StreamingToolExecutor.ts</span></span><br><span class="line"><span class="keyword">class</span> StreamingToolExecutor &#123;</span><br><span class="line">  <span class="keyword">async</span> *processToolUseBlocks(toolUseBlocks: ToolUseBlock[]): AsyncGenerator &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> block of toolUseBlocks) &#123;</span><br><span class="line">      <span class="comment">// 在流式传输过程中就开始执行</span></span><br><span class="line">      <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="keyword">this</span>.executeTool(block)</span><br><span class="line">      <span class="keyword">yield</span> result</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>对比 ReAct</strong>：</p><div class="table-container"><table><thead><tr><th>模式</th><th>工具执行时机</th><th>用户体验</th></tr></thead><tbody><tr><td>ReAct</td><td>等待模型完整响应</td><td>割裂，需要等待</td></tr><tr><td>Async Generator</td><td>流式传输中即时执行</td><td>流畅，实时反馈</td></tr></tbody></table></div><h3 id="2-2-工具编排策略"><a href="#2-2-工具编排策略" class="headerlink" title="2.2 工具编排策略"></a>2.2 工具编排策略</h3><p>工具执行不是简单的逐个运行，而是有精心设计的<strong>编排策略</strong>（<code>src/services/tools/toolOrchestration.ts</code>）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">工具调用列表</span><br><span class="line">  │</span><br><span class="line">  ├─ 分区：只读 vs 写入</span><br><span class="line">  │</span><br><span class="line">  ├─ 只读工具 ──→ 并行执行（最多 10 个并发）</span><br><span class="line">  │   ├─ Read</span><br><span class="line">  │   ├─ Grep</span><br><span class="line">  │   ├─ Glob</span><br><span class="line">  │   └─ WebFetch</span><br><span class="line">  │</span><br><span class="line">  └─ 写入工具 ──→ 串行执行（防止竞态条件）</span><br><span class="line">      ├─ FileEdit</span><br><span class="line">      ├─ FileWrite</span><br><span class="line">      └─ Bash (非只读)</span><br></pre></td></tr></table></figure><p><strong>设计原理</strong>：</p><ul><li>只读工具没有副作用，可以安全并行</li><li>写入工具可能相互影响，必须串行保证顺序</li><li>10 个并发限制防止资源耗尽</li></ul><hr><h2 id="三、六种故障恢复策略"><a href="#三、六种故障恢复策略" class="headerlink" title="三、六种故障恢复策略"></a>三、六种故障恢复策略</h2><p>这是 Claude Code 最精妙的设计之一。核心循环内置了 <strong>6 种恢复策略</strong>，确保用户体验的稳定性：</p><h3 id="3-1-恢复策略详解"><a href="#3-1-恢复策略详解" class="headerlink" title="3.1 恢复策略详解"></a>3.1 恢复策略详解</h3><div class="table-container"><table><thead><tr><th>恢复策略</th><th>触发条件</th><th>恢复方式</th></tr></thead><tbody><tr><td><code>collapse_drain_retry</code></td><td>prompt 过长</td><td>排空已暂存的上下文折叠，重试</td></tr><tr><td><code>reactive_compact_retry</code></td><td>仍然过长</td><td>通过 Claude 生成摘要，重试</td></tr><tr><td><code>max_output_tokens_escalate</code></td><td>触及 8k 默认限制</td><td>升级到 64k 限制重试</td></tr><tr><td><code>max_output_tokens_recovery</code></td><td>触及任何限制</td><td>注入”继续”提示，重试（最多 3 次）</td></tr><tr><td><code>stop_hook_blocking</code></td><td>Stop 钩子阻塞</td><td>将阻塞错误注入上下文，重试</td></tr><tr><td><code>token_budget_continuation</code></td><td>预算尚余</td><td>注入预算提示，继续执行</td></tr></tbody></table></div><h3 id="3-2-恢复代码示例"><a href="#3-2-恢复代码示例" class="headerlink" title="3.2 恢复代码示例"></a>3.2 恢复代码示例</h3><p>每种恢复都通过修改 <code>state</code> 实现：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 例：prompt 过长恢复</span></span><br><span class="line"><span class="keyword">if</span> (error.type === <span class="string">'prompt_too_long'</span>) &#123;</span><br><span class="line">  <span class="comment">// 排空所有暂存的折叠</span></span><br><span class="line">  <span class="keyword">const</span> compacted = drainStagedCollapses(state.messages)</span><br><span class="line">  state = &#123; </span><br><span class="line">    ...state, </span><br><span class="line">    messages: compacted, </span><br><span class="line">    transition: &#123; reason: <span class="string">'collapse_drain_retry'</span> &#125; </span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">continue</span>  <span class="comment">// 回到循环顶部重试</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 例：max_output_tokens 恢复</span></span><br><span class="line"><span class="keyword">if</span> (error.type === <span class="string">'max_output_tokens'</span>) &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    ...state,</span><br><span class="line">    maxOutputTokensRecoveryCount: state.maxOutputTokensRecoveryCount + <span class="number">1</span>,</span><br><span class="line">    transition: &#123; reason: <span class="string">'max_output_tokens_recovery'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 注入"继续"提示</span></span><br><span class="line">  messages.push(createUserMessage(&#123; content: <span class="string">'Please continue.'</span> &#125;))</span><br><span class="line">  <span class="keyword">continue</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-为什么这些恢复策略重要？"><a href="#3-3-为什么这些恢复策略重要？" class="headerlink" title="3.3 为什么这些恢复策略重要？"></a>3.3 为什么这些恢复策略重要？</h3><p>想象一个场景：用户正在让 Claude 修改一个大型代码库，对话已经进行了 50 轮，积累了大量上下文。突然：</p><ol><li><strong>Token 溢出</strong> → 自动压缩，用户无感知</li><li><strong>API 超时</strong> → 自动重试，用户无感知</li><li><strong>模型达到输出限制</strong> → 注入”继续”，自动续写</li></ol><p>用户几乎感觉不到任何中断。这是 Claude Code 能提供流畅体验的关键。</p><hr><h2 id="四、与-LangChain-Agent-的具体差异"><a href="#四、与-LangChain-Agent-的具体差异" class="headerlink" title="四、与 LangChain Agent 的具体差异"></a>四、与 LangChain Agent 的具体差异</h2><h3 id="4-1-代码对比"><a href="#4-1-代码对比" class="headerlink" title="4.1 代码对比"></a>4.1 代码对比</h3><p><strong>LangChain Agent（简化）：</strong><br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">agent = initialize_agent(tools, llm, agent=<span class="string">"zero-shot-react-description"</span>)</span><br><span class="line">result = agent.run(<span class="string">"do something"</span>)</span><br><span class="line"><span class="comment"># 内部：LLM → parse → tool → LLM → parse → tool → ... → final answer</span></span><br><span class="line"><span class="comment"># 每一步都是独立的 LLM 调用</span></span><br></pre></td></tr></table></figure></p><p><strong>Claude Code Agent（简化）：</strong><br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> <span class="keyword">await</span> (<span class="keyword">const</span> msg of query(&#123; messages, tools, systemPrompt &#125;)) &#123;</span><br><span class="line">  <span class="keyword">yield</span> msg  <span class="comment">// 实时产出消息</span></span><br><span class="line">  <span class="comment">// 内部：流式 LLM → 流式工具执行 → 状态更新 → 继续</span></span><br><span class="line">  <span class="comment">// 单次 API 调用可以触发多个工具，工具在流式中执行</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h3 id="4-2-关键差异"><a href="#4-2-关键差异" class="headerlink" title="4.2 关键差异"></a>4.2 关键差异</h3><div class="table-container"><table><thead><tr><th>维度</th><th>LangChain</th><th>Claude Code</th></tr></thead><tbody><tr><td>每一轮</td><td>独立的 LLM 调用</td><td>流式 API 调用</td></tr><tr><td>工具解析</td><td>OutputParser 解析文本</td><td>原生 <code>tool_use</code> 块</td></tr><tr><td>执行方式</td><td>等待完整响应</td><td>流式即时执行</td></tr><tr><td>错误处理</td><td>手动 try-catch</td><td>内置 6 种恢复</td></tr><tr><td>并行工具</td><td>需要显式编排</td><td>自动分区并行</td></tr></tbody></table></div><h3 id="4-3-与-LangGraph-的对比"><a href="#4-3-与-LangGraph-的对比" class="headerlink" title="4.3 与 LangGraph 的对比"></a>4.3 与 LangGraph 的对比</h3><p>LangGraph 是 LangChain 的升级版，引入了图结构：</p><div class="table-container"><table><thead><tr><th>维度</th><th>LangGraph</th><th>Claude Code</th></tr></thead><tbody><tr><td><strong>状态流转</strong></td><td>显式图节点 + 边</td><td>隐式状态机（while + continue）</td></tr><tr><td><strong>可视化</strong></td><td>可导出为图</td><td>状态转换原因可追溯</td></tr><tr><td><strong>持久化</strong></td><td>Checkpoint + State</td><td>文件系统 + 消息历史</td></tr><tr><td><strong>人机交互</strong></td><td>interrupt_before/after</td><td>权限系统 + 钩子</td></tr><tr><td><strong>多 Agent</strong></td><td>需要显式编排</td><td>AgentTool 统一接口</td></tr></tbody></table></div><p>Claude Code 的优势在于<strong>简单性</strong>——不需要定义图结构，一个 while 循环就能处理所有情况。</p><hr><h2 id="五、设计原则总结"><a href="#五、设计原则总结" class="headerlink" title="五、设计原则总结"></a>五、设计原则总结</h2><p>从源码分析中，我们可以总结出以下核心设计原则：</p><h3 id="5-1-最小抽象原则"><a href="#5-1-最小抽象原则" class="headerlink" title="5.1 最小抽象原则"></a>5.1 最小抽象原则</h3><p>与 LangChain 的”万物皆抽象”不同，Claude Code 的核心只有：</p><ul><li><strong>一个循环</strong>（<code>while (true)</code> in <code>query()</code>）</li><li><strong>一个状态</strong>（<code>State</code> 对象）</li><li><strong>一个接口</strong>（<code>Tool</code> 类型）</li></ul><p>没有 Agent → AgentExecutor → Chain → Memory → Callback 的嵌套抽象层。</p><h3 id="5-2-原生-API-集成"><a href="#5-2-原生-API-集成" class="headerlink" title="5.2 原生 API 集成"></a>5.2 原生 API 集成</h3><p>Claude Code 直接使用 Anthropic API 的原生能力：</p><ul><li><strong>原生工具调用</strong>：无需 OutputParser，直接使用 <code>tool_use</code> 块</li><li><strong>原生流式传输</strong>：无需包装层，直接消费 SSE 流</li><li><strong>原生缓存</strong>：利用 API 的 prompt caching 特性</li><li><strong>原生思维链</strong>：直接使用 extended thinking</li></ul><p>这避免了”框架税”——LangChain 等框架在 LLM 和开发者之间增加的抽象层。</p><h3 id="5-3-可观测性设计"><a href="#5-3-可观测性设计" class="headerlink" title="5.3 可观测性设计"></a>5.3 可观测性设计</h3><p><code>transition</code> 字段的设计体现了对可观测性的重视：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Continue = &#123;</span><br><span class="line">  reason: <span class="string">'next_turn'</span> </span><br><span class="line">    | <span class="string">'collapse_drain_retry'</span></span><br><span class="line">    | <span class="string">'reactive_compact_retry'</span></span><br><span class="line">    | <span class="string">'max_output_tokens_recovery'</span></span><br><span class="line">    | <span class="string">'stop_hook_blocking'</span></span><br><span class="line">    | <span class="string">'token_budget_continuation'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每一轮循环都知道自己为什么继续，这对于调试和测试至关重要。</p><hr><h2 id="六、关键源文件索引"><a href="#六、关键源文件索引" class="headerlink" title="六、关键源文件索引"></a>六、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>行数</th><th>职责</th></tr></thead><tbody><tr><td><code>src/query.ts</code></td><td>~1730</td><td>Agent 主循环，状态机核心</td></tr><tr><td><code>src/QueryEngine.ts</code></td><td>~687</td><td>高层封装，对外 API</td></tr><tr><td><code>src/services/tools/StreamingToolExecutor.ts</code></td><td>~200</td><td>流式工具执行器</td></tr><tr><td><code>src/services/tools/toolOrchestration.ts</code></td><td>~150</td><td>工具编排策略</td></tr><tr><td><code>src/query/transitions.ts</code></td><td>~50</td><td>状态转换类型定义</td></tr><tr><td><code>src/query/tokenBudget.ts</code></td><td>~100</td><td>Token 预算管理</td></tr><tr><td><code>src/query/stopHooks.ts</code></td><td>~200</td><td>Stop 钩子处理</td></tr></tbody></table></div><hr><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><p>Claude Code 的 Async Generator 状态机设计解决了 ReAct 模式的根本性限制：</p><ol><li><strong>流式执行</strong>：工具在模型生成过程中就开始运行</li><li><strong>状态可控</strong>：统一的 <code>State</code> 对象，恢复只需修改状态</li><li><strong>自动恢复</strong>：6 种内置策略确保用户体验稳定</li><li><strong>缓存友好</strong>：静态部分全局缓存，动态部分最小化</li><li><strong>并行能力</strong>：只读工具自动并行，写入工具串行保序</li></ol><p>这个设计选择体现了 Claude Code 团队对产品体验的深刻理解：<strong>用户不应该等待，也不应该因为技术问题中断</strong>。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-architecture-overview/">Claude Code 源码揭秘：整体架构概览</a></li><li>下一篇：<a href="/claude-code-tool-system/">工具系统设计：从定义到执行的七步管道</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;当大多数人谈论 AI Agent 架构时，ReAct（Reasoning + Acting）几乎是唯一的答案。但 Claude Code 选择了一条不同的路——Async Generator 状态机。这个设计决策背后有着深刻的思考，它解决了 ReAct 的根本性限制，为流式交互和优雅恢复奠定了基础。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="State Machine" scheme="https://donehub.github.io/tags/State-Machine/"/>
    
  </entry>
  
  <entry>
    <title>Computer Use：桌面控制的九层安全关卡</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-computer-use/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-computer-use/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:39.739Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Computer Use 是 Claude Code 最具争议也最强大的能力——AI 可以直接操控你的桌面，点击按钮、输入文字、截图分析。这听起来像科幻电影，但 Claude Code 实现了一个九层安全关卡系统，确保每一步操作都在可控范围内。更关键的是，它通过 Python Bridge 实现跨语言通信，让 TypeScript 代理驱动 Python 执行器。</p></blockquote><a id="more"></a><h2 id="导读：当-AI-控制你的屏幕"><a href="#导读：当-AI-控制你的屏幕" class="headerlink" title="导读：当 AI 控制你的屏幕"></a>导读：当 AI 控制你的屏幕</h2><p>想象这个场景：</p><blockquote><p>Claude Code 正在帮你调试一个 GUI 应用。它打开应用窗口，点击菜单，输入测试数据，截图分析结果，然后告诉你”登录按钮在点击后无响应”。</p></blockquote><p>这就是 <strong>Computer Use</strong>——AI 直接操控桌面环境的能力。</p><p>但这也带来巨大的安全风险：AI 可能误删文件、点击错误按钮、泄露敏感信息。Claude Code 的解决方案是<strong>九层安全关卡</strong>，每一层都可以中断操作。</p><hr><h2 id="一、Computer-Use-架构概览"><a href="#一、Computer-Use-架构概览" class="headerlink" title="一、Computer Use 架构概览"></a>一、Computer Use 架构概览</h2><h3 id="1-1-整体架构"><a href="#1-1-整体架构" class="headerlink" title="1.1 整体架构"></a>1.1 整体架构</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    Computer Use 架构                         │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Claude Code (TypeScript)                                   │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Computer Use Tool                                          │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  JSON-RPC over stdio                                        │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Python Bridge (computer_controller.py)                     │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Platform Abstraction Layer                                 │</span><br><span class="line">│       ├─ Windows: pyautogui + Win32 API                    │</span><br><span class="line">│       ├─ macOS: PyObjC + AppleScript                       │</span><br><span class="line">│       └─ Linux: xdotool + Gdk&#x2F;Xlib                         │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Desktop Environment                                        │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-2-为什么用-Python？"><a href="#1-2-为什么用-Python？" class="headerlink" title="1.2 为什么用 Python？"></a>1.2 为什么用 Python？</h3><p>虽然 Claude Code 是 TypeScript 项目，但 Computer Use 使用 Python 实现：</p><div class="table-container"><table><thead><tr><th>原因</th><th>说明</th></tr></thead><tbody><tr><td><strong>生态成熟</strong></td><td>pyautogui、PyObjC 等库已稳定运行多年</td></tr><tr><td><strong>跨平台</strong></td><td>Python GUI 库对 Windows/macOS/Linux 支持一致</td></tr><tr><td><strong>快速迭代</strong></td><td>不需要为每个平台单独编写 native 代码</td></tr></tbody></table></div><hr><h2 id="二、24-个桌面操作工具"><a href="#二、24-个桌面操作工具" class="headerlink" title="二、24 个桌面操作工具"></a>二、24 个桌面操作工具</h2><h3 id="2-1-工具分类"><a href="#2-1-工具分类" class="headerlink" title="2.1 工具分类"></a>2.1 工具分类</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                   24 个 Computer Use 工具                    │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  输入类（Input）                                             │</span><br><span class="line">│    ├─ computer_mouse_click      左键&#x2F;右键&#x2F;中键点击          │</span><br><span class="line">│    ├─ computer_mouse_double_click 双击                     │</span><br><span class="line">│    ├─ computer_mouse_drag       拖拽操作                    │</span><br><span class="line">│    ├─ computer_mouse_move       移动鼠标                    │</span><br><span class="line">│    ├─ computer_mouse_scroll     滚轮滚动                    │</span><br><span class="line">│    ├─ computer_keyboard_hotkey  组合键（Ctrl+C 等）         │</span><br><span class="line">│    ├─ computer_keyboard_press   单键按下                    │</span><br><span class="line">│    ├─ computer_keyboard_type    文字输入                    │</span><br><span class="line">│    └─ computer_clipboard_paste  粘贴内容                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  显示类（Display）                                           │</span><br><span class="line">│    ├─ computer_screen_capture   截图                        │</span><br><span class="line">│    ├─ computer_screen_get_size  获取屏幕尺寸                │</span><br><span class="line">│    ├─ computer_window_list      窗口列表                    │</span><br><span class="line">│    ├─ computer_window_activate  激活窗口                    │</span><br><span class="line">│    ├─ computer_window_get_position 窗口位置                 │</span><br><span class="line">│    └─ computer_window_get_size  窗口尺寸                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  文件类（File）                                              │</span><br><span class="line">│    ├─ computer_file_read        读取文件                    │</span><br><span class="line">│    ├─ computer_file_write       写入文件                    │</span><br><span class="line">│    ├─ computer_file_delete      删除文件                    │</span><br><span class="line">│    ├─ computer_file_list        列出目录                    │</span><br><span class="line">│    ├─ computer_file_move        移动文件                    │</span><br><span class="line">│    ├─ computer_file_copy        复制文件                    │</span><br><span class="line">│    └─ computer_file_info        文件信息                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  进程类（Process）                                           │</span><br><span class="line">│    ├─ computer_process_list     进程列表                    │</span><br><span class="line">│    ├─ computer_process_start    启动进程                    │</span><br><span class="line">│    └─ computer_process_kill     杀死进程                    │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-2-工具定义示例"><a href="#2-2-工具定义示例" class="headerlink" title="2.2 工具定义示例"></a>2.2 工具定义示例</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/ComputerUseTool/tools.ts</span></span><br><span class="line"><span class="keyword">const</span> computer_mouse_click = &#123;</span><br><span class="line">  name: <span class="string">'computer_mouse_click'</span>,</span><br><span class="line">  inputSchema: &#123;</span><br><span class="line">    <span class="keyword">type</span>: <span class="string">'object'</span>,</span><br><span class="line">    properties: &#123;</span><br><span class="line">      x: &#123; <span class="keyword">type</span>: <span class="string">'number'</span>, description: <span class="string">'X coordinate'</span> &#125;,</span><br><span class="line">      y: &#123; <span class="keyword">type</span>: <span class="string">'number'</span>, description: <span class="string">'Y coordinate'</span> &#125;,</span><br><span class="line">      button: &#123; </span><br><span class="line">        <span class="keyword">type</span>: <span class="string">'string'</span>, </span><br><span class="line">        <span class="keyword">enum</span>: [<span class="string">'left'</span>, <span class="string">'right'</span>, <span class="string">'middle'</span>],</span><br><span class="line">        <span class="keyword">default</span>: <span class="string">'left'</span></span><br><span class="line">      &#125;,</span><br><span class="line">      clicks: &#123; <span class="keyword">type</span>: <span class="string">'number'</span>, <span class="keyword">default</span>: <span class="number">1</span> &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    required: [<span class="string">'x'</span>, <span class="string">'y'</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">  description: <span class="string">'Click at the specified coordinates'</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="三、九层安全关卡"><a href="#三、九层安全关卡" class="headerlink" title="三、九层安全关卡"></a>三、九层安全关卡</h2><h3 id="3-1-安全关卡架构"><a href="#3-1-安全关卡架构" class="headerlink" title="3.1 安全关卡架构"></a>3.1 安全关卡架构</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    九层安全关卡                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 1: 功能门控（Feature Gate）                           │</span><br><span class="line">│    └─ tengu_computer_use Feature Flag 必须开启             │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 2: 用户确认（User Consent）                           │</span><br><span class="line">│    └─ 首次使用弹出确认对话框                                │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 3: 操作类型检查（Action Type）                        │</span><br><span class="line">│    └─ 读写操作需要额外确认                                  │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 4: 路径约束（Path Constraint）                        │</span><br><span class="line">│    ├─ 文件操作限制在白名单目录                              │</span><br><span class="line">│    └─ 禁止访问 .git、.claude、系统目录                      │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 5: 危险命令过滤（Dangerous Command）                   │</span><br><span class="line">│    ├─ 禁止 rm -rf、killall 等命令                          │</span><br><span class="line">│    ├─ 禁止访问密码管理器、银行应用                          │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 6: 屏幕边界检查（Screen Boundary）                    │</span><br><span class="line">│    ├─ 鼠标坐标必须在屏幕范围内                              │</span><br><span class="line">│    └─ 窗口操作必须针对可见窗口                              │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 7: 操作频率限制（Rate Limit）                         │</span><br><span class="line">│    ├─ 每秒最多 10 次操作                                    │</span><br><span class="line">│    └─ 连续失败 3 次暂停                                     │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 8: 截图内容分析（Screenshot Analysis）                │</span><br><span class="line">│    ├─ 检测敏感内容（密码框、私人信息）                      │</span><br><span class="line">│    └─ 检测错误弹窗                                          │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 9: 实时监控（Real-time Monitoring）                   │</span><br><span class="line">│    ├─ 用户可随时按 Ctrl+C 中断                             │</span><br><span class="line">│    ├─ 操作日志实时输出                                      │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="3-2-Gate-实现示例"><a href="#3-2-Gate-实现示例" class="headerlink" title="3.2 Gate 实现示例"></a>3.2 Gate 实现示例</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/ComputerUseTool/security.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">gateComputerUseAction</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  action: ComputerUseAction,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">GateResult</span> </span>&#123;</span><br><span class="line">  <span class="comment">// Gate 1: Feature Gate</span></span><br><span class="line">  <span class="keyword">if</span> (!feature(<span class="string">'tengu_computer_use'</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'deny'</span>, reason: <span class="string">'Feature not enabled'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 2: User Consent</span></span><br><span class="line">  <span class="keyword">if</span> (!context.computerUseConsent) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'ask'</span>, reason: <span class="string">'First-time use requires consent'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 3: Action Type</span></span><br><span class="line">  <span class="keyword">if</span> (isWriteAction(action) &amp;&amp; !context.computerUseWriteConsent) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'ask'</span>, reason: <span class="string">'Write operation requires confirmation'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 4: Path Constraint</span></span><br><span class="line">  <span class="keyword">if</span> (action.type === <span class="string">'file'</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!isInAllowedDirectory(action.path, context.allowedDirectories)) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; action: <span class="string">'deny'</span>, reason: <span class="string">'Path not in allowed directories'</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 5: Dangerous Command</span></span><br><span class="line">  <span class="keyword">if</span> (isDangerousCommand(action)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'deny'</span>, reason: <span class="string">'Dangerous command blocked'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 6: Screen Boundary</span></span><br><span class="line">  <span class="keyword">if</span> (action.type === <span class="string">'mouse'</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> screenSize = getScreenSize()</span><br><span class="line">    <span class="keyword">if</span> (action.x &lt; <span class="number">0</span> || action.x &gt; screenSize.width ||</span><br><span class="line">        action.y &lt; <span class="number">0</span> || action.y &gt; screenSize.height) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; action: <span class="string">'deny'</span>, reason: <span class="string">'Coordinates out of screen bounds'</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 7: Rate Limit</span></span><br><span class="line">  <span class="keyword">if</span> (isRateLimited(context.computerUseHistory)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'wait'</span>, reason: <span class="string">'Rate limit exceeded'</span>, waitTime: <span class="number">1000</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 8: Screenshot Analysis (performed after capture)</span></span><br><span class="line">  <span class="comment">// Gate 9: Real-time Monitoring (handled by interrupt mechanism)</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123; action: <span class="string">'allow'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="四、Python-Bridge-通信协议"><a href="#四、Python-Bridge-通信协议" class="headerlink" title="四、Python Bridge 通信协议"></a>四、Python Bridge 通信协议</h2><h3 id="4-1-JSON-RPC-over-stdio"><a href="#4-1-JSON-RPC-over-stdio" class="headerlink" title="4.1 JSON-RPC over stdio"></a>4.1 JSON-RPC over stdio</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/ComputerUseTool/bridge.ts</span></span><br><span class="line"><span class="keyword">interface</span> BridgeMessage &#123;</span><br><span class="line">  jsonrpc: <span class="string">'2.0'</span></span><br><span class="line">  id: <span class="built_in">number</span></span><br><span class="line">  method: <span class="built_in">string</span></span><br><span class="line">  params: Record&lt;<span class="built_in">string</span>, unknown&gt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> BridgeResponse &#123;</span><br><span class="line">  jsonrpc: <span class="string">'2.0'</span></span><br><span class="line">  id: <span class="built_in">number</span></span><br><span class="line">  result?: unknown</span><br><span class="line">  error?: &#123; code: <span class="built_in">number</span>; message: <span class="built_in">string</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">callBridge</span>(<span class="params">method: <span class="built_in">string</span>, params: unknown</span>): <span class="title">Promise</span>&lt;<span class="title">unknown</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> message: BridgeMessage = &#123;</span><br><span class="line">    jsonrpc: <span class="string">'2.0'</span>,</span><br><span class="line">    id: nextId++,</span><br><span class="line">    method,</span><br><span class="line">    params,</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 写入 stdin</span></span><br><span class="line">  bridgeProcess.stdin.write(<span class="built_in">JSON</span>.stringify(message) + <span class="string">'\n'</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 读取 stdout</span></span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> readBridgeResponse()</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (response.error) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> BridgeError(response.error.code, response.error.message)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> response.result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-Python-执行器"><a href="#4-2-Python-执行器" class="headerlink" title="4.2 Python 执行器"></a>4.2 Python 执行器</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># computer_controller.py</span></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> Any</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ComputerController</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.handlers = &#123;</span><br><span class="line">            <span class="string">'computer_mouse_click'</span>: self.mouse_click,</span><br><span class="line">            <span class="string">'computer_keyboard_type'</span>: self.keyboard_type,</span><br><span class="line">            <span class="string">'computer_screen_capture'</span>: self.screen_capture,</span><br><span class="line">            <span class="comment"># ... 24 个处理器</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">            line = sys.stdin.readline()</span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> line:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">            request = json.loads(line)</span><br><span class="line">            method = request[<span class="string">'method'</span>]</span><br><span class="line">            params = request[<span class="string">'params'</span>]</span><br><span class="line">            id = request[<span class="string">'id'</span>]</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                handler = self.handlers[method]</span><br><span class="line">                result = handler(**params)</span><br><span class="line">                response = &#123;</span><br><span class="line">                    <span class="string">'jsonrpc'</span>: <span class="string">'2.0'</span>,</span><br><span class="line">                    <span class="string">'id'</span>: id,</span><br><span class="line">                    <span class="string">'result'</span>: result</span><br><span class="line">                &#125;</span><br><span class="line">            <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">                response = &#123;</span><br><span class="line">                    <span class="string">'jsonrpc'</span>: <span class="string">'2.0'</span>,</span><br><span class="line">                    <span class="string">'id'</span>: id,</span><br><span class="line">                    <span class="string">'error'</span>: &#123;<span class="string">'code'</span>: <span class="number">1</span>, <span class="string">'message'</span>: str(e)&#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">            sys.stdout.write(json.dumps(response) + <span class="string">'\n'</span>)</span><br><span class="line">            sys.stdout.flush()</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">mouse_click</span><span class="params">(self, x: int, y: int, button: str = <span class="string">'left'</span>)</span>:</span></span><br><span class="line">        <span class="keyword">import</span> pyautogui</span><br><span class="line">        pyautogui.click(x, y, button=button)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">screen_capture</span><span class="params">(self)</span> -&gt; str:</span></span><br><span class="line">        <span class="keyword">import</span> pyautogui</span><br><span class="line">        <span class="keyword">import</span> base64</span><br><span class="line">        screenshot = pyautogui.screenshot()</span><br><span class="line">        <span class="comment"># 返回 base64 编码</span></span><br><span class="line">        <span class="keyword">return</span> base64.b64encode(screenshot).decode(<span class="string">'utf-8'</span>)</span><br></pre></td></tr></table></figure><hr><h2 id="五、截图分析机制"><a href="#五、截图分析机制" class="headerlink" title="五、截图分析机制"></a>五、截图分析机制</h2><h3 id="5-1-截图流程"><a href="#5-1-截图流程" class="headerlink" title="5.1 截图流程"></a>5.1 截图流程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Model 决定截图</span><br><span class="line">    ↓</span><br><span class="line">computer_screen_capture 工具调用</span><br><span class="line">    ↓</span><br><span class="line">Python Bridge 执行 pyautogui.screenshot()</span><br><span class="line">    ↓</span><br><span class="line">PNG → Base64 编码</span><br><span class="line">    ↓</span><br><span class="line">返回给 Claude Code</span><br><span class="line">    ↓</span><br><span class="line">作为 image block 注入对话</span><br><span class="line">    ↓</span><br><span class="line">Model 多模态分析</span><br></pre></td></tr></table></figure><h3 id="5-2-截图内容过滤"><a href="#5-2-截图内容过滤" class="headerlink" title="5.2 截图内容过滤"></a>5.2 截图内容过滤</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/ComputerUseTool/screenshotFilter.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">filterScreenshot</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  base64Image: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">FilterResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 使用本地 OCR 检测敏感文本</span></span><br><span class="line">  <span class="keyword">const</span> detectedText = <span class="keyword">await</span> localOcrDetect(base64Image)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 检测敏感关键词</span></span><br><span class="line">  <span class="keyword">const</span> sensitiveKeywords = [<span class="string">'password'</span>, <span class="string">'secret'</span>, <span class="string">'api key'</span>, <span class="string">'token'</span>]</span><br><span class="line">  <span class="keyword">const</span> foundSensitive = sensitiveKeywords.some(<span class="function"><span class="params">k</span> =&gt;</span> </span><br><span class="line">    detectedText.toLowerCase().includes(k)</span><br><span class="line">  )</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (foundSensitive) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      action: <span class="string">'blur'</span>,</span><br><span class="line">      regions: findSensitiveRegions(detectedText),</span><br><span class="line">      reason: <span class="string">'Sensitive content detected'</span>,</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123; action: <span class="string">'allow'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="六、窗口管理系统"><a href="#六、窗口管理系统" class="headerlink" title="六、窗口管理系统"></a>六、窗口管理系统</h2><h3 id="6-1-窗口发现"><a href="#6-1-窗口发现" class="headerlink" title="6.1 窗口发现"></a>6.1 窗口发现</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 窗口列表返回格式</span></span><br><span class="line"><span class="keyword">interface</span> WindowInfo &#123;</span><br><span class="line">  id: <span class="built_in">number</span></span><br><span class="line">  title: <span class="built_in">string</span></span><br><span class="line">  process: <span class="built_in">string</span></span><br><span class="line">  position: &#123; x: <span class="built_in">number</span>; y: <span class="built_in">number</span> &#125;</span><br><span class="line">  size: &#123; width: <span class="built_in">number</span>; height: <span class="built_in">number</span> &#125;</span><br><span class="line">  visible: <span class="built_in">boolean</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 示例返回</span></span><br><span class="line">[</span><br><span class="line">  &#123; id: <span class="number">1234</span>, title: <span class="string">'VS Code'</span>, process: <span class="string">'code'</span>, position: &#123; x: <span class="number">0</span>, y: <span class="number">0</span> &#125;, size: &#123; width: <span class="number">1920</span>, height: <span class="number">1080</span> &#125;, visible: <span class="literal">true</span> &#125;,</span><br><span class="line">  &#123; id: <span class="number">5678</span>, title: <span class="string">'Chrome'</span>, process: <span class="string">'chrome'</span>, position: &#123; x: <span class="number">100</span>, y: <span class="number">100</span> &#125;, size: &#123; width: <span class="number">800</span>, height: <span class="number">600</span> &#125;, visible: <span class="literal">true</span> &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h3 id="6-2-窗口激活策略"><a href="#6-2-窗口激活策略" class="headerlink" title="6.2 窗口激活策略"></a>6.2 窗口激活策略</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 激活窗口的安全检查</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">activateWindow</span>(<span class="params">windowId: <span class="built_in">number</span></span>): <span class="title">Promise</span>&lt;<span class="title">void</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 检查窗口是否存在</span></span><br><span class="line">  <span class="keyword">const</span> <span class="built_in">window</span> = <span class="keyword">await</span> getWindowInfo(windowId)</span><br><span class="line">  <span class="keyword">if</span> (!<span class="built_in">window</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Window not found'</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 检查窗口是否属于敏感应用</span></span><br><span class="line">  <span class="keyword">const</span> sensitiveApps = [<span class="string">'Keychain Access'</span>, <span class="string">'1Password'</span>, <span class="string">'Banking App'</span>]</span><br><span class="line">  <span class="keyword">if</span> (sensitiveApps.some(<span class="function"><span class="params">app</span> =&gt;</span> <span class="built_in">window</span>.title.includes(app))) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Cannot activate sensitive application'</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3. 执行激活</span></span><br><span class="line">  <span class="keyword">await</span> callBridge(<span class="string">'computer_window_activate'</span>, &#123; window_id: windowId &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、操作审计日志"><a href="#七、操作审计日志" class="headerlink" title="七、操作审计日志"></a>七、操作审计日志</h2><h3 id="7-1-日志格式"><a href="#7-1-日志格式" class="headerlink" title="7.1 日志格式"></a>7.1 日志格式</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ComputerUseLogEntry &#123;</span><br><span class="line">  timestamp: <span class="built_in">number</span></span><br><span class="line">  action: <span class="built_in">string</span></span><br><span class="line">  params: Record&lt;<span class="built_in">string</span>, unknown&gt;</span><br><span class="line">  result: <span class="string">'success'</span> | <span class="string">'deny'</span> | <span class="string">'error'</span></span><br><span class="line">  reason?: <span class="built_in">string</span></span><br><span class="line">  duration: <span class="built_in">number</span></span><br><span class="line">  screenshot?: <span class="built_in">string</span>  <span class="comment">// 操作后的截图（可选）</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 示例日志</span></span><br><span class="line">[</span><br><span class="line">  &#123; timestamp: <span class="number">1712345678</span>, action: <span class="string">'mouse_click'</span>, params: &#123; x: <span class="number">100</span>, y: <span class="number">200</span> &#125;, result: <span class="string">'success'</span>, duration: <span class="number">50</span> &#125;,</span><br><span class="line">  &#123; timestamp: <span class="number">1712345680</span>, action: <span class="string">'keyboard_type'</span>, params: &#123; text: <span class="string">'hello'</span> &#125;, result: <span class="string">'success'</span>, duration: <span class="number">100</span> &#125;,</span><br><span class="line">  &#123; timestamp: <span class="number">1712345682</span>, action: <span class="string">'file_delete'</span>, params: &#123; path: <span class="string">'/etc/passwd'</span> &#125;, result: <span class="string">'deny'</span>, reason: <span class="string">'Dangerous path'</span>, duration: <span class="number">0</span> &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h3 id="7-2-日志存储"><a href="#7-2-日志存储" class="headerlink" title="7.2 日志存储"></a>7.2 日志存储</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 日志持久化到文件</span></span><br><span class="line"><span class="keyword">const</span> LOG_PATH = <span class="string">'.claude/computer_use_history.jsonl'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">appendLog</span>(<span class="params">entry: ComputerUseLogEntry</span>): <span class="title">Promise</span>&lt;<span class="title">void</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> logLine = <span class="built_in">JSON</span>.stringify(entry) + <span class="string">'\n'</span></span><br><span class="line">  <span class="keyword">await</span> fs.appendFile(LOG_PATH, logLine)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="八、中断机制"><a href="#八、中断机制" class="headerlink" title="八、中断机制"></a>八、中断机制</h2><h3 id="8-1-Ctrl-C-中断"><a href="#8-1-Ctrl-C-中断" class="headerlink" title="8.1 Ctrl+C 中断"></a>8.1 Ctrl+C 中断</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 监听中断信号</span></span><br><span class="line">process.on(<span class="string">'SIGINT'</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="comment">// 1. 通知 Python Bridge 停止</span></span><br><span class="line">  <span class="keyword">await</span> callBridge(<span class="string">'stop'</span>, &#123;&#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 恢复鼠标状态</span></span><br><span class="line">  <span class="keyword">await</span> callBridge(<span class="string">'mouse_move'</span>, &#123; x: lastSafeX, y: lastSafeY &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3. 记录中断</span></span><br><span class="line">  appendLog(&#123;</span><br><span class="line">    timestamp: <span class="built_in">Date</span>.now(),</span><br><span class="line">    action: <span class="string">'interrupt'</span>,</span><br><span class="line">    params: &#123;&#125;,</span><br><span class="line">    result: <span class="string">'success'</span>,</span><br><span class="line">    reason: <span class="string">'User pressed Ctrl+C'</span>,</span><br><span class="line">    duration: <span class="number">0</span>,</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 4. 提示用户</span></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'\nComputer Use interrupted. All operations stopped.'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="8-2-紧急停止"><a href="#8-2-紧急停止" class="headerlink" title="8.2 紧急停止"></a>8.2 紧急停止</h3><p>Python Bridge 维护一个紧急停止标志：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># computer_controller.py</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ComputerController</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.emergency_stop = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">while</span> <span class="keyword">not</span> self.emergency_stop:</span><br><span class="line">            <span class="comment"># ... 处理请求</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">stop</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.emergency_stop = <span class="literal">True</span></span><br><span class="line">        <span class="comment"># 恢复鼠标到安全位置</span></span><br><span class="line">        pyautogui.moveTo(self.safe_x, self.safe_y)</span><br></pre></td></tr></table></figure><hr><h2 id="九、平台适配层"><a href="#九、平台适配层" class="headerlink" title="九、平台适配层"></a>九、平台适配层</h2><h3 id="9-1-Windows-实现"><a href="#9-1-Windows-实现" class="headerlink" title="9.1 Windows 实现"></a>9.1 Windows 实现</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># platform/windows.py</span></span><br><span class="line"><span class="keyword">import</span> pyautogui</span><br><span class="line"><span class="keyword">import</span> ctypes</span><br><span class="line"><span class="keyword">from</span> ctypes <span class="keyword">import</span> wintypes</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_active_window</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""获取活动窗口"""</span></span><br><span class="line">    hwnd = ctypes.windll.user32.GetForegroundWindow()</span><br><span class="line">    <span class="keyword">return</span> hwnd</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_window_title</span><span class="params">(hwnd)</span>:</span></span><br><span class="line">    <span class="string">"""获取窗口标题"""</span></span><br><span class="line">    length = ctypes.windll.user32.GetWindowTextLengthW(hwnd)</span><br><span class="line">    title = ctypes.create_unicode_buffer(length + <span class="number">1</span>)</span><br><span class="line">    ctypes.windll.user32.GetWindowTextW(hwnd, title, length + <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">return</span> title.value</span><br></pre></td></tr></table></figure><h3 id="9-2-macOS-实现"><a href="#9-2-macOS-实现" class="headerlink" title="9.2 macOS 实现"></a>9.2 macOS 实现</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># platform/macos.py</span></span><br><span class="line"><span class="keyword">import</span> pyautogui</span><br><span class="line"><span class="keyword">from</span> AppKit <span class="keyword">import</span> NSWorkspace, NSRunningApplication</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_active_window</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""获取活动窗口"""</span></span><br><span class="line">    workspace = NSWorkspace.sharedWorkspace()</span><br><span class="line">    app = workspace.activeApplication()</span><br><span class="line">    <span class="keyword">return</span> app.localizedName()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">activate_window</span><span class="params">(title)</span>:</span></span><br><span class="line">    <span class="string">"""激活窗口"""</span></span><br><span class="line">    workspace = NSWorkspace.sharedWorkspace()</span><br><span class="line">    apps = workspace.runningApplications()</span><br><span class="line">    <span class="keyword">for</span> app <span class="keyword">in</span> apps:</span><br><span class="line">        <span class="keyword">if</span> app.localizedName() == title:</span><br><span class="line">            app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps)</span><br><span class="line">            <span class="keyword">break</span></span><br></pre></td></tr></table></figure><h3 id="9-3-Linux-实现"><a href="#9-3-Linux-实现" class="headerlink" title="9.3 Linux 实现"></a>9.3 Linux 实现</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># platform/linux.py</span></span><br><span class="line"><span class="keyword">import</span> pyautogui</span><br><span class="line"><span class="keyword">import</span> subprocess</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_active_window</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""获取活动窗口"""</span></span><br><span class="line">    result = subprocess.run(</span><br><span class="line">        [<span class="string">'xdotool'</span>, <span class="string">'getactivewindow'</span>],</span><br><span class="line">        capture_output=<span class="literal">True</span>,</span><br><span class="line">        text=<span class="literal">True</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> int(result.stdout.strip())</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_window_title</span><span class="params">(window_id)</span>:</span></span><br><span class="line">    <span class="string">"""获取窗口标题"""</span></span><br><span class="line">    result = subprocess.run(</span><br><span class="line">        [<span class="string">'xdotool'</span>, <span class="string">'getwindowname'</span>, str(window_id)],</span><br><span class="line">        capture_output=<span class="literal">True</span>,</span><br><span class="line">        text=<span class="literal">True</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> result.stdout.strip()</span><br></pre></td></tr></table></figure><hr><h2 id="十、关键源文件索引"><a href="#十、关键源文件索引" class="headerlink" title="十、关键源文件索引"></a>十、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/tools/ComputerUseTool/ComputerUseTool.ts</code></td><td>工具定义、权限检查、安全关卡</td></tr><tr><td><code>src/tools/ComputerUseTool/bridge.ts</code></td><td>Python Bridge 通信</td></tr><tr><td><code>src/tools/ComputerUseTool/security.ts</code></td><td>九层安全关卡实现</td></tr><tr><td><code>src/tools/ComputerUseTool/tools.ts</code></td><td>24 个工具定义</td></tr><tr><td><code>src/tools/ComputerUseTool/screenshotFilter.ts</code></td><td>截图内容过滤</td></tr><tr><td><code>computer_controller.py</code></td><td>Python 执行器主入口</td></tr><tr><td><code>platform/windows.py</code></td><td>Windows 平台适配</td></tr><tr><td><code>platform/macos.py</code></td><td>macOS 平台适配</td></tr><tr><td><code>platform/linux.py</code></td><td>Linux 平台适配</td></tr></tbody></table></div><hr><h2 id="十一、总结"><a href="#十一、总结" class="headerlink" title="十一、总结"></a>十一、总结</h2><p>Claude Code 的 Computer Use 系统体现了几个核心设计原则：</p><ol><li><strong>九层防御</strong>：从功能门控到实时监控，层层把关</li><li><strong>跨语言架构</strong>：TypeScript 代理 + Python 执行器</li><li><strong>JSON-RPC 协议</strong>：简单高效的跨进程通信</li><li><strong>平台抽象</strong>：统一接口，底层适配三大操作系统</li><li><strong>审计日志</strong>：完整记录所有操作，便于追溯</li><li><strong>用户可控</strong>：随时 Ctrl+C 中断，恢复安全状态</li></ol><p>这个设计让 AI 真正能够”看见”和”操控”桌面环境，同时保持高度安全性。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-channel-system/">Channel 系统：IM 远程控制 Agent</a></li><li>下一篇：<a href="/claude-code-terminal-ui/">Terminal UI：React + Ink 的 TUI 实现</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Computer Use 是 Claude Code 最具争议也最强大的能力——AI 可以直接操控你的桌面，点击按钮、输入文字、截图分析。这听起来像科幻电影，但 Claude Code 实现了一个九层安全关卡系统，确保每一步操作都在可控范围内。更关键的是，它通过 Python Bridge 实现跨语言通信，让 TypeScript 代理驱动 Python 执行器。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Computer Use" scheme="https://donehub.github.io/tags/Computer-Use/"/>
    
  </entry>
  
  <entry>
    <title>Channel 系统：IM 远程控制 Agent</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-channel-system/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-channel-system/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:38.708Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>你在手机上打开 Telegram，给 Claude Code 发一条消息，它就开始在你的电脑上工作——这就是 Channel 系统。它打破了 AI 编程助手只能在终端中交互的限制，实现了真正的远程控制。更精妙的是，它有六层访问控制和权限中继机制，确保安全性。</p></blockquote><a id="more"></a><h2 id="导读：打破终端的边界"><a href="#导读：打破终端的边界" class="headerlink" title="导读：打破终端的边界"></a>导读：打破终端的边界</h2><p>传统的 AI 编程助手只能在终端中交互。如果你想让它工作，你必须坐在电脑前。</p><p>Channel 系统改变了这一切：</p><ul><li>你在手机上通过 Telegram 发消息</li><li>Claude Code 收到消息，理解意图，执行操作</li><li>结果回复到你的 Telegram 聊天窗口</li></ul><p><strong>这不是简单的消息转发</strong>——这是一个完整的远程控制系统：</p><ul><li>六层访问控制确保只有授权的 Channel 能推送消息</li><li>权限中继让你在手机上也能审批危险操作</li><li>MCP 协议让任何 IM 平台都能集成</li></ul><hr><h2 id="一、Channel-的本质"><a href="#一、Channel-的本质" class="headerlink" title="一、Channel 的本质"></a>一、Channel 的本质</h2><h3 id="1-1-Channel-就是一个-MCP-Server"><a href="#1-1-Channel-就是一个-MCP-Server" class="headerlink" title="1.1 Channel 就是一个 MCP Server"></a>1.1 Channel 就是一个 MCP Server</h3><p>从技术角度看，一个 Channel 就是一个特殊的 <strong>MCP Server</strong>：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Channel 的能力声明</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">"experimental"</span>: &#123;</span><br><span class="line">    <span class="string">"claude/channel"</span>: &#123;&#125;           <span class="comment">// 声明 Channel 能力</span></span><br><span class="line">    <span class="string">"claude/channel/permission"</span>: &#123;&#125;  <span class="comment">// 声明权限中继能力（可选）</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-2-Channel-的两种形态"><a href="#1-2-Channel-的两种形态" class="headerlink" title="1.2 Channel 的两种形态"></a>1.2 Channel 的两种形态</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ChannelEntry =</span><br><span class="line">  | &#123; kind: <span class="string">'plugin'</span>; name: <span class="built_in">string</span>; marketplace: <span class="built_in">string</span>; dev?: <span class="built_in">boolean</span> &#125;</span><br><span class="line">  | &#123; kind: <span class="string">'server'</span>; name: <span class="built_in">string</span>; dev?: <span class="built_in">boolean</span> &#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>形态</th><th>说明</th><th>安全性</th></tr></thead><tbody><tr><td><strong>plugin</strong></td><td>来自 marketplace 的验证插件</td><td>需要白名单</td></tr><tr><td><strong>server</strong></td><td>直接指定的 MCP 服务器名称</td><td>需要 dev 旁路</td></tr></tbody></table></div><hr><h2 id="二、消息流转全链路"><a href="#二、消息流转全链路" class="headerlink" title="二、消息流转全链路"></a>二、消息流转全链路</h2><h3 id="2-1-入站流程（IM-→-Agent）"><a href="#2-1-入站流程（IM-→-Agent）" class="headerlink" title="2.1 入站流程（IM → Agent）"></a>2.1 入站流程（IM → Agent）</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    入站消息流程                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Telegram&#x2F;Feishu&#x2F;Discord                                    │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Channel Plugin（MCP Server）                               │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  notifications&#x2F;claude&#x2F;channel &#123; content, meta &#125;             │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  useManageMCPConnections → registerNotificationHandler      │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  wrapChannelMessage() → &lt;channel source&#x3D;&quot;...&quot; user&#x3D;&quot;...&quot;&gt;  │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  enqueue(&#123; priority: &#39;next&#39;, isMeta: true &#125;)                │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  SleepTool 每 ~1s 轮询 hasCommandsInQueue()                │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Model 看到 &lt;channel&gt; 标签，理解消息来源                      │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-2-出站流程（Agent-→-IM）"><a href="#2-2-出站流程（Agent-→-IM）" class="headerlink" title="2.2 出站流程（Agent → IM）"></a>2.2 出站流程（Agent → IM）</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    出站消息流程                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Model 决定使用哪个工具回复                                   │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  callTool() → Channel 的 MCP 工具                           │</span><br><span class="line">│  （reply &#x2F; react &#x2F; edit_message &#x2F; download_attachment）      │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  MCP 协议调用 Channel Server                                │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Channel Server 发送消息到 IM 平台                           │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Telegram&#x2F;Feishu&#x2F;Discord 用户收到回复                        │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-3-消息封装格式"><a href="#2-3-消息封装格式" class="headerlink" title="2.3 消息封装格式"></a>2.3 消息封装格式</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">channel</span> <span class="attr">source</span>=<span class="string">"plugin:telegram:tg"</span> <span class="attr">user</span>=<span class="string">"alice"</span> <span class="attr">chat_id</span>=<span class="string">"123456"</span>&gt;</span></span><br><span class="line">帮我看看 main.ts 有什么问题</span><br><span class="line"><span class="tag">&lt;/<span class="name">channel</span>&gt;</span></span><br></pre></td></tr></table></figure><p>模型看到这个标签后，就知道消息来自 Telegram 的用户 alice，并会使用 Telegram 的 <code>reply</code> 工具回复。</p><hr><h2 id="三、六层访问控制"><a href="#三、六层访问控制" class="headerlink" title="三、六层访问控制"></a>三、六层访问控制</h2><h3 id="3-1-Gate-函数"><a href="#3-1-Gate-函数" class="headerlink" title="3.1 Gate 函数"></a>3.1 Gate 函数</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/mcp/channelNotification.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">gateChannelServer</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  serverName: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  capabilities: ServerCapabilities | <span class="literal">undefined</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  pluginSource: <span class="built_in">string</span> | <span class="literal">undefined</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">ChannelGateResult</span>  // </span>&#123; action: <span class="string">'register'</span> &#125; | &#123; action: <span class="string">'skip'</span>, kind, reason &#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-六层关卡详解"><a href="#3-2-六层关卡详解" class="headerlink" title="3.2 六层关卡详解"></a>3.2 六层关卡详解</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    六层访问控制                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 1: 能力声明（Capability）                             │</span><br><span class="line">│    └─ MCP Server 必须声明 claude&#x2F;channel 能力              │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 2: 运行时开关（Runtime Gate）                         │</span><br><span class="line">│    └─ tengu_harbor Feature Flag 必须开启                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 3: OAuth 认证（Auth）                                 │</span><br><span class="line">│    └─ 必须通过 OAuth 认证（API Key 用户被阻止）             │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 4: 组织策略（Policy）                                 │</span><br><span class="line">│    └─ Teams&#x2F;Enterprise 必须在托管设置中显式启用             │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 5: 会话白名单（Session）                              │</span><br><span class="line">│    └─ 必须在 --channels 参数列表中                         │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 6: Marketplace 验证 + 白名单（Allowlist）             │</span><br><span class="line">│    ├─ 验证插件来源标签与实际安装来源匹配                    │</span><br><span class="line">│    └─ 插件必须在 GrowthBook 审批白名单中                   │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="3-3-Gate-结果类型"><a href="#3-3-Gate-结果类型" class="headerlink" title="3.3 Gate 结果类型"></a>3.3 Gate 结果类型</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ChannelGateResult =</span><br><span class="line">  | &#123; action: <span class="string">'register'</span> &#125;           <span class="comment">// 通过所有检查</span></span><br><span class="line">  | &#123; action: <span class="string">'skip'</span>; kind: <span class="built_in">string</span>; reason: <span class="built_in">string</span> &#125;  <span class="comment">// 某层拦截</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// kind 枚举：capability | disabled | auth | policy | session | marketplace | allowlist</span></span><br></pre></td></tr></table></figure><hr><h2 id="四、权限中继系统"><a href="#四、权限中继系统" class="headerlink" title="四、权限中继系统"></a>四、权限中继系统</h2><h3 id="4-1-为什么需要权限中继"><a href="#4-1-为什么需要权限中继" class="headerlink" title="4.1 为什么需要权限中继"></a>4.1 为什么需要权限中继</h3><p>当 Claude Code 需要执行敏感操作（如运行 Bash 命令），会弹出权限确认对话框。但如果用户通过 Telegram 远程控制 Agent，他看不到本地终端的对话框。</p><p><strong>权限中继</strong>解决了这个问题：将权限提示转发到 IM 平台，让用户在手机上也能审批或拒绝操作。</p><h3 id="4-2-出站：权限请求"><a href="#4-2-出站：权限请求" class="headerlink" title="4.2 出站：权限请求"></a>4.2 出站：权限请求</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通知 Schema</span></span><br><span class="line"><span class="keyword">const</span> CHANNEL_PERMISSION_REQUEST_METHOD =</span><br><span class="line">  <span class="string">'notifications/claude/channel/permission_request'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> ChannelPermissionRequestParams = &#123;</span><br><span class="line">  request_id: <span class="built_in">string</span>      <span class="comment">// 5 字母标识符（如 "tbxkq"）</span></span><br><span class="line">  tool_name: <span class="built_in">string</span>       <span class="comment">// 工具名（如 "Bash"）</span></span><br><span class="line">  description: <span class="built_in">string</span>     <span class="comment">// 人类可读描述</span></span><br><span class="line">  input_preview: <span class="built_in">string</span>   <span class="comment">// JSON 输入预览，截断到 200 字符</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-Short-Request-ID-设计"><a href="#4-3-Short-Request-ID-设计" class="headerlink" title="4.3 Short Request ID 设计"></a>4.3 Short Request ID 设计</h3><p>5 个字母标识符的设计充满巧思：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/mcp/channelPermissions.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">shortRequestId</span>(<span class="params">toolUseID: <span class="built_in">string</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 25 字母表：a-z 去掉 l（与 1/I 混淆）</span></span><br><span class="line">  <span class="keyword">const</span> alphabet = <span class="string">'abcdefghijkmnopqrstuvwxyz'</span></span><br><span class="line">  <span class="keyword">const</span> id = hashToId(toolUseID, alphabet)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 脏话过滤</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> bad of ID_AVOID_SUBSTRINGS) &#123;</span><br><span class="line">    <span class="keyword">if</span> (id.includes(bad)) &#123;</span><br><span class="line">      <span class="keyword">return</span> shortRequestId(<span class="string">`<span class="subst">$&#123;toolUseID&#125;</span>:retry`</span>)  <span class="comment">// 重试</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> id</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>设计决策</strong>：</p><ul><li><strong>纯字母</strong>：手机用户不需要切换键盘模式</li><li><strong>大小写不敏感</strong>：适配手机自动更正</li><li><strong>脏话过滤</strong>：防止尴尬场景</li></ul><h3 id="4-4-入站：权限响应"><a href="#4-4-入站：权限响应" class="headerlink" title="4.4 入站：权限响应"></a>4.4 入站：权限响应</h3><p>用户在 IM 中回复格式：<code>yes tbxkq</code> 或 <code>no tbxkq</code></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 服务端解析正则</span></span><br><span class="line"><span class="keyword">const</span> PERMISSION_REPLY_RE = <span class="regexp">/^\s*(y|yes|n|no)\s+([a-km-z]&#123;5&#125;)\s*$/i</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 结构化通知</span></span><br><span class="line"><span class="keyword">const</span> ChannelPermissionNotificationSchema = z.object(&#123;</span><br><span class="line">  method: z.literal(<span class="string">'notifications/claude/channel/permission'</span>),</span><br><span class="line">  params: z.object(&#123;</span><br><span class="line">    request_id: z.string(),</span><br><span class="line">    behavior: z.enum([<span class="string">'allow'</span>, <span class="string">'deny'</span>]),</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="4-5-多源竞争"><a href="#4-5-多源竞争" class="headerlink" title="4.5 多源竞争"></a>4.5 多源竞争</h3><p>权限响应来自四个来源，先到先得：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐</span><br><span class="line">│   本地终端    │   │    Bridge    │   │   Channels   │   │    Hooks     │</span><br><span class="line">│  Local UI    │   │   远程控制    │   │ Telegram etc │   │  Permission  │</span><br><span class="line">└──────┬───────┘   └──────┬───────┘   └──────┬───────┘   └──────┬───────┘</span><br><span class="line">       │                   │                   │                  │</span><br><span class="line">       └───────────────────┴───────────────────┴──────────────────┘</span><br><span class="line">                                    │</span><br><span class="line">                              claim() — 先到先得</span><br><span class="line">                                    │</span><br><span class="line">                              ┌─────┴─────┐</span><br><span class="line">                              │  resolve   │</span><br><span class="line">                              │  allow&#x2F;deny│</span><br><span class="line">                              └───────────┘</span><br></pre></td></tr></table></figure><hr><h2 id="五、安全设计"><a href="#五、安全设计" class="headerlink" title="五、安全设计"></a>五、安全设计</h2><h3 id="5-1-XML-注入防护"><a href="#5-1-XML-注入防护" class="headerlink" title="5.1 XML 注入防护"></a>5.1 XML 注入防护</h3><p>Channel 消息中的元数据会成为 XML 属性。两道防线：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 键名过滤：只允许纯标识符格式</span></span><br><span class="line"><span class="keyword">const</span> SAFE_META_KEY = <span class="regexp">/^[a-zA-Z_][a-zA-Z0-9_]*$/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 值转义</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">escapeXmlAttr</span>(<span class="params">value: <span class="built_in">string</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> value</span><br><span class="line">    .replace(<span class="regexp">/&amp;/g</span>, <span class="string">'&amp;amp;'</span>)</span><br><span class="line">    .replace(<span class="regexp">/"/g</span>, <span class="string">'&amp;quot;'</span>)</span><br><span class="line">    .replace(<span class="regexp">/&lt;/g</span>, <span class="string">'&amp;lt;'</span>)</span><br><span class="line">    .replace(<span class="regexp">/&gt;/g</span>, <span class="string">'&amp;gt;'</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-Marketplace-验证"><a href="#5-2-Marketplace-验证" class="headerlink" title="5.2 Marketplace 验证"></a>5.2 Marketplace 验证</h3><p><code>--channels plugin:slack@anthropic</code> 只是用户的”意图声明”。运行时验证：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> actual = pluginSource</span><br><span class="line">  ? parsePluginIdentifier(pluginSource).marketplace</span><br><span class="line">  : <span class="literal">undefined</span></span><br><span class="line"><span class="keyword">if</span> (actual !== entry.marketplace) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123; action: <span class="string">'skip'</span>, kind: <span class="string">'marketplace'</span>, reason: <span class="string">'Tag mismatch'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-3-权限中继的信任边界"><a href="#5-3-权限中继的信任边界" class="headerlink" title="5.3 权限中继的信任边界"></a>5.3 权限中继的信任边界</h3><p><strong>问题</strong>：Claude 会自我审批吗？</p><p><strong>答案</strong>：审批方是通过 Channel 的人类，不是 Claude。但信任边界不是终端——而是白名单。一个被妥协的 Channel Server 可以伪造响应，但：</p><ul><li>它本来就有无限的对话注入能力</li><li>权限对话框减缓攻击速度，但不能完全阻止</li></ul><h3 id="5-4-skipSlashCommands"><a href="#5-4-skipSlashCommands" class="headerlink" title="5.4 skipSlashCommands"></a>5.4 skipSlashCommands</h3><p>Channel 消息入队时设置 <code>skipSlashCommands: true</code>，确保 IM 用户发送的 <code>/help</code> 等文本不会被解释为 Claude Code 的斜杠命令。</p><hr><h2 id="六、插件-Channel-架构"><a href="#六、插件-Channel-架构" class="headerlink" title="六、插件 Channel 架构"></a>六、插件 Channel 架构</h2><h3 id="6-1-Plugin-Manifest-声明"><a href="#6-1-Plugin-Manifest-声明" class="headerlink" title="6.1 Plugin Manifest 声明"></a>6.1 Plugin Manifest 声明</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"name"</span>: <span class="string">"telegram"</span>,</span><br><span class="line">  <span class="attr">"version"</span>: <span class="string">"1.0.0"</span>,</span><br><span class="line">  <span class="attr">"mcpServers"</span>: &#123;</span><br><span class="line">    <span class="attr">"tg"</span>: &#123;</span><br><span class="line">      <span class="attr">"command"</span>: <span class="string">"node"</span>,</span><br><span class="line">      <span class="attr">"args"</span>: [<span class="string">"./server.js"</span>],</span><br><span class="line">      <span class="attr">"env"</span>: &#123;</span><br><span class="line">        <span class="attr">"BOT_TOKEN"</span>: <span class="string">"$&#123;user_config.bot_token&#125;"</span>,</span><br><span class="line">        <span class="attr">"OWNER_ID"</span>: <span class="string">"$&#123;user_config.owner_id&#125;"</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">"channels"</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">"server"</span>: <span class="string">"tg"</span>,</span><br><span class="line">      <span class="attr">"displayName"</span>: <span class="string">"Telegram"</span>,</span><br><span class="line">      <span class="attr">"userConfig"</span>: &#123;</span><br><span class="line">        <span class="attr">"bot_token"</span>: &#123;</span><br><span class="line">          <span class="attr">"type"</span>: <span class="string">"string"</span>,</span><br><span class="line">          <span class="attr">"description"</span>: <span class="string">"Telegram Bot API Token"</span>,</span><br><span class="line">          <span class="attr">"required"</span>: <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">"secret"</span>: <span class="literal">true</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">"owner_id"</span>: &#123;</span><br><span class="line">          <span class="attr">"type"</span>: <span class="string">"string"</span>,</span><br><span class="line">          <span class="attr">"description"</span>: <span class="string">"Your Telegram User ID"</span>,</span><br><span class="line">          <span class="attr">"required"</span>: <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-作用域命名"><a href="#6-2-作用域命名" class="headerlink" title="6.2 作用域命名"></a>6.2 作用域命名</h3><p>插件提供的 MCP Server 会被添加作用域前缀：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 输入：&#123; "tg": &#123; ... &#125; &#125; from telegram@anthropic</span></span><br><span class="line"><span class="comment">// 输出：&#123; "plugin:telegram:tg": &#123; ... &#125; &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addPluginScopeToServers</span>(<span class="params">servers, pluginName, pluginSource</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> scopedServers = &#123;&#125;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> [name, config] of <span class="built_in">Object</span>.entries(servers)) &#123;</span><br><span class="line">    <span class="keyword">const</span> scopedName = <span class="string">`plugin:<span class="subst">$&#123;pluginName&#125;</span>:<span class="subst">$&#123;name&#125;</span>`</span></span><br><span class="line">    scopedServers[scopedName] = &#123;</span><br><span class="line">      ...config,</span><br><span class="line">      scope: <span class="string">'dynamic'</span>,</span><br><span class="line">      pluginSource,</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> scopedServers</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、命令行接口"><a href="#七、命令行接口" class="headerlink" title="七、命令行接口"></a>七、命令行接口</h2><h3 id="7-1-启动参数"><a href="#7-1-启动参数" class="headerlink" title="7.1 启动参数"></a>7.1 启动参数</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用已审批的 Channel 插件</span></span><br><span class="line">claude --channels plugin:telegram@anthropic plugin:feishu@anthropic</span><br><span class="line"></span><br><span class="line"><span class="comment"># 本地开发模式（旁路白名单）</span></span><br><span class="line">claude --dangerously-load-development-channels plugin:my-channel@<span class="built_in">local</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 两者可以同时使用</span></span><br><span class="line">claude --channels plugin:telegram@anthropic \</span><br><span class="line">       --dangerously-load-development-channels plugin:dev-channel@<span class="built_in">local</span></span><br></pre></td></tr></table></figure><h3 id="7-2-特性门控"><a href="#7-2-特性门控" class="headerlink" title="7.2 特性门控"></a>7.2 特性门控</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/main.tsx</span></span><br><span class="line"><span class="keyword">if</span> (feature(<span class="string">'KAIROS'</span>) || feature(<span class="string">'KAIROS_CHANNELS'</span>)) &#123;</span><br><span class="line">  program.addOption(<span class="keyword">new</span> Option(<span class="string">'--channels &lt;servers...&gt;'</span>, <span class="string">'...'</span>).hideHelp())</span><br><span class="line">  program.addOption(<span class="keyword">new</span> Option(<span class="string">'--dangerously-load-development-channels &lt;servers...&gt;'</span>, <span class="string">'...'</span>).hideHelp())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>hideHelp()</code> 表示这些选项不会出现在 <code>--help</code> 输出中——Channel 功能目前处于隐藏特性阶段。</p><hr><h2 id="八、关键源文件索引"><a href="#八、关键源文件索引" class="headerlink" title="八、关键源文件索引"></a>八、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>行数</th><th>职责</th></tr></thead><tbody><tr><td><code>src/services/mcp/channelNotification.ts</code></td><td>~320</td><td>门控、消息封装、白名单集成</td></tr><tr><td><code>src/services/mcp/channelPermissions.ts</code></td><td>~240</td><td>权限中继、请求 ID 生成</td></tr><tr><td><code>src/services/mcp/channelAllowlist.ts</code></td><td>~80</td><td>GrowthBook 白名单查询</td></tr><tr><td><code>src/services/mcp/useManageMCPConnections.ts</code></td><td>-</td><td>连接管理、通知处理器注册</td></tr><tr><td><code>src/components/messages/UserChannelMessage.tsx</code></td><td>~140</td><td>终端渲染 Channel 消息</td></tr><tr><td><code>src/components/DevChannelsDialog.tsx</code></td><td>~105</td><td>开发模式确认对话框</td></tr><tr><td><code>src/utils/plugins/mcpPluginIntegration.ts</code></td><td>-</td><td>插件 MCP 集成、作用域命名</td></tr><tr><td><code>src/bootstrap/state.ts</code></td><td>-</td><td>全局 Channel 白名单状态</td></tr></tbody></table></div><hr><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>Channel 系统体现了几个核心设计原则：</p><ol><li><strong>安全优先</strong>：六层访问控制确保只有授权 Channel 能推送消息</li><li><strong>协议驱动</strong>：Channel 就是 MCP Server，任何语言都可以实现</li><li><strong>松耦合</strong>：Channel 失败不会阻断本地工作流</li><li><strong>渐进式信任</strong>：从全局开关到白名单，信任级别逐级递增</li><li><strong>插件友好</strong>：声明式配置，自动用户配置提示</li><li><strong>权限中继</strong>：远程审批危险操作</li></ol><p>这个设计让 Claude Code 真正成为一个”无处不在”的 AI 编程助手。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-memory-system/">Memory 系统：跨会话持久化知识库</a></li><li>下一篇：<a href="/claude-code-computer-use/">Computer Use：桌面控制的九层安全关卡</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;你在手机上打开 Telegram，给 Claude Code 发一条消息，它就开始在你的电脑上工作——这就是 Channel 系统。它打破了 AI 编程助手只能在终端中交互的限制，实现了真正的远程控制。更精妙的是，它有六层访问控制和权限中继机制，确保安全性。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Channel" scheme="https://donehub.github.io/tags/Channel/"/>
    
  </entry>
  
  <entry>
    <title>Memory 系统：跨会话持久化知识库</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-memory-system/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-memory-system/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:37.686Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>让 Claude Code 跨会话记住你是谁、你偏好什么、项目正在发生什么——这是 Memory 系统的核心目标。它不是简单的聊天记录持久化，而是一个结构化的知识管理系统，通过四种记忆类型、自动提取机制、团队同步等功能，让 AI 真正”理解”你。</p></blockquote><a id="more"></a><h2 id="导读：为什么需要-Memory？"><a href="#导读：为什么需要-Memory？" class="headerlink" title="导读：为什么需要 Memory？"></a>导读：为什么需要 Memory？</h2><p>假设你今天让 Claude Code 帮你重构代码，明天让它继续优化。如果没有 Memory：</p><ul><li>Claude 不知道你昨天做了什么</li><li>Claude 不知道你的编码风格偏好</li><li>Claude 不知道项目正在进行的决策</li></ul><p>每次对话都从零开始，这是 AI 编程助手的一个根本性限制。</p><p>Claude Code 的 Memory 系统解决了这个问题：<strong>让 AI 跨会话积累知识</strong>。</p><hr><h2 id="一、四种记忆类型"><a href="#一、四种记忆类型" class="headerlink" title="一、四种记忆类型"></a>一、四种记忆类型</h2><h3 id="1-1-类型定义"><a href="#1-1-类型定义" class="headerlink" title="1.1 类型定义"></a>1.1 类型定义</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────┐</span><br><span class="line">│                   四种记忆类型                        │</span><br><span class="line">├─────────────────────────────────────────────────────┤</span><br><span class="line">│                                                     │</span><br><span class="line">│  User（用户画像）                                    │</span><br><span class="line">│    └─ 角色、目标、技能水平、偏好                     │</span><br><span class="line">│    └─ 示例：&quot;用户是数据科学家，关注日志系统&quot;        │</span><br><span class="line">│                                                     │</span><br><span class="line">│  Feedback（行为反馈）                                │</span><br><span class="line">│    └─ 对 Claude 工作方式的纠正或肯定                │</span><br><span class="line">│    └─ 示例：&quot;集成测试用真实数据库，不用 mock&quot;        │</span><br><span class="line">│                                                     │</span><br><span class="line">│  Project（项目动态）                                 │</span><br><span class="line">│    └─ 谁在做什么、为什么、截止日期                  │</span><br><span class="line">│    └─ 示例：&quot;3&#x2F;5 起合并冻结，移动团队发版&quot;          │</span><br><span class="line">│                                                     │</span><br><span class="line">│  Reference（外部引用）                               │</span><br><span class="line">│    └─ 外部系统指针：仪表板、工单系统、Slack 频道    │</span><br><span class="line">│    └─ 示例：&quot;Pipeline bug 在 Linear &#39;INGEST&#39; 项目&quot;  │</span><br><span class="line">│                                                     │</span><br><span class="line">└─────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-2-不存储的内容"><a href="#1-2-不存储的内容" class="headerlink" title="1.2 不存储的内容"></a>1.2 不存储的内容</h3><p>Memory 系统有一个核心原则：<strong>只记住无法从代码中推断出来的东西</strong>。</p><div class="table-container"><table><thead><tr><th>记住</th><th>不记住</th></tr></thead><tbody><tr><td>你是数据科学家，关注日志系统</td><td>代码架构、文件结构</td></tr><tr><td>“不要 mock 数据库”</td><td>Git 历史、谁改了什么</td></tr><tr><td>周四后冻结非关键合并</td><td>已有的 CLAUDE.md 内容</td></tr><tr><td>Bug 跟踪在 Linear 的 INGEST 项目</td><td>调试方案（修复已在代码里）</td></tr></tbody></table></div><hr><h2 id="二、Memory-存储格式"><a href="#二、Memory-存储格式" class="headerlink" title="二、Memory 存储格式"></a>二、Memory 存储格式</h2><h3 id="2-1-文件结构"><a href="#2-1-文件结构" class="headerlink" title="2.1 文件结构"></a>2.1 文件结构</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">~&#x2F;.claude&#x2F;</span><br><span class="line">└── projects&#x2F;</span><br><span class="line">    └── &#123;项目路径哈希&#125;&#x2F;</span><br><span class="line">        └── memory&#x2F;                    ← 自动记忆目录</span><br><span class="line">            ├── MEMORY.md              ← 索引文件</span><br><span class="line">            ├── user_role.md           ← 用户画像记忆</span><br><span class="line">            ├── feedback_testing.md    ← 行为反馈记忆</span><br><span class="line">            ├── project_freeze.md      ← 项目动态记忆</span><br><span class="line">            ├── reference_linear.md    ← 外部引用记忆</span><br><span class="line">            └── team&#x2F;                  ← 团队共享记忆</span><br><span class="line">                ├── MEMORY.md</span><br><span class="line">                └── ...</span><br></pre></td></tr></table></figure><h3 id="2-2-记忆文件格式"><a href="#2-2-记忆文件格式" class="headerlink" title="2.2 记忆文件格式"></a>2.2 记忆文件格式</h3><p>每个记忆文件使用 YAML frontmatter + Markdown：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">name: testing_policy</span><br><span class="line">description: 集成测试必须用真实数据库，不能用 mock</span><br><span class="line">type: feedback</span><br><span class="line">---</span><br><span class="line"></span><br><span class="line"><span class="strong">**规则：**</span> 集成测试必须连接真实数据库，禁止使用 mock。</span><br><span class="line"></span><br><span class="line"><span class="strong">**Why:**</span> 去年第四季度出现过 mock 测试通过但生产迁移失败的问题。</span><br><span class="line"></span><br><span class="line"><span class="strong">**How to apply:**</span> 所有标记为 integration test 的测试文件都要使用测试数据库连接。</span><br></pre></td></tr></table></figure><h3 id="2-3-MEMORY-md-索引文件"><a href="#2-3-MEMORY-md-索引文件" class="headerlink" title="2.3 MEMORY.md 索引文件"></a>2.3 MEMORY.md 索引文件</h3><p>MEMORY.md 是索引而不是内容，它<strong>始终加载到上下文</strong>中：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># Memory Index</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">- </span>[<span class="string">用户角色</span>](<span class="link">user_role.md</span>) — 数据科学家，关注可观测性/日志</span><br><span class="line"><span class="bullet">- </span>[<span class="string">测试策略</span>](<span class="link">feedback_testing.md</span>) — 集成测试用真实数据库，不 mock</span><br><span class="line"><span class="bullet">- </span>[<span class="string">合并冻结</span>](<span class="link">project_freeze.md</span>) — 2026-03-05 起冻结非关键合并</span><br><span class="line"><span class="bullet">- </span>[<span class="string">Bug 追踪</span>](<span class="link">reference_linear.md</span>) — 流水线 bug 在 Linear INGEST 项目</span><br></pre></td></tr></table></figure><p><strong>限制</strong>：最多 200 行或 25KB，超出会被截断。</p><hr><h2 id="三、Memory-提取机制"><a href="#三、Memory-提取机制" class="headerlink" title="三、Memory 提取机制"></a>三、Memory 提取机制</h2><h3 id="3-1-自动提取流程"><a href="#3-1-自动提取流程" class="headerlink" title="3.1 自动提取流程"></a>3.1 自动提取流程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">触发时机：每次模型完成回复（无 tool_use）时</span><br><span class="line">         ↓</span><br><span class="line">守卫检查：主代理？功能门控开启？自动记忆启用？</span><br><span class="line">         ↓</span><br><span class="line">频率控制：turnsSinceLastExtraction++ (默认每 1 次)</span><br><span class="line">         ↓</span><br><span class="line">互斥检查：主代理自己写了记忆？→ 跳过</span><br><span class="line">         ↓</span><br><span class="line">扫描现有记忆 → 生成清单</span><br><span class="line">         ↓</span><br><span class="line">运行分叉代理（runForkedAgent）</span><br><span class="line">  - 共享父会话提示词缓存</span><br><span class="line">  - 最多 5 个 turn</span><br><span class="line">  - 限制工具权限</span><br><span class="line">         ↓</span><br><span class="line">写入新记忆文件 + 更新 MEMORY.md</span><br><span class="line">         ↓</span><br><span class="line">通知用户：&quot;Memory updated in ...&quot;</span><br></pre></td></tr></table></figure><h3 id="3-2-工具权限限制"><a href="#3-2-工具权限限制" class="headerlink" title="3.2 工具权限限制"></a>3.2 工具权限限制</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/extractMemories/extractMemories.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createAutoMemCanUseTool</span>(<span class="params">memoryDir: <span class="built_in">string</span></span>): <span class="title">CanUseToolFn</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function">(<span class="params">toolName, input</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// ✅ 允许：Read, Grep, Glob（无限制）</span></span><br><span class="line">    <span class="keyword">if</span> ([<span class="string">'Read'</span>, <span class="string">'Grep'</span>, <span class="string">'Glob'</span>].includes(toolName)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ✅ 允许：Bash（只读命令：ls, find, grep, cat...）</span></span><br><span class="line">    <span class="keyword">if</span> (toolName === <span class="string">'Bash'</span> &amp;&amp; isReadOnlyCommand(input.command)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ✅ 允许：Edit/Write（仅 auto-memory 目录内）</span></span><br><span class="line">    <span class="keyword">if</span> ([<span class="string">'Edit'</span>, <span class="string">'Write'</span>].includes(toolName)) &#123;</span><br><span class="line">      <span class="keyword">return</span> isInsideMemoryDir(input.file_path, memoryDir)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ❌ 拒绝：MCP, Agent, 非只读 Bash</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-互斥机制"><a href="#3-3-互斥机制" class="headerlink" title="3.3 互斥机制"></a>3.3 互斥机制</h3><p>防止重复保存：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">hasMemoryWritesSince</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  messages: Message[],</span></span></span><br><span class="line"><span class="function"><span class="params">  sinceUuid: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 扫描 sinceUuid 之后的所有 assistant 消息</span></span><br><span class="line">  <span class="comment">// 如果有任何 Edit/Write 指向 auto-memory 目录 → return true</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> msg of messages) &#123;</span><br><span class="line">    <span class="keyword">if</span> (msg.uuid === sinceUuid) <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">if</span> (msg.type === <span class="string">'assistant'</span>) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">const</span> block of msg.content) &#123;</span><br><span class="line">        <span class="keyword">if</span> (block.type === <span class="string">'tool_use'</span> &amp;&amp; </span><br><span class="line">            [<span class="string">'Edit'</span>, <span class="string">'Write'</span>].includes(block.name) &amp;&amp;</span><br><span class="line">            isMemoryPath(block.input.file_path)) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="四、Memory-在-Prompt-中的使用"><a href="#四、Memory-在-Prompt-中的使用" class="headerlink" title="四、Memory 在 Prompt 中的使用"></a>四、Memory 在 Prompt 中的使用</h2><h3 id="4-1-系统提示词注入"><a href="#4-1-系统提示词注入" class="headerlink" title="4.1 系统提示词注入"></a>4.1 系统提示词注入</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/memdir/memdir.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">loadMemoryPrompt</span>(<span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">string</span> | <span class="title">null</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> sections = []</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1. 记忆类型说明</span></span><br><span class="line">  sections.push(<span class="string">`## Types of memory`</span>)</span><br><span class="line">  sections.push(...getMemoryTypeDescriptions())</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 不存储的内容</span></span><br><span class="line">  sections.push(<span class="string">`## What NOT to save`</span>)</span><br><span class="line">  sections.push(...getWhatNotToSaveList())</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 如何保存记忆</span></span><br><span class="line">  sections.push(<span class="string">`## How to save memories`</span>)</span><br><span class="line">  sections.push(getSaveInstructions())</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 何时查阅记忆</span></span><br><span class="line">  sections.push(<span class="string">`## When to access memories`</span>)</span><br><span class="line">  sections.push(getAccessGuidelines())</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. 引用前验证</span></span><br><span class="line">  sections.push(<span class="string">`## Before recommending`</span>)</span><br><span class="line">  sections.push(getValidationGuidelines())</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 6. MEMORY.md 索引内容</span></span><br><span class="line">  sections.push(<span class="string">`## MEMORY.md`</span>)</span><br><span class="line">  sections.push(<span class="keyword">await</span> loadMemoryIndex())</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> sections.join(<span class="string">'\n\n'</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-智能检索"><a href="#4-2-智能检索" class="headerlink" title="4.2 智能检索"></a>4.2 智能检索</h3><p>每次用户查询时动态选择相关记忆：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/memdir/findRelevantMemories.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">findRelevantMemories</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  query: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  memoryDir: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  recentTools: <span class="built_in">string</span>[] = [],</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">RelevantMemory</span>[]&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 扫描所有 .md 文件（排除 MEMORY.md）</span></span><br><span class="line">  <span class="keyword">const</span> files = <span class="keyword">await</span> scanMemoryFiles(memoryDir)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 解析 frontmatter（前 30 行）</span></span><br><span class="line">  <span class="keyword">const</span> candidates = <span class="keyword">await</span> <span class="built_in">Promise</span>.all(</span><br><span class="line">    files.map(<span class="function"><span class="params">f</span> =&gt;</span> parseMemoryFile(f))</span><br><span class="line">  )</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. Sonnet 模型选择</span></span><br><span class="line">  <span class="keyword">const</span> selected = <span class="keyword">await</span> sideQuery(&#123;</span><br><span class="line">    model: <span class="string">'claude-sonnet-4-5'</span>,</span><br><span class="line">    systemPrompt: MEMORY_SELECTOR_PROMPT,</span><br><span class="line">    messages: [&#123; role: <span class="string">'user'</span>, content: query &#125;],</span><br><span class="line">    context: &#123; candidates, recentTools &#125;,</span><br><span class="line">  &#125;)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 返回选中记忆</span></span><br><span class="line">  <span class="keyword">return</span> selected.map(<span class="function"><span class="params">s</span> =&gt;</span> (&#123; path: s.path, mtimeMs: s.mtimeMs &#125;))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Sonnet 选择器提示词</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">你是记忆选择器。从候选记忆中选择对处理用户查询有用的记忆。</span><br><span class="line"></span><br><span class="line">规则：</span><br><span class="line">- 最多选 5 个</span><br><span class="line">- 不确定是否有用就不要选</span><br><span class="line">- 如果提供了最近使用的工具列表，不要选这些工具的使用文档</span><br><span class="line">  （但要选择关于这些工具的警告&#x2F;陷阱&#x2F;已知问题）</span><br><span class="line"></span><br><span class="line">返回格式：</span><br><span class="line">- memory_path: 选择原因</span><br></pre></td></tr></table></figure><h3 id="4-3-新鲜度警告"><a href="#4-3-新鲜度警告" class="headerlink" title="4.3 新鲜度警告"></a>4.3 新鲜度警告</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">memoryFreshnessText</span>(<span class="params">mtimeMs: <span class="built_in">number</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> days = memoryAgeDays(mtimeMs)</span><br><span class="line">  <span class="keyword">if</span> (days &lt;= <span class="number">1</span>) <span class="keyword">return</span> <span class="string">''</span>  <span class="comment">// 今天/昨天：无警告</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="string">`This memory is <span class="subst">$&#123;days&#125;</span> days old. Memories are point-in-time </span></span><br><span class="line"><span class="string">observations that may become stale. Verify against current code before </span></span><br><span class="line"><span class="string">asserting as fact.`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="五、团队-Memory-同步"><a href="#五、团队-Memory-同步" class="headerlink" title="五、团队 Memory 同步"></a>五、团队 Memory 同步</h2><h3 id="5-1-API-端点"><a href="#5-1-API-端点" class="headerlink" title="5.1 API 端点"></a>5.1 API 端点</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">GET  &#x2F;api&#x2F;claude_code&#x2F;team_memory?repo&#x3D;&#123;owner&#x2F;repo&#125;  ← 拉取</span><br><span class="line">PUT  &#x2F;api&#x2F;claude_code&#x2F;team_memory?repo&#x3D;&#123;owner&#x2F;repo&#125;  ← 推送</span><br></pre></td></tr></table></figure><h3 id="5-2-同步语义"><a href="#5-2-同步语义" class="headerlink" title="5.2 同步语义"></a>5.2 同步语义</h3><div class="table-container"><table><thead><tr><th>操作</th><th>行为</th></tr></thead><tbody><tr><td><strong>Pull</strong></td><td>服务器内容覆盖本地文件（服务器优先）</td></tr><tr><td><strong>Push</strong></td><td>仅上传内容哈希不同的键（delta 上传）</td></tr><tr><td><strong>删除</strong></td><td>本地删除不会删除远程（下次 pull 会恢复）</td></tr></tbody></table></div><h3 id="5-3-冲突解决"><a href="#5-3-冲突解决" class="headerlink" title="5.3 冲突解决"></a>5.3 冲突解决</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">pushTeamMemory</span>(<span class="params">state</span>): <span class="title">Promise</span>&lt;<span class="title">PushResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 读取本地文件 → 计算哈希</span></span><br><span class="line">  <span class="keyword">const</span> localFiles = <span class="keyword">await</span> readLocalMemoryFiles()</span><br><span class="line">  <span class="keyword">const</span> localHashes = computeHashes(localFiles)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 对比 serverChecksums → 生成 delta</span></span><br><span class="line">  <span class="keyword">const</span> delta = computeDelta(localHashes, state.serverChecksums)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 上传 delta</span></span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> api.pushTeamMemory(delta)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 遇到 412 冲突：</span></span><br><span class="line">  <span class="keyword">if</span> (response.status === <span class="number">412</span>) &#123;</span><br><span class="line">    <span class="comment">// 探测 GET ?view=hashes 获取最新 checksums</span></span><br><span class="line">    <span class="keyword">const</span> latest = <span class="keyword">await</span> api.getTeamMemoryHashes()</span><br><span class="line">    <span class="comment">// 重新计算 delta（排除队友已推送的相同内容）</span></span><br><span class="line">    <span class="keyword">const</span> newDelta = recomputeDelta(localHashes, latest)</span><br><span class="line">    <span class="comment">// 重试（最多 2 次）</span></span><br><span class="line">    <span class="keyword">return</span> pushTeamMemory(&#123; ...state, serverChecksums: latest &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; success: <span class="literal">true</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-4-安全限制"><a href="#5-4-安全限制" class="headerlink" title="5.4 安全限制"></a>5.4 安全限制</h3><ul><li><strong>单文件最大</strong>：250KB</li><li><strong>上传体最大</strong>：200KB（分批上传）</li><li><strong>秘密扫描</strong>：使用 gitleaks 规则扫描凭证，检测到则跳过该文件</li></ul><hr><h2 id="六、AutoDream：后台记忆整合"><a href="#六、AutoDream：后台记忆整合" class="headerlink" title="六、AutoDream：后台记忆整合"></a>六、AutoDream：后台记忆整合</h2><h3 id="6-1-触发条件"><a href="#6-1-触发条件" class="headerlink" title="6.1 触发条件"></a>6.1 触发条件</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/autoDream/autoDream.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">shouldTriggerAutoDream</span>(<span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">boolean</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 四重门控</span></span><br><span class="line">  <span class="keyword">if</span> (hoursSinceLastConsolidation &lt; minHours) <span class="keyword">return</span> <span class="literal">false</span>  <span class="comment">// 时间门：默认 24h</span></span><br><span class="line">  <span class="keyword">if</span> (sessionsSinceLastConsolidation &lt; minSessions) <span class="keyword">return</span> <span class="literal">false</span>  <span class="comment">// 会话门：默认 5 次</span></span><br><span class="line">  <span class="keyword">if</span> (otherProcessConsolidating) <span class="keyword">return</span> <span class="literal">false</span>  <span class="comment">// 锁门：互斥</span></span><br><span class="line">  <span class="keyword">if</span> (timeSinceLastScan &lt; <span class="number">10</span> * <span class="number">60</span> * <span class="number">1000</span>) <span class="keyword">return</span> <span class="literal">false</span>  <span class="comment">// 扫描节流：10 分钟</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-四阶段流程"><a href="#6-2-四阶段流程" class="headerlink" title="6.2 四阶段流程"></a>6.2 四阶段流程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────┐</span><br><span class="line">│              AutoDream 四阶段流程                    │</span><br><span class="line">├─────────────────────────────────────────────────────┤</span><br><span class="line">│                                                     │</span><br><span class="line">│  Phase 1: 定向（Orientation）                       │</span><br><span class="line">│    └─ 确定要审查的会话列表                          │</span><br><span class="line">│                                                     │</span><br><span class="line">│  Phase 2: 收集（Collection）                        │</span><br><span class="line">│    └─ 从会话中提取候选记忆                          │</span><br><span class="line">│                                                     │</span><br><span class="line">│  Phase 3: 整合（Consolidation）                     │</span><br><span class="line">│    └─ 合并、去重、更新记忆文件                      │</span><br><span class="line">│                                                     │</span><br><span class="line">│  Phase 4: 修剪（Pruning）                           │</span><br><span class="line">│    └─ 删除过时或重复的记忆                          │</span><br><span class="line">│                                                     │</span><br><span class="line">└─────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="6-3-DreamTask-状态"><a href="#6-3-DreamTask-状态" class="headerlink" title="6.3 DreamTask 状态"></a>6.3 DreamTask 状态</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> DreamTaskState = &#123;</span><br><span class="line">  <span class="keyword">type</span>: <span class="string">'dream'</span></span><br><span class="line">  phase: <span class="string">'starting'</span> | <span class="string">'updating'</span>  <span class="comment">// updating = 已开始编辑文件</span></span><br><span class="line">  sessionsReviewing: <span class="built_in">number</span>       <span class="comment">// 正在审查的会话数</span></span><br><span class="line">  filesTouched: <span class="built_in">string</span>[]          <span class="comment">// 编辑过的文件路径</span></span><br><span class="line">  turns: DreamTurn[]              <span class="comment">// 对话轮次记录</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、关键源文件索引"><a href="#七、关键源文件索引" class="headerlink" title="七、关键源文件索引"></a>七、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/memdir/paths.ts</code></td><td>路径解析，优先级链</td></tr><tr><td><code>src/memdir/memdir.ts</code></td><td>提示词构建，MEMORY.md 截断</td></tr><tr><td><code>src/memdir/memoryScan.ts</code></td><td>扫描目录、解析 frontmatter</td></tr><tr><td><code>src/memdir/memoryTypes.ts</code></td><td>四种记忆类型定义</td></tr><tr><td><code>src/memdir/findRelevantMemories.ts</code></td><td>Sonnet 智能检索</td></tr><tr><td><code>src/services/extractMemories/</code></td><td>自动提取服务</td></tr><tr><td><code>src/services/teamMemorySync/</code></td><td>团队记忆同步</td></tr><tr><td><code>src/services/autoDream/</code></td><td>AutoDream 后台整合</td></tr><tr><td><code>src/utils/frontmatterParser.ts</code></td><td>YAML frontmatter 解析</td></tr><tr><td><code>src/components/memory/</code></td><td>UI 组件</td></tr><tr><td><code>src/commands/memory/</code></td><td><code>/memory</code> 命令</td></tr></tbody></table></div><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Claude Code 的 Memory 系统体现了几个核心设计原则：</p><ol><li><strong>结构化知识</strong>：四种记忆类型，避免信息混淆</li><li><strong>自动提取</strong>：后台智能分析对话，提取有价值信息</li><li><strong>智能检索</strong>：Sonnet 模型动态选择相关记忆</li><li><strong>新鲜度管理</strong>：旧记忆附带警告，使用前需验证</li><li><strong>团队同步</strong>：服务器优先语义，支持协作</li><li><strong>AutoDream</strong>：后台”做梦”整理记忆</li></ol><p>这个设计让 AI 真正能够”记住”用户，而不是每次都从零开始。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-permission-security/">权限与安全：分层模型与人机协作</a></li><li>下一篇：<a href="/claude-code-channel-system/">Channel 系统：IM 远程控制 Agent</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;让 Claude Code 跨会话记住你是谁、你偏好什么、项目正在发生什么——这是 Memory 系统的核心目标。它不是简单的聊天记录持久化，而是一个结构化的知识管理系统，通过四种记忆类型、自动提取机制、团队同步等功能，让 AI 真正”理解”你。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Memory" scheme="https://donehub.github.io/tags/Memory/"/>
    
  </entry>
  
  <entry>
    <title>多 Agent 编排：四种代理类型与协作机制</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-multi-agent/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-multi-agent/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:23.188Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Claude Code 的多 Agent 系统可能是其最被低估的设计之一。它不是简单的”子代理调用”，而是一个完整的协作框架：四种 Agent 类型（Subagent、Fork、Teammate、Remote）、Teams 邮箱通信、权限同步、Worktree 隔离。这个设计让 Claude Code 能够处理单 Agent 无法完成的复杂任务。</p></blockquote><a id="more"></a><h2 id="导读：为什么需要多-Agent？"><a href="#导读：为什么需要多-Agent？" class="headerlink" title="导读：为什么需要多 Agent？"></a>导读：为什么需要多 Agent？</h2><p>假设你让 Claude Code 做这件事：</p><blockquote><p>“帮我把这个项目的所有 TypeScript 文件迁移到 strict 模式，同时更新 ESLint 配置，然后运行所有测试确保没有回归。”</p></blockquote><p>单 Agent 会怎么做？顺序执行：修改 tsconfig → 修改 ESLint → 修改文件 → 运行测试。每一步都要等待上一步完成。</p><p>但如果使用多 Agent：</p><ol><li><strong>Explore Agent</strong>：并行扫描所有 TypeScript 文件，识别需要修改的地方</li><li><strong>多个 Fork Agent</strong>：并行修改不同的文件组</li><li><strong>Plan Agent</strong>：协调修改顺序，避免冲突</li><li><strong>Verification Agent</strong>：运行测试，验证修改</li></ol><p>这就是多 Agent 编排的价值：<strong>并行化、专业化、隔离性</strong>。</p><hr><h2 id="一、五种-Agent-类型和职责"><a href="#一、五种-Agent-类型和职责" class="headerlink" title="一、五种 Agent 类型和职责"></a>一、五种 Agent 类型和职责</h2><h3 id="1-1-Agent-类型概览"><a href="#1-1-Agent-类型概览" class="headerlink" title="1.1 Agent 类型概览"></a>1.1 Agent 类型概览</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│                  Agent Tool                      │</span><br><span class="line">│              (入口 &amp; 路由分发)                    │</span><br><span class="line">├───────────┬───────────┬───────────┬─────────────┤</span><br><span class="line">│  Subagent │  Fork     │ Teammate  │   Remote    │</span><br><span class="line">│  (子代理)  │ (分叉)    │ (队友)    │   (远程)    │</span><br><span class="line">│           │           │           │             │</span><br><span class="line">│ 独立上下文 │ 继承上下文 │ 团队协作   │ CCR 环境   │</span><br><span class="line">│ 按类型过滤 │ 缓存共享   │ 邮箱通信   │ 远程执行   │</span><br><span class="line">│ 工具池     │ 字节一致   │ 权限同步   │ 轮询结果   │</span><br><span class="line">└───────────┴───────────┴───────────┴─────────────┘</span><br><span class="line">                        │</span><br><span class="line">                  ┌─────┴─────┐</span><br><span class="line">                  │ DreamTask │</span><br><span class="line">                  │ (记忆整合)  │</span><br><span class="line">                  │ 定时后台   │</span><br><span class="line">                  └───────────┘</span><br></pre></td></tr></table></figure><h3 id="1-2-内置-Agent-类型"><a href="#1-2-内置-Agent-类型" class="headerlink" title="1.2 内置 Agent 类型"></a>1.2 内置 Agent 类型</h3><div class="table-container"><table><thead><tr><th>Agent 类型</th><th>用途</th><th>工具池限制</th></tr></thead><tbody><tr><td><strong>General Purpose</strong></td><td>通用任务</td><td>全部工具</td></tr><tr><td><strong>Explore</strong></td><td>代码库探索</td><td>Read, Grep, Glob, WebSearch</td></tr><tr><td><strong>Plan</strong></td><td>制定计划</td><td>全部工具，但受限输出</td></tr><tr><td><strong>Verification</strong></td><td>验证结果</td><td>Bash, Read, Grep</td></tr><tr><td><strong>Coordinator</strong></td><td>编排协调</td><td>受限工具集</td></tr></tbody></table></div><h3 id="1-3-Agent-定义结构"><a href="#1-3-Agent-定义结构" class="headerlink" title="1.3 Agent 定义结构"></a>1.3 Agent 定义结构</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/AgentTool/loadAgentsDir.ts</span></span><br><span class="line"><span class="keyword">type</span> AgentDefinition = &#123;</span><br><span class="line">  agentType: <span class="built_in">string</span>              <span class="comment">// Agent 类型标识</span></span><br><span class="line">  description: <span class="built_in">string</span>            <span class="comment">// 描述</span></span><br><span class="line">  getSystemPrompt: <span class="function">(<span class="params">context</span>) =&gt;</span> <span class="built_in">string</span>  <span class="comment">// 系统提示词</span></span><br><span class="line">  tools?: <span class="built_in">string</span>[]               <span class="comment">// 允许的工具（'*' = 全部）</span></span><br><span class="line">  disallowedTools?: <span class="built_in">string</span>[]     <span class="comment">// 禁止的工具</span></span><br><span class="line">  model?: <span class="built_in">string</span>                 <span class="comment">// 模型选择</span></span><br><span class="line">  permissionMode?: PermissionMode <span class="comment">// 权限模式</span></span><br><span class="line">  isBuiltin?: <span class="built_in">boolean</span>            <span class="comment">// 是否内置</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="二、Agent-生成流程：四条路径"><a href="#二、Agent-生成流程：四条路径" class="headerlink" title="二、Agent 生成流程：四条路径"></a>二、Agent 生成流程：四条路径</h2><h3 id="2-1-入口：AgentTool-call"><a href="#2-1-入口：AgentTool-call" class="headerlink" title="2.1 入口：AgentTool.call()"></a>2.1 入口：AgentTool.call()</h3><p><code>src/tools/AgentTool/AgentTool.tsx</code> 中的 <code>call()</code> 函数是所有 Agent 生成的入口：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">AgentTool.call(input)</span><br><span class="line">  │</span><br><span class="line">  ├─ team_name + name? ──────→ 路径1: spawnTeammate()</span><br><span class="line">  │</span><br><span class="line">  ├─ run_in_background?  ────→ 路径2: registerAsyncAgent()</span><br><span class="line">  │     └─ agent.background?</span><br><span class="line">  │</span><br><span class="line">  ├─ 省略 subagent_type? ───→ 路径3: Fork (buildForkedMessages())</span><br><span class="line">  │     └─ fork 实验开启?</span><br><span class="line">  │</span><br><span class="line">  └─ 默认 ───────────────────→ 路径4: runAgent() 同步执行</span><br></pre></td></tr></table></figure><h3 id="2-2-路径1：Teammate-生成"><a href="#2-2-路径1：Teammate-生成" class="headerlink" title="2.2 路径1：Teammate 生成"></a>2.2 路径1：Teammate 生成</h3><p><strong>触发条件</strong>：<code>team_name</code> 和 <code>name</code> 同时存在</p><p><strong>流程</strong>：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/shared/spawnMultiAgent.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">spawnTeammate</span>(<span class="params">config, context</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 检测执行后端</span></span><br><span class="line">  <span class="keyword">const</span> backend = detectBackend()  <span class="comment">// tmux / iTerm2 / in-process</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 生成唯一 agentId</span></span><br><span class="line">  <span class="keyword">const</span> agentId = formatAgentId(name, teamName)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 分配颜色</span></span><br><span class="line">  <span class="keyword">const</span> color = assignColor(name)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 创建执行环境</span></span><br><span class="line">  <span class="keyword">if</span> (backend === <span class="string">'in-process'</span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> spawnInProcessTeammate(config, context)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (backend === <span class="string">'tmux'</span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> TmuxBackend.createPane(config)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (backend === <span class="string">'iTerm2'</span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> ITerm2Backend.createWindow(config)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. 写入 TeamFile</span></span><br><span class="line">  <span class="keyword">await</span> updateTeamFile(teamName, &#123; members: [...members, newMember] &#125;)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; status: <span class="string">'teammate_spawned'</span>, agentId, tmuxPaneId, ... &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>In-Process 队友的隔离</strong>：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/swarm/spawnInProcess.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">spawnInProcessTeammate</span>(<span class="params">config, context</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 独立的 AbortController（不随 leader 中断）</span></span><br><span class="line">  <span class="keyword">const</span> abortController = <span class="keyword">new</span> AbortController()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. AsyncLocalStorage 上下文隔离</span></span><br><span class="line">  runWithTeammateContext(teammateContext, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="comment">// 3. 独立的任务状态</span></span><br><span class="line">    <span class="comment">// 4. 独立的消息循环</span></span><br><span class="line">    <span class="comment">// 5. 共享的权限管道（通过 mailbox）</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-路径2：异步-Subagent"><a href="#2-3-路径2：异步-Subagent" class="headerlink" title="2.3 路径2：异步 Subagent"></a>2.3 路径2：异步 Subagent</h3><p><strong>触发条件</strong>：<code>run_in_background=true</code> 或 Agent 定义中 <code>background: true</code></p><p><strong>流程</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">registerAsyncAgent()</span><br><span class="line">  │</span><br><span class="line">  ├─ 创建 LocalAgentTask（status: &#39;running&#39;）</span><br><span class="line">  ├─ 注册到 agentNameRegistry（如有 name）</span><br><span class="line">  ├─ 创建输出文件符号链接</span><br><span class="line">  ├─ 创建 AbortController（链接到父代理）</span><br><span class="line">  ├─ 发射 SDK event: task_started</span><br><span class="line">  │</span><br><span class="line">  └─ void runAsyncAgentLifecycle()  ← 异步分离执行</span><br><span class="line">       │</span><br><span class="line">       ├─ 创建 ProgressTracker</span><br><span class="line">       ├─ 遍历 makeStream() 生成器</span><br><span class="line">       │   ├─ 追加消息到 agentMessages[]</span><br><span class="line">       │   ├─ 更新进度（tokens、tools、activities）</span><br><span class="line">       │   └─ 发射 SDK progress events</span><br><span class="line">       │</span><br><span class="line">       └─ 完成时：</span><br><span class="line">           ├─ finalizeAgentTool()（提取结果）</span><br><span class="line">           ├─ completeAgentTask()（标记完成）</span><br><span class="line">           ├─ 清理 worktree（如有隔离）</span><br><span class="line">           └─ enqueuePendingNotification()（通知主代理）</span><br></pre></td></tr></table></figure><h3 id="2-4-路径3：Fork-Subagent"><a href="#2-4-路径3：Fork-Subagent" class="headerlink" title="2.4 路径3：Fork Subagent"></a>2.4 路径3：Fork Subagent</h3><p><strong>触发条件</strong>：省略 <code>subagent_type</code> 且 Fork 实验开启</p><p><strong>核心优化</strong>：通过字节级一致的 API 请求前缀，实现 <strong>prompt cache 命中</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">buildForkedMessages(directive, assistantMessage)</span><br><span class="line">  │</span><br><span class="line">  ├─ 保留父代理完整的 assistant message（所有 tool_use 块）</span><br><span class="line">  ├─ 构建 user message：</span><br><span class="line">  │   ├─ 对每个 tool_use 创建占位 tool_result（字节一致）</span><br><span class="line">  │   └─ 追加 per-child directive（唯一差异部分）</span><br><span class="line">  │</span><br><span class="line">  └─ 结果：字节级一致的 API 前缀 → prompt cache 命中！</span><br></pre></td></tr></table></figure><p><strong>Fork 子代理的行为约束</strong>（通过 <code>FORK_BOILERPLATE_TAG</code> 注入）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">1. 你是分叉的工作进程，不是主代理</span><br><span class="line">2. 不要对话、提问或建议后续步骤</span><br><span class="line">3. 直接使用工具（Bash、Read、Write 等）</span><br><span class="line">4. 如修改文件，在报告前提交更改</span><br><span class="line">5. 工具调用之间不要输出文本</span><br><span class="line">6. 严格限制在指令范围内</span><br><span class="line">7. 报告控制在 500 词以内</span><br><span class="line">8. 响应必须以 &quot;Scope:&quot; 开头</span><br></pre></td></tr></table></figure><h3 id="2-5-路径4：同步-Subagent"><a href="#2-5-路径4：同步-Subagent" class="headerlink" title="2.5 路径4：同步 Subagent"></a>2.5 路径4：同步 Subagent</h3><p><strong>触发条件</strong>：默认路径（无 team_name、无 background、非 fork）</p><p><strong>流程</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">runAgent(promptMessages, toolUseContext, options)</span><br><span class="line">  │</span><br><span class="line">  ├─ 解析 Agent 定义（getSystemPrompt、tools、permissions）</span><br><span class="line">  ├─ 构建系统提示词（buildEffectiveSystemPrompt）</span><br><span class="line">  ├─ 创建隔离的 ToolUseContext（createSubagentContext）</span><br><span class="line">  ├─ 启动查询循环（query() async generator）</span><br><span class="line">  │   ├─ 发送 API 请求</span><br><span class="line">  │   ├─ 处理流式事件</span><br><span class="line">  │   ├─ 执行工具调用</span><br><span class="line">  │   └─ 累积消息和 usage</span><br><span class="line">  │</span><br><span class="line">  └─ 返回 AgentToolResult</span><br><span class="line">       ├─ content: 最后 assistant 消息的文本</span><br><span class="line">       ├─ totalToolUseCount</span><br><span class="line">       ├─ totalDurationMs</span><br><span class="line">       └─ totalTokens</span><br></pre></td></tr></table></figure><hr><h2 id="三、工具池系统：三层过滤"><a href="#三、工具池系统：三层过滤" class="headerlink" title="三、工具池系统：三层过滤"></a>三、工具池系统：三层过滤</h2><h3 id="3-1-第一层：全局禁止"><a href="#3-1-第一层：全局禁止" class="headerlink" title="3.1 第一层：全局禁止"></a>3.1 第一层：全局禁止</h3><p><code>ALL_AGENT_DISALLOWED_TOOLS</code> — 对所有 Agent 禁止的工具：</p><div class="table-container"><table><thead><tr><th>工具</th><th>禁止原因</th></tr></thead><tbody><tr><td>TaskOutput</td><td>仅主代理可读取任务输出</td></tr><tr><td>ExitPlanMode</td><td>仅主代理可退出计划模式</td></tr><tr><td>EnterPlanMode</td><td>仅主代理可进入计划模式</td></tr><tr><td>AskUserQuestion</td><td>子代理不应直接问用户</td></tr><tr><td>TaskStop</td><td>仅主代理可终止任务</td></tr><tr><td>Agent</td><td>防止递归生成（Ant 内部例外）</td></tr></tbody></table></div><h3 id="3-2-第二层：Agent-类型过滤"><a href="#3-2-第二层：Agent-类型过滤" class="headerlink" title="3.2 第二层：Agent 类型过滤"></a>3.2 第二层：Agent 类型过滤</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/AgentTool/agentToolUtils.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">filterToolsForAgent</span>(<span class="params">tools, agentDef</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 移除 ALL_AGENT_DISALLOWED_TOOLS</span></span><br><span class="line">  <span class="comment">// 2. 如果非内置 Agent，额外移除 CUSTOM_AGENT_DISALLOWED_TOOLS</span></span><br><span class="line">  <span class="comment">// 3. 如果是异步 Agent，限制为 ASYNC_AGENT_ALLOWED_TOOLS</span></span><br><span class="line">  <span class="comment">// 4. MCP 工具始终允许</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>ASYNC_AGENT_ALLOWED_TOOLS</strong>（15 个）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Read, WebSearch, TodoWrite, Grep, WebFetch, Glob,</span><br><span class="line">Bash&#x2F;PowerShell, FileEdit, FileWrite, NotebookEdit,</span><br><span class="line">Skill, SyntheticOutput, ToolSearch, EnterWorktree, ExitWorktree</span><br></pre></td></tr></table></figure><h3 id="3-3-第三层：Agent-定义过滤"><a href="#3-3-第三层：Agent-定义过滤" class="headerlink" title="3.3 第三层：Agent 定义过滤"></a>3.3 第三层：Agent 定义过滤</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">resolveAgentTools</span>(<span class="params">agentDef, availableTools</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (tools === [<span class="string">'*'</span>] || <span class="literal">undefined</span>)  → 通配符，全部允许</span><br><span class="line">  <span class="keyword">if</span> (tools === [<span class="string">'Read'</span>, <span class="string">'Grep'</span>])    → 仅允许列表中的工具</span><br><span class="line">  <span class="keyword">if</span> (disallowedTools === [<span class="string">'Agent'</span>]) → 从可用工具中减去</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>过滤流程图</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">所有可用工具</span><br><span class="line">  │</span><br><span class="line">  ├─ 减去 ALL_AGENT_DISALLOWED_TOOLS ──→ 通用禁止</span><br><span class="line">  │</span><br><span class="line">  ├─ 非内置？减去 CUSTOM_AGENT_DISALLOWED_TOOLS</span><br><span class="line">  │</span><br><span class="line">  ├─ 异步？限制为 ASYNC_AGENT_ALLOWED_TOOLS</span><br><span class="line">  │</span><br><span class="line">  ├─ 有 tools 列表？取交集</span><br><span class="line">  │</span><br><span class="line">  ├─ 有 disallowedTools？取差集</span><br><span class="line">  │</span><br><span class="line">  └─ 最终工具池</span><br></pre></td></tr></table></figure><hr><h2 id="四、上下文传递机制"><a href="#四、上下文传递机制" class="headerlink" title="四、上下文传递机制"></a>四、上下文传递机制</h2><h3 id="4-1-CacheSafeParams-—-缓存安全参数"><a href="#4-1-CacheSafeParams-—-缓存安全参数" class="headerlink" title="4.1 CacheSafeParams — 缓存安全参数"></a>4.1 CacheSafeParams — 缓存安全参数</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/forkedAgent.ts</span></span><br><span class="line"><span class="keyword">type</span> CacheSafeParams = &#123;</span><br><span class="line">  systemPrompt: SystemPrompt       <span class="comment">// 系统提示词</span></span><br><span class="line">  userContext: &#123; [k: <span class="built_in">string</span>]: <span class="built_in">string</span> &#125;  <span class="comment">// 目录结构、CLAUDE.md 等</span></span><br><span class="line">  systemContext: &#123; [k: <span class="built_in">string</span>]: <span class="built_in">string</span> &#125; <span class="comment">// git status、环境信息</span></span><br><span class="line">  toolUseContext: ToolUseContext    <span class="comment">// 工具配置、模型、选项</span></span><br><span class="line">  forkContextMessages: Message[]   <span class="comment">// Fork 上下文消息（用于缓存共享）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>缓存共享原理</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────┐</span><br><span class="line">│         共享前缀（字节一致）              │</span><br><span class="line">│  ┌──────────────────────────────────┐   │</span><br><span class="line">│  │ System Prompt                    │   │</span><br><span class="line">│  │ User Context                     │   │</span><br><span class="line">│  │ System Context                   │   │</span><br><span class="line">│  │ Tool Use Context                 │   │</span><br><span class="line">│  │ 对话历史 Messages                │   │</span><br><span class="line">│  │ Assistant Message (all tool_use) │   │</span><br><span class="line">│  │ User Message (placeholder results)│  │</span><br><span class="line">│  └──────────────────────────────────┘   │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│  唯一差异：per-child directive text      │</span><br><span class="line">└─────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="4-2-SubagentContext-—-子代理上下文隔离"><a href="#4-2-SubagentContext-—-子代理上下文隔离" class="headerlink" title="4.2 SubagentContext — 子代理上下文隔离"></a>4.2 SubagentContext — 子代理上下文隔离</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> SubagentContextOverrides = &#123;</span><br><span class="line">  options?: ToolUseContext[<span class="string">'options'</span>]           <span class="comment">// 自定义工具、模型</span></span><br><span class="line">  agentId?: AgentId                            <span class="comment">// 子代理 ID</span></span><br><span class="line">  agentType?: <span class="built_in">string</span>                           <span class="comment">// Agent 类型</span></span><br><span class="line">  messages?: Message[]                         <span class="comment">// 自定义消息历史</span></span><br><span class="line">  readFileState?: ToolUseContext[<span class="string">'readFileState'</span>] <span class="comment">// 文件读取缓存</span></span><br><span class="line">  abortController?: AbortController            <span class="comment">// 中止控制器</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 显式 opt-in 共享（默认隔离）</span></span><br><span class="line">  shareSetAppState?: <span class="built_in">boolean</span>                   <span class="comment">// 共享 AppState 写入</span></span><br><span class="line">  shareSetResponseLength?: <span class="built_in">boolean</span>             <span class="comment">// 共享响应长度度量</span></span><br><span class="line">  shareAbortController?: <span class="built_in">boolean</span>               <span class="comment">// 共享中止控制器</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 实验性注入</span></span><br><span class="line">  criticalSystemReminder_EXPERIMENTAL?: <span class="built_in">string</span> <span class="comment">// 每轮重新注入的提醒</span></span><br><span class="line">  contentReplacementState?: ContentReplacementState</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>隔离 vs 共享</strong>：</p><div class="table-container"><table><thead><tr><th>资源</th><th>默认</th><th>说明</th></tr></thead><tbody><tr><td>readFileState</td><td>克隆</td><td>文件读取缓存独立</td></tr><tr><td>messages</td><td>新建</td><td>消息历史独立</td></tr><tr><td>abortController</td><td>新建（链接父）</td><td>父取消时子也取消</td></tr><tr><td>setAppState</td><td>No-op</td><td>默认不影响父状态</td></tr><tr><td>contentReplacementState</td><td>克隆</td><td>内容替换状态独立</td></tr></tbody></table></div><hr><h2 id="五、Agent-Teams：邮箱通信"><a href="#五、Agent-Teams：邮箱通信" class="headerlink" title="五、Agent Teams：邮箱通信"></a>五、Agent Teams：邮箱通信</h2><h3 id="5-1-TeamFile-结构"><a href="#5-1-TeamFile-结构" class="headerlink" title="5.1 TeamFile 结构"></a>5.1 TeamFile 结构</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 存储路径：~/.claude/teams/&#123;team_name&#125;/config.json</span></span><br><span class="line">&#123;</span><br><span class="line">  name: <span class="built_in">string</span>                        <span class="comment">// 团队名称</span></span><br><span class="line">  description?: <span class="built_in">string</span>                <span class="comment">// 团队描述</span></span><br><span class="line">  createdAt: <span class="built_in">number</span>                   <span class="comment">// 创建时间戳</span></span><br><span class="line">  leadAgentId: <span class="built_in">string</span>                 <span class="comment">// Team Lead 的 Agent ID</span></span><br><span class="line">  leadSessionId?: <span class="built_in">string</span>              <span class="comment">// Lead 的会话 UUID</span></span><br><span class="line">  hiddenPaneIds?: <span class="built_in">string</span>[]            <span class="comment">// UI 中隐藏的 pane</span></span><br><span class="line">  teamAllowedPaths?: TeamAllowedPath[] <span class="comment">// 团队级共享权限</span></span><br><span class="line">  members: <span class="built_in">Array</span>&lt;&#123;</span><br><span class="line">    agentId: <span class="built_in">string</span>                   <span class="comment">// 成员 Agent ID</span></span><br><span class="line">    name: <span class="built_in">string</span>                      <span class="comment">// 显示名称</span></span><br><span class="line">    agentType?: <span class="built_in">string</span>                <span class="comment">// 角色类型</span></span><br><span class="line">    model?: <span class="built_in">string</span>                    <span class="comment">// 使用的模型</span></span><br><span class="line">    prompt?: <span class="built_in">string</span>                   <span class="comment">// 初始任务</span></span><br><span class="line">    color?: <span class="built_in">string</span>                    <span class="comment">// UI 颜色</span></span><br><span class="line">    planModeRequired?: <span class="built_in">boolean</span>        <span class="comment">// 是否需要 plan 审批</span></span><br><span class="line">    joinedAt: <span class="built_in">number</span>                  <span class="comment">// 加入时间</span></span><br><span class="line">    tmuxPaneId: <span class="built_in">string</span>                <span class="comment">// 终端 pane ID</span></span><br><span class="line">    cwd: <span class="built_in">string</span>                       <span class="comment">// 工作目录</span></span><br><span class="line">    worktreePath?: <span class="built_in">string</span>             <span class="comment">// Worktree 路径</span></span><br><span class="line">    sessionId?: <span class="built_in">string</span>                <span class="comment">// 会话 ID</span></span><br><span class="line">    subscriptions: <span class="built_in">string</span>[]           <span class="comment">// 消息订阅</span></span><br><span class="line">    backendType?: <span class="string">'tmux'</span>|<span class="string">'iterm2'</span>|<span class="string">'in-process'</span></span><br><span class="line">    isActive?: <span class="built_in">boolean</span>                <span class="comment">// false=空闲, true/undefined=活跃</span></span><br><span class="line">    mode?: PermissionMode             <span class="comment">// 当前权限模式</span></span><br><span class="line">  &#125;&gt;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-邮箱系统"><a href="#5-2-邮箱系统" class="headerlink" title="5.2 邮箱系统"></a>5.2 邮箱系统</h3><p><strong>存储路径</strong>：<code>~/.claude/teams/{team_name}/inboxes/{agent_name}.json</code></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> TeammateMessage = &#123;</span><br><span class="line">  <span class="keyword">from</span>: <span class="built_in">string</span>        <span class="comment">// 发送者名称</span></span><br><span class="line">  text: <span class="built_in">string</span>        <span class="comment">// 消息内容（纯文本或 JSON）</span></span><br><span class="line">  timestamp: <span class="built_in">string</span>   <span class="comment">// ISO 时间戳</span></span><br><span class="line">  read: <span class="built_in">boolean</span>       <span class="comment">// 是否已读</span></span><br><span class="line">  color?: <span class="built_in">string</span>      <span class="comment">// 发送者颜色</span></span><br><span class="line">  summary?: <span class="built_in">string</span>    <span class="comment">// 5-10 词摘要</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>并发安全</strong>：使用 <code>proper-lockfile</code> 文件锁，10 次重试，5-100ms 指数退避。</p><h3 id="5-3-收件箱轮询"><a href="#5-3-收件箱轮询" class="headerlink" title="5.3 收件箱轮询"></a>5.3 收件箱轮询</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/hooks/useInboxPoller.ts</span></span><br><span class="line"><span class="comment">// 轮询间隔：1000ms</span></span><br><span class="line"></span><br><span class="line">useEffect(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> interval = setInterval(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> messages = <span class="keyword">await</span> readUnreadMessages(agentName, teamName)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> msg of messages) &#123;</span><br><span class="line">      <span class="keyword">if</span> (isShutdownRequest(msg.text)) &#123;</span><br><span class="line">        <span class="comment">// 处理关停请求</span></span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isPlanApprovalResponse(msg.text)) &#123;</span><br><span class="line">        <span class="comment">// 处理 plan 审批</span></span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isPermissionRequest(msg.text)) &#123;</span><br><span class="line">        <span class="comment">// 路由到权限系统</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 纯文本消息 → 提交为新对话轮</span></span><br><span class="line">        onSubmitMessage(formatted)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, INBOX_POLL_INTERVAL_MS)</span><br><span class="line">&#125;, [])</span><br></pre></td></tr></table></figure><h3 id="5-4-消息路由"><a href="#5-4-消息路由" class="headerlink" title="5.4 消息路由"></a>5.4 消息路由</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">SendMessage(&#123; to, message &#125;)</span><br><span class="line">  │</span><br><span class="line">  ├─ to &#x3D;&#x3D;&#x3D; &quot;*&quot; → 广播</span><br><span class="line">  │   └─ 遍历所有队友，逐个写入 mailbox</span><br><span class="line">  │</span><br><span class="line">  ├─ agentNameRegistry.has(to) → in-process 子代理</span><br><span class="line">  │   └─ 通过 AppState pending messages 队列路由</span><br><span class="line">  │</span><br><span class="line">  ├─ teamFile.members.find(to) → 进程级队友</span><br><span class="line">  │   └─ writeToMailbox(to, message, teamName)</span><br><span class="line">  │</span><br><span class="line">  ├─ to.startsWith(&quot;bridge:&quot;) → 远程会话</span><br><span class="line">  │   └─ postInterClaudeMessage(sessionId, message)</span><br><span class="line">  │</span><br><span class="line">  └─ to.startsWith(&quot;uds:&quot;) → Unix Domain Socket</span><br><span class="line">      └─ sendToUdsSocket(socketPath, message)</span><br></pre></td></tr></table></figure><hr><h2 id="六、Worktree-隔离"><a href="#六、Worktree-隔离" class="headerlink" title="六、Worktree 隔离"></a>六、Worktree 隔离</h2><h3 id="6-1-创建流程"><a href="#6-1-创建流程" class="headerlink" title="6.1 创建流程"></a>6.1 创建流程</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/worktree.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">createAgentWorktree</span>(<span class="params">slug</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 校验 slug（防目录逃逸攻击）</span></span><br><span class="line">  validateWorktreeSlug(slug)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 创建 git worktree</span></span><br><span class="line">  git worktree add &#123;path&#125; -b &#123;branch&#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 符号链接大目录（节省磁盘）</span></span><br><span class="line">  symlink(node_modules, worktree/node_modules)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 应用 sparse-checkout（如配置）</span></span><br><span class="line">  <span class="keyword">if</span> (sparseCheckoutPaths) &#123;</span><br><span class="line">    git sparse-checkout <span class="keyword">set</span> &#123;paths&#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. 返回 WorktreeSession</span></span><br><span class="line">  <span class="keyword">return</span> &#123; worktreePath, worktreeBranch, headCommit &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-清理机制"><a href="#6-2-清理机制" class="headerlink" title="6.2 清理机制"></a>6.2 清理机制</h3><ul><li>Agent 完成后自动检测是否有改动（<code>hasWorktreeChanges()</code>）</li><li>有改动：返回 worktree 路径和分支名给用户</li><li>无改动：自动删除 worktree（<code>removeAgentWorktree()</code>）</li><li>异常退出：通过 <code>registerTeamForSessionCleanup()</code> 确保清理</li></ul><hr><h2 id="七、权限同步机制"><a href="#七、权限同步机制" class="headerlink" title="七、权限同步机制"></a>七、权限同步机制</h2><h3 id="7-1-团队级权限"><a href="#7-1-团队级权限" class="headerlink" title="7.1 团队级权限"></a>7.1 团队级权限</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> TeamAllowedPath = &#123;</span><br><span class="line">  path: <span class="built_in">string</span>        <span class="comment">// 绝对目录路径</span></span><br><span class="line">  toolName: <span class="built_in">string</span>    <span class="comment">// 适用的工具（如 "Edit", "Write"）</span></span><br><span class="line">  addedBy: <span class="built_in">string</span>     <span class="comment">// 添加者名称</span></span><br><span class="line">  addedAt: <span class="built_in">number</span>     <span class="comment">// 添加时间</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>队友启动时，自动继承团队级权限规则。</p><h3 id="7-2-Bubble-模式"><a href="#7-2-Bubble-模式" class="headerlink" title="7.2 Bubble 模式"></a>7.2 Bubble 模式</h3><p>Fork Agent 使用 <code>bubble</code> 权限模式 — 权限提示冒泡到父代理终端：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Fork Agent 需要权限</span><br><span class="line">  │</span><br><span class="line">  └─ bubble 模式 → 权限请求发送到父代理</span><br><span class="line">       │</span><br><span class="line">       └─ 父代理的 ToolUseConfirm 对话框显示</span><br><span class="line">            │</span><br><span class="line">            ├─ 用户批准 → 结果回传给 Fork Agent</span><br><span class="line">            └─ 用户拒绝 → Fork Agent 收到拒绝</span><br></pre></td></tr></table></figure><hr><h2 id="八、关键源文件索引"><a href="#八、关键源文件索引" class="headerlink" title="八、关键源文件索引"></a>八、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/tools/AgentTool/AgentTool.tsx</code></td><td>主工具实现，路由分发</td></tr><tr><td><code>src/tools/AgentTool/runAgent.ts</code></td><td>执行引擎，查询循环</td></tr><tr><td><code>src/tools/AgentTool/agentToolUtils.ts</code></td><td>工具池解析，结果终结</td></tr><tr><td><code>src/tools/AgentTool/forkSubagent.ts</code></td><td>Fork 语义，消息继承</td></tr><tr><td><code>src/tools/AgentTool/loadAgentsDir.ts</code></td><td>Agent 定义类型，解析加载</td></tr><tr><td><code>src/tools/AgentTool/builtInAgents.ts</code></td><td>内置 Agent 注册表</td></tr><tr><td><code>src/tools/shared/spawnMultiAgent.ts</code></td><td>队友生成入口</td></tr><tr><td><code>src/utils/swarm/spawnInProcess.ts</code></td><td>进程内队友生成</td></tr><tr><td><code>src/utils/swarm/teamHelpers.ts</code></td><td>团队文件读写</td></tr><tr><td><code>src/utils/teammateMailbox.ts</code></td><td>邮箱消息队列</td></tr><tr><td><code>src/utils/forkedAgent.ts</code></td><td>缓存安全参数，子代理上下文</td></tr><tr><td><code>src/utils/worktree.ts</code></td><td>Git worktree 隔离</td></tr><tr><td><code>src/tasks/LocalAgentTask/LocalAgentTask.tsx</code></td><td>本地 Agent 任务</td></tr></tbody></table></div><hr><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>Claude Code 的多 Agent 系统设计体现了几个核心原则：</p><ol><li><strong>分层编排</strong>：四种 Agent 类型，满足不同场景需求</li><li><strong>上下文隔离</strong>：子代理默认独立上下文，显式 opt-in 共享</li><li><strong>缓存优化</strong>：Fork Agent 通过字节一致前缀实现 prompt cache 共享</li><li><strong>邮箱通信</strong>：Teams 通过文件系统邮箱实现异步协作</li><li><strong>权限同步</strong>：团队级权限自动继承，Bubble 模式支持权限冒泡</li><li><strong>Worktree 隔离</strong>：安全的实验性修改环境</li></ol><p>这个设计使得 Claude Code 能够处理单 Agent 无法完成的复杂任务，同时保持系统的稳定性和可观测性。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-tool-system/">工具系统设计：从定义到执行的七步管道</a></li><li>下一篇：<a href="/claude-code-context-compression/">Context 管理：四级压缩与无限对话的秘密</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Claude Code 的多 Agent 系统可能是其最被低估的设计之一。它不是简单的”子代理调用”，而是一个完整的协作框架：四种 Agent 类型（Subagent、Fork、Teammate、Remote）、Teams 邮箱通信、权限同步、Worktree 隔离。这个设计让 Claude Code 能够处理单 Agent 无法完成的复杂任务。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Multi-Agent" scheme="https://donehub.github.io/tags/Multi-Agent/"/>
    
  </entry>
  
  <entry>
    <title>Context 管理：四级压缩与无限对话的秘密</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-context-compression/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-context-compression/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:24.210Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>“对话没有上下文限制”——这是 Claude Code 的一个核心承诺。但它真的能做到吗？答案是：通过四级压缩系统，实现”伪无限对话”。这背后的设计非常精妙：不是简单截断，而是智能地压缩和保留关键信息。</p></blockquote><a id="more"></a><h2 id="导读：上下文限制的困境"><a href="#导读：上下文限制的困境" class="headerlink" title="导读：上下文限制的困境"></a>导读：上下文限制的困境</h2><p>所有 LLM 都有上下文限制。Claude 3.5 Sonnet 是 200k tokens，但实际可用空间更小，因为：</p><ul><li>系统提示词占用 ~20k tokens</li><li>工具定义占用 ~15k tokens</li><li>每轮对话累积消息</li></ul><p>假设你进行了 50 轮对话，每轮平均 4k tokens，那就是 200k tokens —— 已经触及限制。</p><p><strong>传统解决方案</strong>：简单截断历史消息。但问题很明显：</p><ul><li>用户之前的重要信息被丢弃</li><li>Agent 可能重复问同样的问题</li><li>长期任务上下文丢失</li></ul><p><strong>Claude Code 的方案</strong>：四级渐进式压缩。</p><hr><h2 id="一、四级压缩策略概览"><a href="#一、四级压缩策略概览" class="headerlink" title="一、四级压缩策略概览"></a>一、四级压缩策略概览</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    四级压缩策略                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  第 1 级：Snip 压缩                                          │</span><br><span class="line">│    对已处理的消息进行智能裁剪                                │</span><br><span class="line">│    ├─ 移除重复的文件内容                                    │</span><br><span class="line">│    ├─ 截断过长的工具输出                                    │</span><br><span class="line">│    └─ 触发时机：每轮自动                                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  第 2 级：Micro 压缩                                         │</span><br><span class="line">│    修改已缓存消息的内容                                     │</span><br><span class="line">│    ├─ 不改变缓存键                                          │</span><br><span class="line">│    └─ 触发时机：每轮自动                                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  第 3 级：上下文折叠（Context Collapse）                     │</span><br><span class="line">│    分阶段摘要历史消息                                       │</span><br><span class="line">│    ├─ 先摘要最旧的消息                                      │</span><br><span class="line">│    ├─ 保留最近的细节                                        │</span><br><span class="line">│    └─ 触发时机：上下文接近限制                              │</span><br><span class="line">│                                                             │</span><br><span class="line">│  第 4 级：Auto Compact                                       │</span><br><span class="line">│    通过 Claude 生成完整摘要                                 │</span><br><span class="line">│    ├─ 替换所有历史消息                                      │</span><br><span class="line">│    └─ 触发时机：上下文严重不足                              │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><hr><h2 id="二、第一级：Snip-压缩"><a href="#二、第一级：Snip-压缩" class="headerlink" title="二、第一级：Snip 压缩"></a>二、第一级：Snip 压缩</h2><h3 id="2-1-工作原理"><a href="#2-1-工作原理" class="headerlink" title="2.1 工作原理"></a>2.1 工作原理</h3><p>Snip 压缩对已处理的消息进行智能裁剪——移除重复的文件内容、过长的工具输出等。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/compact/snipCompact.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">snipMessages</span>(<span class="params">messages: Message[]</span>): <span class="title">Message</span>[] </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> seen = <span class="keyword">new</span> Set&lt;<span class="built_in">string</span>&gt;()</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> messages.map(<span class="function"><span class="params">msg</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (msg.type === <span class="string">'user'</span>) &#123;</span><br><span class="line">      <span class="comment">// 检测重复的文件内容</span></span><br><span class="line">      <span class="keyword">const</span> content = extractFileContent(msg)</span><br><span class="line">      <span class="keyword">if</span> (seen.has(content)) &#123;</span><br><span class="line">        <span class="comment">// 重复内容，替换为引用</span></span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          ...msg,</span><br><span class="line">          content: <span class="string">`[Duplicate file content, see earlier in conversation]`</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      seen.add(content)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (msg.type === <span class="string">'tool_result'</span>) &#123;</span><br><span class="line">      <span class="comment">// 截断过长的工具输出</span></span><br><span class="line">      <span class="keyword">if</span> (msg.content.length &gt; MAX_TOOL_RESULT_SIZE) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          ...msg,</span><br><span class="line">          content: msg.content.slice(<span class="number">0</span>, MAX_TOOL_RESULT_SIZE) + </span><br><span class="line">            <span class="string">`\n... [truncated, <span class="subst">$&#123;msg.content.length&#125;</span> total chars]`</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> msg</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-智能裁剪规则"><a href="#2-2-智能裁剪规则" class="headerlink" title="2.2 智能裁剪规则"></a>2.2 智能裁剪规则</h3><div class="table-container"><table><thead><tr><th>内容类型</th><th>裁剪策略</th></tr></thead><tbody><tr><td>重复文件内容</td><td>替换为引用标记</td></tr><tr><td>大型工具输出</td><td>保留前 4KB + 截断标记</td></tr><tr><td>Base64 图片</td><td>保留元数据，替换内容</td></tr><tr><td>长对话引用</td><td>摘要化</td></tr></tbody></table></div><hr><h2 id="三、第二级：Micro-压缩"><a href="#三、第二级：Micro-压缩" class="headerlink" title="三、第二级：Micro 压缩"></a>三、第二级：Micro 压缩</h2><h3 id="3-1-工作原理"><a href="#3-1-工作原理" class="headerlink" title="3.1 工作原理"></a>3.1 工作原理</h3><p>Micro 压缩修改已缓存消息的内容，而不改变缓存键。这是一种”原地优化”策略。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/compact/microCompact.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">microCompactMessages</span>(<span class="params">messages: Message[]</span>): <span class="title">Message</span>[] </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> messages.map(<span class="function"><span class="params">msg</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (msg.type === <span class="string">'assistant'</span>) &#123;</span><br><span class="line">      <span class="comment">// 移除多余的空白和格式</span></span><br><span class="line">      <span class="keyword">const</span> compressed = compressContent(msg.content)</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 移除重复的 tool_use 说明</span></span><br><span class="line">      <span class="keyword">const</span> deduped = deduplicateToolUses(compressed)</span><br><span class="line">      </span><br><span class="line">      <span class="keyword">return</span> &#123; ...msg, content: deduped &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> msg</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-关键特性"><a href="#3-2-关键特性" class="headerlink" title="3.2 关键特性"></a>3.2 关键特性</h3><ul><li><strong>不影响缓存</strong>：缓存键基于消息 ID 和位置，不基于内容</li><li><strong>无损压缩</strong>：保留所有语义信息</li><li><strong>增量应用</strong>：每次处理一点点，避免大变动</li></ul><hr><h2 id="四、第三级：上下文折叠（Context-Collapse）"><a href="#四、第三级：上下文折叠（Context-Collapse）" class="headerlink" title="四、第三级：上下文折叠（Context Collapse）"></a>四、第三级：上下文折叠（Context Collapse）</h2><h3 id="4-1-工作原理"><a href="#4-1-工作原理" class="headerlink" title="4.1 工作原理"></a>4.1 工作原理</h3><p>当上下文接近限制时，系统启动 Context Collapse —— 将历史消息分阶段摘要。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/contextCollapse/index.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">contextCollapse</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  messages: Message[],</span></span></span><br><span class="line"><span class="function"><span class="params">  options: CollapseOptions</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">Message</span>[]&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 识别可折叠的消息段</span></span><br><span class="line">  <span class="keyword">const</span> segments = identifyCollapsibleSegments(messages)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 按优先级排序（最旧的优先）</span></span><br><span class="line">  <span class="keyword">const</span> sortedSegments = segments.sort(<span class="function">(<span class="params">a, b</span>) =&gt;</span> </span><br><span class="line">    a.startIndex - b.startIndex</span><br><span class="line">  )</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 对每个段生成摘要</span></span><br><span class="line">  <span class="keyword">const</span> summaries: Message[] = []</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> segment of sortedSegments) &#123;</span><br><span class="line">    <span class="keyword">if</span> (shouldCollapse(segment, options)) &#123;</span><br><span class="line">      <span class="keyword">const</span> summary = <span class="keyword">await</span> generateSummary(segment.messages)</span><br><span class="line">      summaries.push(createSummaryMessage(summary, segment))</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 替换原消息段</span></span><br><span class="line">  <span class="keyword">return</span> replaceSegmentsWithSummaries(messages, summaries)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-折叠策略"><a href="#4-2-折叠策略" class="headerlink" title="4.2 折叠策略"></a>4.2 折叠策略</h3><p><strong>渐进式折叠</strong>：不是一次性摘要全部，而是<strong>渐进式折叠</strong>——先摘要最旧的消息，保留最近的细节。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">原消息序列：</span><br><span class="line">[Msg1] [Msg2] [Msg3] [Msg4] [Msg5] [Msg6] [Msg7] [Msg8] [Msg9] [Msg10]</span><br><span class="line">  ↑                                                           ↑</span><br><span class="line">  最旧                                                        最新</span><br><span class="line"></span><br><span class="line">第一次折叠（上下文 &gt; 150k）：</span><br><span class="line">[Summary1] [Msg6] [Msg7] [Msg8] [Msg9] [Msg10]</span><br><span class="line">  ↑ 摘要了 Msg1-Msg5</span><br><span class="line"></span><br><span class="line">第二次折叠（上下文 &gt; 180k）：</span><br><span class="line">[Summary1] [Summary2] [Msg8] [Msg9] [Msg10]</span><br><span class="line">                        ↑ 摘要了 Msg6-Msg7</span><br><span class="line"></span><br><span class="line">第三次折叠（上下文 &gt; 195k）：</span><br><span class="line">[Summary1] [Summary2] [Summary3] [Msg10]</span><br><span class="line">                                  ↑ 摘要了 Msg8-Msg9</span><br></pre></td></tr></table></figure><h3 id="4-3-摘要格式"><a href="#4-3-摘要格式" class="headerlink" title="4.3 摘要格式"></a>4.3 摘要格式</h3><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">## Summary of Previous Work</span></span><br><span class="line"></span><br><span class="line"><span class="section">### Tasks Completed</span></span><br><span class="line"><span class="bullet">- </span>Implemented user authentication with JWT</span><br><span class="line"><span class="bullet">- </span>Added password reset functionality</span><br><span class="line"><span class="bullet">- </span>Created user profile page</span><br><span class="line"></span><br><span class="line"><span class="section">### Files Modified</span></span><br><span class="line"><span class="bullet">- </span>src/auth/auth.service.ts: Added JWT token generation</span><br><span class="line"><span class="bullet">- </span>src/user/user.controller.ts: Added profile endpoints</span><br><span class="line"><span class="bullet">- </span>src/user/user.service.ts: Added password reset logic</span><br><span class="line"></span><br><span class="line"><span class="section">### Current State</span></span><br><span class="line"><span class="bullet">- </span>Authentication system is fully functional</span><br><span class="line"><span class="bullet">- </span>Password reset emails are being sent</span><br><span class="line"><span class="bullet">- </span>Profile page is accessible at /profile</span><br><span class="line"></span><br><span class="line"><span class="section">### Pending Items</span></span><br><span class="line"><span class="bullet">- </span>Need to add email verification</span><br><span class="line"><span class="bullet">- </span>Need to implement rate limiting</span><br></pre></td></tr></table></figure><hr><h2 id="五、第四级：Auto-Compact"><a href="#五、第四级：Auto-Compact" class="headerlink" title="五、第四级：Auto Compact"></a>五、第四级：Auto Compact</h2><h3 id="5-1-工作原理"><a href="#5-1-工作原理" class="headerlink" title="5.1 工作原理"></a>5.1 工作原理</h3><p>当所有局部优化都不够时，通过 Claude 自身生成一个完整的对话摘要，替换所有历史消息。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/compact/autoCompact.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">autoCompact</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  messages: Message[],</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">Message</span>[]&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 构建 compact 请求</span></span><br><span class="line">  <span class="keyword">const</span> compactPrompt = buildCompactPrompt(messages)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 调用 Claude 生成摘要</span></span><br><span class="line">  <span class="keyword">const</span> summary = <span class="keyword">await</span> query(&#123;</span><br><span class="line">    messages: [createUserMessage(compactPrompt)],</span><br><span class="line">    systemPrompt: COMPACT_SYSTEM_PROMPT,</span><br><span class="line">    toolUseContext: context,</span><br><span class="line">    maxTurns: <span class="number">1</span>,</span><br><span class="line">  &#125;)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 创建新的消息序列</span></span><br><span class="line">  <span class="keyword">const</span> summaryMessage = createSummaryMessage(summary)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 替换历史消息</span></span><br><span class="line">  <span class="keyword">return</span> [summaryMessage]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-触发条件"><a href="#5-2-触发条件" class="headerlink" title="5.2 触发条件"></a>5.2 触发条件</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/compact/autoCompact.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">shouldTriggerAutoCompact</span>(<span class="params">state: AutoCompactTracking</span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 检查 token 使用率</span></span><br><span class="line">  <span class="keyword">const</span> usageRatio = state.currentTokens / state.maxTokens</span><br><span class="line">  <span class="keyword">if</span> (usageRatio &lt; <span class="number">0.9</span>) <span class="keyword">return</span> <span class="literal">false</span>  <span class="comment">// 低于 90% 不触发</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 检查是否已经尝试过</span></span><br><span class="line">  <span class="keyword">if</span> (state.hasAttemptedAutoCompact) <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 检查距离上次 compact 的轮数</span></span><br><span class="line">  <span class="keyword">if</span> (state.turnsSinceLastCompact &lt; MIN_TURNS_BETWEEN_COMPACT) <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-3-Compact-系统提示词"><a href="#5-3-Compact-系统提示词" class="headerlink" title="5.3 Compact 系统提示词"></a>5.3 Compact 系统提示词</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> COMPACT_SYSTEM_PROMPT = <span class="string">`</span></span><br><span class="line"><span class="string">You are a summarization assistant. Your job is to create a concise but </span></span><br><span class="line"><span class="string">complete summary of a conversation.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Your summary should:</span></span><br><span class="line"><span class="string">1. Capture all important decisions and their reasoning</span></span><br><span class="line"><span class="string">2. List all files that were created or modified</span></span><br><span class="line"><span class="string">3. Note any pending tasks or open questions</span></span><br><span class="line"><span class="string">4. Preserve the context needed to continue the work</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Format your summary as:</span></span><br><span class="line"><span class="string">## Summary</span></span><br><span class="line"><span class="string">[Brief overview of the conversation]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">## Decisions Made</span></span><br><span class="line"><span class="string">- [Decision 1]: [Reasoning]</span></span><br><span class="line"><span class="string">- [Decision 2]: [Reasoning]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">## Files Modified</span></span><br><span class="line"><span class="string">- [File path]: [What was changed]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">## Pending Tasks</span></span><br><span class="line"><span class="string">- [Task 1]</span></span><br><span class="line"><span class="string">- [Task 2]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">## Context for Continuation</span></span><br><span class="line"><span class="string">[Any other relevant context]</span></span><br><span class="line"><span class="string">`</span></span><br></pre></td></tr></table></figure><hr><h2 id="六、上下文注入"><a href="#六、上下文注入" class="headerlink" title="六、上下文注入"></a>六、上下文注入</h2><h3 id="6-1-系统上下文"><a href="#6-1-系统上下文" class="headerlink" title="6.1 系统上下文"></a>6.1 系统上下文</h3><p>每次 API 调用前，自动注入系统上下文：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/context.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getSystemContext</span>(<span class="params"></span>): <span class="title">SystemContext</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    gitStatus: getGitStatus(),          <span class="comment">// 当前分支、最近提交、文件状态</span></span><br><span class="line">    currentDate: <span class="keyword">new</span> <span class="built_in">Date</span>().toISOString(),</span><br><span class="line">    cacheBreakerInjection: getSystemInjection(),</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-用户上下文"><a href="#6-2-用户上下文" class="headerlink" title="6.2 用户上下文"></a>6.2 用户上下文</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/context.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUserContext</span>(<span class="params"></span>): <span class="title">UserContext</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    claudeMdContent: loadClaudeMdFiles(),  <span class="comment">// 所有 CLAUDE.md 合并内容</span></span><br><span class="line">    mcpInstructions: getMcpInstructions(),  <span class="comment">// MCP 服务器指令</span></span><br><span class="line">    memoryContent: loadMemoryPrompt(),      <span class="comment">// 记忆系统内容</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-3-系统提醒"><a href="#6-3-系统提醒" class="headerlink" title="6.3 系统提醒"></a>6.3 系统提醒</h3><p>系统提醒是一种特殊的<strong>附件消息</strong>，注入到工具结果或用户消息中：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">system-reminder</span>&gt;</span></span><br><span class="line">  这里是系统级的上下文信息，与具体的工具结果无关。</span><br><span class="line"><span class="tag">&lt;/<span class="name">system-reminder</span>&gt;</span></span><br></pre></td></tr></table></figure><p>用途包括：</p><ul><li>文件读取时的安全警告</li><li>记忆系统的时效提醒</li><li>用户侧问的附带信息</li><li>Deferred 工具的可用通知</li></ul><hr><h2 id="七、Token-预算管理"><a href="#七、Token-预算管理" class="headerlink" title="七、Token 预算管理"></a>七、Token 预算管理</h2><h3 id="7-1-预算计算"><a href="#7-1-预算计算" class="headerlink" title="7.1 预算计算"></a>7.1 预算计算</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/query/tokenBudget.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">calculateTokenBudget</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  model: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  messages: Message[],</span></span></span><br><span class="line"><span class="function"><span class="params">  systemPrompt: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  tools: Tools,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">TokenBudget</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 获取模型上下文限制</span></span><br><span class="line">  <span class="keyword">const</span> contextLimit = getModelContextLimit(model)  <span class="comment">// 如 200k</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 计算固定消耗</span></span><br><span class="line">  <span class="keyword">const</span> systemPromptTokens = countTokens(systemPrompt)</span><br><span class="line">  <span class="keyword">const</span> toolsTokens = countToolsTokens(tools)</span><br><span class="line">  <span class="keyword">const</span> fixedCost = systemPromptTokens + toolsTokens</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 计算消息消耗</span></span><br><span class="line">  <span class="keyword">const</span> messagesTokens = countMessagesTokens(messages)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 计算可用预算</span></span><br><span class="line">  <span class="keyword">const</span> availableBudget = contextLimit - fixedCost - messagesTokens</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. 预留输出空间</span></span><br><span class="line">  <span class="keyword">const</span> outputReserve = <span class="number">8192</span>  <span class="comment">// 默认输出限制</span></span><br><span class="line">  <span class="keyword">const</span> finalBudget = availableBudget - outputReserve</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    total: contextLimit,</span><br><span class="line">    fixed: fixedCost,</span><br><span class="line">    messages: messagesTokens,</span><br><span class="line">    available: finalBudget,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-2-预算警告"><a href="#7-2-预算警告" class="headerlink" title="7.2 预算警告"></a>7.2 预算警告</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/tokens.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">calculateTokenWarningState</span>(<span class="params">budget: TokenBudget</span>): <span class="title">TokenWarningState</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> usageRatio = budget.messages / budget.available</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (usageRatio &gt; <span class="number">0.95</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; level: <span class="string">'critical'</span>, message: <span class="string">'Context nearly exhausted'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (usageRatio &gt; <span class="number">0.85</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; level: <span class="string">'warning'</span>, message: <span class="string">'Context running low'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (usageRatio &gt; <span class="number">0.70</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; level: <span class="string">'info'</span>, message: <span class="string">'Context usage moderate'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123; level: <span class="string">'ok'</span>, message: <span class="string">''</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="八、恢复与压缩的关系"><a href="#八、恢复与压缩的关系" class="headerlink" title="八、恢复与压缩的关系"></a>八、恢复与压缩的关系</h2><h3 id="8-1-压缩触发的恢复"><a href="#8-1-压缩触发的恢复" class="headerlink" title="8.1 压缩触发的恢复"></a>8.1 压缩触发的恢复</h3><p>当压缩策略执行后，会记录状态：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">state = &#123;</span><br><span class="line">  ...state,</span><br><span class="line">  autoCompactTracking: &#123;</span><br><span class="line">    hasAttemptedAutoCompact: <span class="literal">true</span>,</span><br><span class="line">    turnsSinceLastCompact: <span class="number">0</span>,</span><br><span class="line">    compressedTokens: savedTokens,</span><br><span class="line">  &#125;,</span><br><span class="line">  transition: &#123; reason: <span class="string">'reactive_compact_retry'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="8-2-恢复链"><a href="#8-2-恢复链" class="headerlink" title="8.2 恢复链"></a>8.2 恢复链</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">prompt_too_long 错误</span><br><span class="line">  │</span><br><span class="line">  ├─ 尝试 Snip 压缩 → 重试</span><br><span class="line">  │   └─ 成功 → 继续</span><br><span class="line">  │</span><br><span class="line">  ├─ 尝试 Micro 压缩 → 重试</span><br><span class="line">  │   └─ 成功 → 继续</span><br><span class="line">  │</span><br><span class="line">  ├─ 尝试 Context Collapse → 重试</span><br><span class="line">  │   └─ 成功 → 继续</span><br><span class="line">  │</span><br><span class="line">  └─ 尝试 Auto Compact → 重试</span><br><span class="line">      └─ 成功 → 继续</span><br><span class="line">      └─ 失败 → 报错给用户</span><br></pre></td></tr></table></figure><hr><h2 id="九、关键源文件索引"><a href="#九、关键源文件索引" class="headerlink" title="九、关键源文件索引"></a>九、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/services/compact/autoCompact.ts</code></td><td>自动压缩触发和管理</td></tr><tr><td><code>src/services/compact/compact.ts</code></td><td>压缩实现</td></tr><tr><td><code>src/services/compact/reactiveCompact.ts</code></td><td>反应式压缩（错误触发）</td></tr><tr><td><code>src/services/contextCollapse/index.ts</code></td><td>上下文折叠实现</td></tr><tr><td><code>src/services/compact/snipCompact.ts</code></td><td>Snip 压缩</td></tr><tr><td><code>src/utils/tokens.ts</code></td><td>Token 计数和预算管理</td></tr><tr><td><code>src/context.ts</code></td><td>系统和用户上下文</td></tr><tr><td><code>src/utils/attachments.ts</code></td><td>系统提醒附件</td></tr></tbody></table></div><hr><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><p>Claude Code 的四级压缩系统是其”无限对话”承诺的技术基础：</p><ol><li><strong>Snip 压缩</strong>：智能移除重复内容，每轮自动</li><li><strong>Micro 压缩</strong>：原地优化缓存消息，不影响缓存</li><li><strong>Context Collapse</strong>：渐进式摘要，保留最近细节</li><li><strong>Auto Compact</strong>：Claude 生成的完整摘要</li></ol><p>这个设计的关键洞察是：<strong>不是简单截断，而是智能压缩</strong>。通过保留关键信息（决策、文件修改、待办事项），Agent 能够在压缩后继续有效工作。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-multi-agent/">多 Agent 编排：四种代理类型与协作机制</a></li><li>下一篇：<a href="/claude-code-system-prompt/">System Prompt 工程：动态组装与缓存优化</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;“对话没有上下文限制”——这是 Claude Code 的一个核心承诺。但它真的能做到吗？答案是：通过四级压缩系统，实现”伪无限对话”。这背后的设计非常精妙：不是简单截断，而是智能地压缩和保留关键信息。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Context Compression" scheme="https://donehub.github.io/tags/Context-Compression/"/>
    
  </entry>
  
  <entry>
    <title>权限与安全：分层模型与人机协作</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-permission-security/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-permission-security/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:36.660Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>AI Agent 执行命令、修改文件、访问网络——这些都是高风险操作。Claude Code 的权限系统设计了一个分层的决策模型：规则 → 模式 → 钩子 → 分类器 → 用户确认。每一层都可以独立中断工具调用，确保安全性。</p></blockquote><a id="more"></a><h2 id="导读：为什么权限如此重要？"><a href="#导读：为什么权限如此重要？" class="headerlink" title="导读：为什么权限如此重要？"></a>导读：为什么权限如此重要？</h2><p>想象这个场景：</p><blockquote><p>AI Agent 正在帮你重构代码，突然它执行了 <code>rm -rf node_modules</code>。</p></blockquote><p>如果没有权限检查，这个命令会直接执行，删除所有依赖。但在 Claude Code 中，这个命令会触发：</p><ol><li><strong>规则检查</strong>：匹配 deny 规则？<code>rm -rf *</code> 通常是 deny 的</li><li><strong>模式检查</strong>：当前是什么权限模式？<code>default</code> 模式需要确认</li><li><strong>钩子检查</strong>：PreToolUse 钩子是否拦截？</li><li><strong>分类器检查</strong>：安全分类器是否判定为危险？</li><li><strong>用户确认</strong>：弹出对话框，让用户决定</li></ol><p>每一层都是独立的防线，确保危险操作不会静默执行。</p><hr><h2 id="一、分层权限模型"><a href="#一、分层权限模型" class="headerlink" title="一、分层权限模型"></a>一、分层权限模型</h2><h3 id="1-1-权限决策层级"><a href="#1-1-权限决策层级" class="headerlink" title="1.1 权限决策层级"></a>1.1 权限决策层级</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│         权限规则（Rules）             │</span><br><span class="line">│  来源：userSettings, projectSettings │</span><br><span class="line">│        flagSettings, policySettings  │</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│        权限模式（Modes）              │</span><br><span class="line">│  default | plan | acceptEdits       │</span><br><span class="line">│  bypassPermissions | auto | bubble  │</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│         钩子（Hooks）                │</span><br><span class="line">│  PreToolUse 可拦截或修改            │</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│      安全分类器（Classifier）         │</span><br><span class="line">│  ML 模型评估工具调用安全性           │</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│         用户确认                     │</span><br><span class="line">│  弹出对话框，展示详细信息            │</span><br><span class="line">└─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-2-权限模式详解"><a href="#1-2-权限模式详解" class="headerlink" title="1.2 权限模式详解"></a>1.2 权限模式详解</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/types/permissions.ts</span></span><br><span class="line"><span class="keyword">type</span> PermissionMode =</span><br><span class="line">  | <span class="string">'acceptEdits'</span>      <span class="comment">// 工作目录内编辑自动允许</span></span><br><span class="line">  | <span class="string">'bypassPermissions'</span> <span class="comment">// 完全绕过权限</span></span><br><span class="line">  | <span class="string">'default'</span>          <span class="comment">// 默认模式，需要审批</span></span><br><span class="line">  | <span class="string">'dontAsk'</span>          <span class="comment">// 自动拒绝</span></span><br><span class="line">  | <span class="string">'plan'</span>             <span class="comment">// 计划模式</span></span><br><span class="line">  | <span class="string">'auto'</span>             <span class="comment">// 自动模式（AI 分类器决策）</span></span><br><span class="line">  | <span class="string">'bubble'</span>           <span class="comment">// 内部模式（权限冒泡到父代理）</span></span><br></pre></td></tr></table></figure><p><strong>模式行为表</strong>：</p><div class="table-container"><table><thead><tr><th>模式</th><th>读操作</th><th>写操作</th><th>危险操作</th></tr></thead><tbody><tr><td><code>default</code></td><td>可能询问</td><td>询问</td><td>询问</td></tr><tr><td><code>acceptEdits</code></td><td>自动</td><td>自动（工作目录内）</td><td>询问</td></tr><tr><td><code>bypassPermissions</code></td><td>自动</td><td>自动</td><td>自动</td></tr><tr><td><code>dontAsk</code></td><td>自动拒绝</td><td>自动拒绝</td><td>自动拒绝</td></tr><tr><td><code>plan</code></td><td>自动</td><td>询问</td><td>询问</td></tr><tr><td><code>auto</code></td><td>分类器决策</td><td>分类器决策</td><td>询问</td></tr><tr><td><code>bubble</code></td><td>冒泡到父</td><td>冒泡到父</td><td>冒泡到父</td></tr></tbody></table></div><hr><h2 id="二、权限决策流程"><a href="#二、权限决策流程" class="headerlink" title="二、权限决策流程"></a>二、权限决策流程</h2><h3 id="2-1-完整决策链"><a href="#2-1-完整决策链" class="headerlink" title="2.1 完整决策链"></a>2.1 完整决策链</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/permissions/permissions.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">hasPermissionsToUseToolInner</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  tool: Tool,</span></span></span><br><span class="line"><span class="function"><span class="params">  input: unknown,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">PermissionResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// ===== Phase 1: 否决阶段 =====</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1a. 检查工具级 deny 规则</span></span><br><span class="line">  <span class="keyword">const</span> denyResult = checkDenyRules(tool.name, input, context)</span><br><span class="line">  <span class="keyword">if</span> (denyResult) <span class="keyword">return</span> &#123; behavior: <span class="string">'deny'</span>, ...denyResult &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1b. 检查工具级 ask 规则（沙箱自动允许例外）</span></span><br><span class="line">  <span class="keyword">const</span> askResult = checkAskRules(tool.name, input, context)</span><br><span class="line">  <span class="keyword">if</span> (askResult &amp;&amp; !isSandboxAutoAllow(input, context)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'ask'</span>, ...askResult &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1c. 工具特定权限检查</span></span><br><span class="line">  <span class="keyword">if</span> (tool.checkPermissions) &#123;</span><br><span class="line">    <span class="keyword">const</span> toolResult = <span class="keyword">await</span> tool.checkPermissions(input, context)</span><br><span class="line">    <span class="keyword">if</span> (toolResult.behavior !== <span class="string">'passthrough'</span>) <span class="keyword">return</span> toolResult</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1d. 工具实现拒绝</span></span><br><span class="line">  <span class="keyword">const</span> implResult = checkImplementationDeny(tool, input, context)</span><br><span class="line">  <span class="keyword">if</span> (implResult) <span class="keyword">return</span> &#123; behavior: <span class="string">'deny'</span>, ...implResult &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1e. 需要用户交互（绕过模式也需审批）</span></span><br><span class="line">  <span class="keyword">if</span> (requiresUserInteraction(tool, input)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'ask'</span>, message: <span class="string">'This operation requires user interaction'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1f. 内容特定 ask 规则</span></span><br><span class="line">  <span class="keyword">const</span> contentAskResult = checkContentAskRules(tool, input, context)</span><br><span class="line">  <span class="keyword">if</span> (contentAskResult) <span class="keyword">return</span> &#123; behavior: <span class="string">'ask'</span>, ...contentAskResult &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1g. 安全检查（.git/, .claude/等路径）</span></span><br><span class="line">  <span class="keyword">const</span> safetyResult = checkPathSafety(tool, input, context)</span><br><span class="line">  <span class="keyword">if</span> (safetyResult) <span class="keyword">return</span> &#123; behavior: <span class="string">'ask'</span>, ...safetyResult &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ===== Phase 2: 允许阶段 =====</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2a. bypassPermissions 模式检查</span></span><br><span class="line">  <span class="keyword">if</span> (context.permissionContext.mode === <span class="string">'bypassPermissions'</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span>, decisionReason: &#123; <span class="keyword">type</span>: <span class="string">'mode'</span> &#125; &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2b. 工具级 allow 规则</span></span><br><span class="line">  <span class="keyword">const</span> allowResult = checkAllowRules(tool.name, input, context)</span><br><span class="line">  <span class="keyword">if</span> (allowResult) <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span>, ...allowResult &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ===== Phase 3: 默认 =====</span></span><br><span class="line">  <span class="keyword">return</span> &#123; behavior: <span class="string">'passthrough'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-决策原因追溯"><a href="#2-2-决策原因追溯" class="headerlink" title="2.2 决策原因追溯"></a>2.2 决策原因追溯</h3><p>每次权限决策都会记录原因：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> DecisionReason =</span><br><span class="line">  | &#123; <span class="keyword">type</span>: <span class="string">'rule'</span>; source: PermissionRuleSource; pattern: <span class="built_in">string</span> &#125;</span><br><span class="line">  | &#123; <span class="keyword">type</span>: <span class="string">'mode'</span>; mode: PermissionMode &#125;</span><br><span class="line">  | &#123; <span class="keyword">type</span>: <span class="string">'hook'</span>; hookName: <span class="built_in">string</span> &#125;</span><br><span class="line">  | &#123; <span class="keyword">type</span>: <span class="string">'classifier'</span>; score: <span class="built_in">number</span> &#125;</span><br><span class="line">  | &#123; <span class="keyword">type</span>: <span class="string">'user'</span>; temporary: <span class="built_in">boolean</span> &#125;</span><br></pre></td></tr></table></figure><p>这对于调试和审计至关重要。</p><hr><h2 id="三、规则匹配机制"><a href="#三、规则匹配机制" class="headerlink" title="三、规则匹配机制"></a>三、规则匹配机制</h2><h3 id="3-1-规则来源"><a href="#3-1-规则来源" class="headerlink" title="3.1 规则来源"></a>3.1 规则来源</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> PermissionRuleSource =</span><br><span class="line">  | <span class="string">'userSettings'</span>      <span class="comment">// 用户全局设置</span></span><br><span class="line">  | <span class="string">'projectSettings'</span>   <span class="comment">// 项目设置</span></span><br><span class="line">  | <span class="string">'localSettings'</span>     <span class="comment">// 本地设置</span></span><br><span class="line">  | <span class="string">'policySettings'</span>    <span class="comment">// 策略设置（只读）</span></span><br><span class="line">  | <span class="string">'flagSettings'</span>      <span class="comment">// 功能标志设置</span></span><br><span class="line">  | <span class="string">'cliArg'</span>            <span class="comment">// CLI 参数</span></span><br><span class="line">  | <span class="string">'command'</span>           <span class="comment">// 命令</span></span><br><span class="line">  | <span class="string">'session'</span>           <span class="comment">// 会话级（临时）</span></span><br></pre></td></tr></table></figure><h3 id="3-2-规则模式匹配"><a href="#3-2-规则模式匹配" class="headerlink" title="3.2 规则模式匹配"></a>3.2 规则模式匹配</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 精确匹配</span></span><br><span class="line">&#123; tool: <span class="string">'Bash'</span>, behavior: <span class="string">'deny'</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 参数模式匹配</span></span><br><span class="line">&#123; tool: <span class="string">'Bash(git *)'</span>, behavior: <span class="string">'allow'</span> &#125;   <span class="comment">// 允许所有 git 命令</span></span><br><span class="line">&#123; tool: <span class="string">'Bash(rm -rf *)'</span>, behavior: <span class="string">'deny'</span> &#125;  <span class="comment">// 禁止 rm -rf</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 通配符</span></span><br><span class="line">&#123; tool: <span class="string">'File*'</span>, behavior: <span class="string">'allow'</span> &#125;          <span class="comment">// 允许所有 File 开头的工具</span></span><br><span class="line">&#123; tool: <span class="string">'*'</span>, behavior: <span class="string">'deny'</span> &#125;               <span class="comment">// 禁止所有工具</span></span><br></pre></td></tr></table></figure><h3 id="3-3-子命令处理（Bash-工具）"><a href="#3-3-子命令处理（Bash-工具）" class="headerlink" title="3.3 子命令处理（Bash 工具）"></a>3.3 子命令处理（Bash 工具）</h3><p>对于复合命令（如 <code>cmd1 &amp;&amp; cmd2 | cmd3</code>）：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/BashTool/bashPermissions.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">checkCompoundCommand</span>(<span class="params">command: <span class="built_in">string</span>, context</span>): <span class="title">PermissionResult</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 使用 tree-sitter 解析命令</span></span><br><span class="line">  <span class="keyword">const</span> ast = parseCommand(command)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 提取所有子命令</span></span><br><span class="line">  <span class="keyword">const</span> subCommands = extractSubCommands(ast)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 每个子命令独立检查</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> sub of subCommands) &#123;</span><br><span class="line">    <span class="keyword">const</span> result = checkSubCommand(sub, context)</span><br><span class="line">    <span class="keyword">if</span> (result.behavior === <span class="string">'deny'</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; behavior: <span class="string">'deny'</span>, message: <span class="string">`Subcommand denied: <span class="subst">$&#123;sub&#125;</span>`</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 所有子命令允许则整个命令允许</span></span><br><span class="line">  <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="四、Bash-工具安全设计"><a href="#四、Bash-工具安全设计" class="headerlink" title="四、Bash 工具安全设计"></a>四、Bash 工具安全设计</h2><h3 id="4-1-安全包装器剥离"><a href="#4-1-安全包装器剥离" class="headerlink" title="4.1 安全包装器剥离"></a>4.1 安全包装器剥离</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/BashTool/bashPermissions.ts</span></span><br><span class="line"><span class="keyword">const</span> SAFE_WRAPPER_PATTERNS = [</span><br><span class="line">  /^timeout[ \t]+.../,  <span class="comment">// timeout 命令</span></span><br><span class="line">  /^time[ \t]+<span class="regexp">/,        /</span><span class="regexp">/ time 命令</span></span><br><span class="line"><span class="regexp">  /</span>^nice(?:[ \t]+...)?<span class="regexp">/, /</span><span class="regexp">/ nice 命令</span></span><br><span class="line"><span class="regexp">  /</span>^stdbuf(?:[ \t]+...)?<span class="regexp">/, /</span><span class="regexp">/ stdbuf 命令</span></span><br><span class="line"><span class="regexp">  /</span>^nohup[ \t]+<span class="regexp">/,       /</span><span class="regexp">/ nohup 命令</span></span><br><span class="line"><span class="regexp">]</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">function stripSafeWrappers(command: string): string &#123;</span></span><br><span class="line"><span class="regexp">  for (const pattern of SAFE_WRAPPER_PATTERNS) &#123;</span></span><br><span class="line"><span class="regexp">    command = command.replace(pattern, '')</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">  return command.trim()</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>为什么剥离</strong>：<code>nohup rm -rf /</code> 仍然是 <code>rm -rf /</code>，包装器不改变危险性。</p><h3 id="4-2-Tree-sitter-AST-安全解析"><a href="#4-2-Tree-sitter-AST-安全解析" class="headerlink" title="4.2 Tree-sitter AST 安全解析"></a>4.2 Tree-sitter AST 安全解析</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/BashTool/bashPermissions.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">parseCommandRaw</span>(<span class="params">command: <span class="built_in">string</span></span>): <span class="title">Promise</span>&lt;<span class="title">ParseResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> ast = <span class="keyword">await</span> treeSitterParse(command)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (containsCommandSubstitution(ast)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; kind: <span class="string">'too-complex'</span>, reason: <span class="string">'Contains command substitution'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (containsExpansion(ast)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; kind: <span class="string">'too-complex'</span>, reason: <span class="string">'Contains expansion'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> sem = checkSemantics(ast.commands)</span><br><span class="line">  <span class="keyword">if</span> (!sem.ok) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; kind: <span class="string">'dangerous'</span>, reason: sem.reason &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; kind: <span class="string">'simple'</span>, commands: ast.commands &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>检测的危险模式</strong>：</p><ul><li><code>eval</code>、<code>source</code>、<code>.</code> 命令</li><li><code>$(...)</code> 命令替换</li><li>反引号 <code>`...` </code> 命令替换</li><li><code>${...}</code> 变量扩展</li><li>zsh 特殊内置命令</li></ul><h3 id="4-3-安全环境变量"><a href="#4-3-安全环境变量" class="headerlink" title="4.3 安全环境变量"></a>4.3 安全环境变量</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> SAFE_ENV_VARS = <span class="keyword">new</span> Set([</span><br><span class="line">  <span class="string">'NODE_ENV'</span>, <span class="string">'GOOS'</span>, <span class="string">'GOARCH'</span>, <span class="string">'RUST_LOG'</span>,</span><br><span class="line">  <span class="string">'LANG'</span>, <span class="string">'TZ'</span>, <span class="string">'TERM'</span>, <span class="string">'NO_COLOR'</span>,</span><br><span class="line">  <span class="comment">// 注意：PATH, LD_PRELOAD, PYTHONPATH 等危险变量不在白名单中</span></span><br><span class="line">])</span><br></pre></td></tr></table></figure><h3 id="4-4-路径约束检查"><a href="#4-4-路径约束检查" class="headerlink" title="4.4 路径约束检查"></a>4.4 路径约束检查</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/BashTool/pathValidation.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validateOutputRedirect</span>(<span class="params">command: BashCommand, context</span>): <span class="title">ValidationResult</span> </span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> redirect of command.redirects) &#123;</span><br><span class="line">    <span class="keyword">const</span> resolvedPath = resolvePath(redirect.target)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查是否在允许的工作目录内</span></span><br><span class="line">    <span class="keyword">if</span> (!isInAllowedDirectory(resolvedPath, context)) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; ok: <span class="literal">false</span>, reason: <span class="string">`Redirect path not in allowed directory: <span class="subst">$&#123;resolvedPath&#125;</span>`</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查是否是危险路径</span></span><br><span class="line">    <span class="keyword">if</span> (isDangerousPath(resolvedPath)) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; ok: <span class="literal">false</span>, reason: <span class="string">`Redirect to dangerous path: <span class="subst">$&#123;resolvedPath&#125;</span>`</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123; ok: <span class="literal">true</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-5-cd-git-复合命令检查"><a href="#4-5-cd-git-复合命令检查" class="headerlink" title="4.5 cd + git 复合命令检查"></a>4.5 cd + git 复合命令检查</h3><p>防止裸仓库 RCE（Remote Code Execution）：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">checkCdGitCompound</span>(<span class="params">command: <span class="built_in">string</span></span>): <span class="title">ValidationResult</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> parts = parseCompoundCommand(command)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; parts.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parts[i].startsWith(<span class="string">'cd '</span>) &amp;&amp; parts[i + <span class="number">1</span>]?.startsWith(<span class="string">'git '</span>)) &#123;</span><br><span class="line">      <span class="keyword">const</span> targetDir = extractCdTarget(parts[i])</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">await</span> isBareRepo(targetDir)) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123; ok: <span class="literal">false</span>, reason: <span class="string">'Bare repo RCE risk'</span> &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123; ok: <span class="literal">true</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="五、文件操作权限控制"><a href="#五、文件操作权限控制" class="headerlink" title="五、文件操作权限控制"></a>五、文件操作权限控制</h2><h3 id="5-1-危险文件和目录"><a href="#5-1-危险文件和目录" class="headerlink" title="5.1 危险文件和目录"></a>5.1 危险文件和目录</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/permissions/filesystem.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> DANGEROUS_FILES = [</span><br><span class="line">  <span class="string">'.gitconfig'</span>, <span class="string">'.gitmodules'</span>,</span><br><span class="line">  <span class="string">'.bashrc'</span>, <span class="string">'.bash_profile'</span>, <span class="string">'.zshrc'</span>,</span><br><span class="line">  <span class="string">'.ripgreprc'</span>, <span class="string">'.mcp.json'</span>, <span class="string">'.claude.json'</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> DANGEROUS_DIRECTORIES = [</span><br><span class="line">  <span class="string">'.git'</span>, <span class="string">'.vscode'</span>, <span class="string">'.idea'</span>, <span class="string">'.claude'</span></span><br><span class="line">]</span><br></pre></td></tr></table></figure><h3 id="5-2-编辑前必须读取"><a href="#5-2-编辑前必须读取" class="headerlink" title="5.2 编辑前必须读取"></a>5.2 编辑前必须读取</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/FileEditTool/FileEditTool.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">validateInput</span>(<span class="params">input, context</span>): <span class="title">Promise</span>&lt;<span class="title">ValidationResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 必须先读取</span></span><br><span class="line">  <span class="keyword">const</span> readState = context.readFileState.get(input.file_path)</span><br><span class="line">  <span class="keyword">if</span> (!readState) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; result: <span class="literal">false</span>, message: <span class="string">'Must read file before editing'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 文件未被修改</span></span><br><span class="line">  <span class="keyword">const</span> currentMtime = (<span class="keyword">await</span> stat(input.file_path)).mtimeMs</span><br><span class="line">  <span class="keyword">if</span> (currentMtime &gt; readState.timestamp) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; result: <span class="literal">false</span>, message: <span class="string">'File was modified after reading'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; result: <span class="literal">true</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-3-自动编辑安全检查"><a href="#5-3-自动编辑安全检查" class="headerlink" title="5.3 自动编辑安全检查"></a>5.3 自动编辑安全检查</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">checkPathSafetyForAutoEdit</span>(<span class="params">path: <span class="built_in">string</span></span>): <span class="title">SafetyCheckResult</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 可疑 Windows 路径模式</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="regexp">/[&lt;&gt;:"|?*]/</span>.test(path)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; safe: <span class="literal">false</span>, message: <span class="string">'Invalid characters in path'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. ADS（Alternate Data Stream）攻击</span></span><br><span class="line">  <span class="keyword">if</span> (path.includes(<span class="string">'::'</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; safe: <span class="literal">false</span>, message: <span class="string">'ADS attack detected'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 短名称绕过</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="regexp">/~[0-9]/</span>.test(path)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; safe: <span class="literal">false</span>, message: <span class="string">'Short name bypass detected'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 长路径前缀</span></span><br><span class="line">  <span class="keyword">if</span> (path.startsWith(<span class="string">'\\\\?\\'</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; safe: <span class="literal">false</span>, message: <span class="string">'Long path prefix detected'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. Claude 配置文件</span></span><br><span class="line">  <span class="keyword">if</span> (isClaudeConfigFile(path)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; safe: <span class="literal">false</span>, message: <span class="string">'Claude config file'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; safe: <span class="literal">true</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="六、Auto-模式分类器"><a href="#六、Auto-模式分类器" class="headerlink" title="六、Auto 模式分类器"></a>六、Auto 模式分类器</h2><h3 id="6-1-工作原理"><a href="#6-1-工作原理" class="headerlink" title="6.1 工作原理"></a>6.1 工作原理</h3><p>在 <code>auto</code> 模式下，权限决策通过 AI 分类器：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/permissions/yoloClassifier.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">classifyWithAI</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  tool: Tool,</span></span></span><br><span class="line"><span class="function"><span class="params">  input: unknown,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">PermissionResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 检查 acceptEdits 快速路径</span></span><br><span class="line">  <span class="keyword">if</span> (isAcceptEditsAllowed(tool, input, context)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 检查安全工具白名单</span></span><br><span class="line">  <span class="keyword">if</span> (isSafeTool(tool)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 调用分类器 API</span></span><br><span class="line">  <span class="keyword">const</span> classifierInput = tool.toAutoClassifierInput?.(input) || <span class="built_in">JSON</span>.stringify(input)</span><br><span class="line">  <span class="keyword">const</span> score = <span class="keyword">await</span> callClassifierAPI(classifierInput)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 根据分数决策</span></span><br><span class="line">  <span class="keyword">if</span> (score &gt; <span class="number">0.8</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span>, decisionReason: &#123; <span class="keyword">type</span>: <span class="string">'classifier'</span>, score &#125; &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (score &lt; <span class="number">0.2</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'deny'</span>, decisionReason: &#123; <span class="keyword">type</span>: <span class="string">'classifier'</span>, score &#125; &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. 不确定时询问用户</span></span><br><span class="line">  <span class="keyword">return</span> &#123; behavior: <span class="string">'ask'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-拒绝计数"><a href="#6-2-拒绝计数" class="headerlink" title="6.2 拒绝计数"></a>6.2 拒绝计数</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 防止分类器陷入拒绝循环</span></span><br><span class="line"><span class="keyword">const</span> denialTracking: DenialTrackingState = &#123;</span><br><span class="line">  consecutiveDenials: <span class="number">0</span>,</span><br><span class="line">  maxConsecutiveDenials: <span class="number">3</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleDenial</span>(<span class="params"></span>): <span class="title">PermissionResult</span> </span>&#123;</span><br><span class="line">  denialTracking.consecutiveDenials++</span><br><span class="line">  <span class="keyword">if</span> (denialTracking.consecutiveDenials &gt;= denialTracking.maxConsecutiveDenials) &#123;</span><br><span class="line">    <span class="comment">// 回退到用户审批</span></span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'ask'</span>, message: <span class="string">'Too many denials, please decide'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123; behavior: <span class="string">'deny'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、钩子拦截"><a href="#七、钩子拦截" class="headerlink" title="七、钩子拦截"></a>七、钩子拦截</h2><h3 id="7-1-PreToolUse-钩子"><a href="#7-1-PreToolUse-钩子" class="headerlink" title="7.1 PreToolUse 钩子"></a>7.1 PreToolUse 钩子</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/tools/toolHooks.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">runPreToolUseHooks</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  tool: Tool,</span></span></span><br><span class="line"><span class="function"><span class="params">  input: unknown,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">HookResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> hooks = getHooksForTool(tool.name, context)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> hook of hooks) &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> executeHook(hook, &#123;</span><br><span class="line">      tool_name: tool.name,</span><br><span class="line">      tool_input: input,</span><br><span class="line">    &#125;)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">switch</span> (result.exitCode) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">        <span class="comment">// 成功，可能修改了 input</span></span><br><span class="line">        <span class="keyword">if</span> (result.stdout) &#123;</span><br><span class="line">          input = parseModifiedInput(result.stdout)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">      <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">        <span class="comment">// 阻塞，展示错误给模型</span></span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          blocked: <span class="literal">true</span>,</span><br><span class="line">          message: result.stderr,</span><br><span class="line">          modifiedInput: parseModifiedInput(result.stdout),</span><br><span class="line">        &#125;</span><br><span class="line">      <span class="keyword">default</span>:</span><br><span class="line">        <span class="comment">// 展示给用户</span></span><br><span class="line">        logHookOutput(result.stdout)</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; blocked: <span class="literal">false</span>, modifiedInput: input &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-2-钩子配置示例"><a href="#7-2-钩子配置示例" class="headerlink" title="7.2 钩子配置示例"></a>7.2 钩子配置示例</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// settings.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">"hooks"</span>: &#123;</span><br><span class="line">    <span class="attr">"PreToolUse"</span>: [&#123;</span><br><span class="line">      <span class="attr">"matcher"</span>: <span class="string">"Bash(rm *)"</span>,</span><br><span class="line">      <span class="attr">"hooks"</span>: [&#123;</span><br><span class="line">        <span class="attr">"type"</span>: <span class="string">"command"</span>,</span><br><span class="line">        <span class="attr">"command"</span>: <span class="string">"echo 'Deletion blocked' &amp;&amp; exit 2"</span></span><br><span class="line">      &#125;]</span><br><span class="line">    &#125;],</span><br><span class="line">    <span class="attr">"PostToolUse"</span>: [&#123;</span><br><span class="line">      <span class="attr">"matcher"</span>: <span class="string">"Edit"</span>,</span><br><span class="line">      <span class="attr">"hooks"</span>: [&#123;</span><br><span class="line">        <span class="attr">"type"</span>: <span class="string">"command"</span>,</span><br><span class="line">        <span class="attr">"command"</span>: <span class="string">"npm run lint"</span></span><br><span class="line">      &#125;]</span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="八、关键源文件索引"><a href="#八、关键源文件索引" class="headerlink" title="八、关键源文件索引"></a>八、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/types/permissions.ts</code></td><td>权限类型定义</td></tr><tr><td><code>src/utils/permissions/permissions.ts</code></td><td>权限检查核心逻辑</td></tr><tr><td><code>src/utils/permissions/filesystem.ts</code></td><td>文件系统权限控制</td></tr><tr><td><code>src/utils/permissions/shellRuleMatching.ts</code></td><td>Shell 规则匹配</td></tr><tr><td><code>src/utils/permissions/bashClassifier.ts</code></td><td>Bash 分类器</td></tr><tr><td><code>src/utils/permissions/yoloClassifier.ts</code></td><td>Auto 模式分类器</td></tr><tr><td><code>src/tools/BashTool/bashPermissions.ts</code></td><td>Bash 权限检查</td></tr><tr><td><code>src/tools/BashTool/pathValidation.ts</code></td><td>Bash 路径验证</td></tr><tr><td><code>src/tools/FileEditTool/FileEditTool.ts</code></td><td>文件编辑工具实现</td></tr><tr><td><code>src/services/tools/toolHooks.ts</code></td><td>工具钩子</td></tr></tbody></table></div><hr><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>Claude Code 的权限系统体现了几个核心设计原则：</p><ol><li><strong>分层决策</strong>：规则 → 模式 → 钩子 → 分类器 → 用户确认</li><li><strong>Deny 优先</strong>：任何 deny 匹配立即拒绝</li><li><strong>AST 级安全</strong>：使用 tree-sitter 解析 Bash 命令，检测注入</li><li><strong>路径约束</strong>：严格限制文件操作在工作目录内</li><li><strong>编辑前读取</strong>：防止盲目覆盖，检测并发修改</li><li><strong>可追溯性</strong>：每个决策都有原因记录</li></ol><p>这个设计确保了 AI Agent 的安全性，同时保持了良好的用户体验。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-skills-system/">Skills 系统：条件激活与动态发现</a></li><li>下一篇：<a href="/claude-code-memory-system/">Memory 系统：跨会话持久化知识库</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;AI Agent 执行命令、修改文件、访问网络——这些都是高风险操作。Claude Code 的权限系统设计了一个分层的决策模型：规则 → 模式 → 钩子 → 分类器 → 用户确认。每一层都可以独立中断工具调用，确保安全性。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Permission" scheme="https://donehub.github.io/tags/Permission/"/>
    
  </entry>
  
  <entry>
    <title>Skills 系统：条件激活与动态发现</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-skills-system/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-skills-system/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:35.634Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Skills 是 Claude Code 最强大的扩展机制之一。它不是简单的”命令别名”，而是完整的 AI 行为定义：可以限制工具池、覆盖模型、注入 Hook、选择执行上下文（inline 或 fork）。更令人惊叹的是，Skills 支持条件激活——只有当你操作特定文件时才被发现。</p></blockquote><a id="more"></a><h2 id="导读：Skills-不只是命令"><a href="#导读：Skills-不只是命令" class="headerlink" title="导读：Skills 不只是命令"></a>导读：Skills 不只是命令</h2><p>很多人第一次看到 Skills 时，会认为它就是”斜杠命令”的别名。但实际上，Skills 是一个<strong>完整的 AI 行为定义系统</strong>：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">name: code-review</span><br><span class="line">description: Expert code review with best practices</span><br><span class="line">allowed-tools: ['Read', 'Grep', 'WebSearch']</span><br><span class="line">model: sonnet</span><br><span class="line">context: fork</span><br><span class="line">hooks:</span><br><span class="line">  PreToolUse:</span><br><span class="line"><span class="bullet">    - </span>matcher: "Bash"</span><br><span class="line"><span class="code">      hooks:</span></span><br><span class="line"><span class="bullet">        - </span>type: command</span><br><span class="line"><span class="code">          command: "echo 'Review mode: read-only'"</span></span><br><span class="line">---</span><br><span class="line"></span><br><span class="line">You are a code reviewer. Analyze the code for:</span><br><span class="line"><span class="bullet">- </span>Security vulnerabilities</span><br><span class="line"><span class="bullet">- </span>Performance issues</span><br><span class="line"><span class="bullet">- </span>Code style and best practices</span><br><span class="line"><span class="bullet">- </span>Documentation completeness</span><br><span class="line"></span><br><span class="line">Provide actionable feedback with specific line references.</span><br></pre></td></tr></table></figure><p>这个 Skill 定义了：</p><ul><li><strong>工具限制</strong>：只读工具（Read、Grep、WebSearch）</li><li><strong>模型选择</strong>：使用 Sonnet</li><li><strong>执行上下文</strong>：Fork 模式（独立子代理）</li><li><strong>Hook 注入</strong>：阻止 Bash 调用</li></ul><hr><h2 id="一、Skills-系统架构"><a href="#一、Skills-系统架构" class="headerlink" title="一、Skills 系统架构"></a>一、Skills 系统架构</h2><h3 id="1-1-整体架构"><a href="#1-1-整体架构" class="headerlink" title="1.1 整体架构"></a>1.1 整体架构</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────┐</span><br><span class="line">│                   Skills 系统                        │</span><br><span class="line">│                                                     │</span><br><span class="line">│  ┌─────────────┐  ┌──────────────┐  ┌────────────┐ │</span><br><span class="line">│  │  Discovery   │  │   Prompt     │  │  SkillTool │ │</span><br><span class="line">│  │  发现 &amp; 加载  │→│  注入 &amp; 呈现  │→│  执行引擎   │ │</span><br><span class="line">│  └─────────────┘  └──────────────┘  └────────────┘ │</span><br><span class="line">│         ↑                                    ↓      │</span><br><span class="line">│  ┌─────────────┐                     ┌────────────┐ │</span><br><span class="line">│  │  Activation  │                     │  Context   │ │</span><br><span class="line">│  │  条件激活    │←────────────────────│  上下文修改 │ │</span><br><span class="line">│  └─────────────┘                     └────────────┘ │</span><br><span class="line">└─────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-2-核心模块职责"><a href="#1-2-核心模块职责" class="headerlink" title="1.2 核心模块职责"></a>1.2 核心模块职责</h3><div class="table-container"><table><thead><tr><th>模块</th><th>核心文件</th><th>职责</th></tr></thead><tbody><tr><td>Discovery</td><td><code>loadSkillsDir.ts</code></td><td>从 6 种来源发现和加载 Skills</td></tr><tr><td>Prompt</td><td><code>prompt.ts</code> + <code>attachments.ts</code></td><td>将 Skill 列表注入 system-reminder</td></tr><tr><td>SkillTool</td><td><code>SkillTool.ts</code></td><td>验证、权限检查、执行 Skill</td></tr><tr><td>Activation</td><td><code>loadSkillsDir.ts</code></td><td>条件激活和动态发现</td></tr><tr><td>Context</td><td><code>forkedAgent.ts</code></td><td>上下文准备和修改</td></tr></tbody></table></div><hr><h2 id="二、Skill-发现与加载"><a href="#二、Skill-发现与加载" class="headerlink" title="二、Skill 发现与加载"></a>二、Skill 发现与加载</h2><h3 id="2-1-六种来源"><a href="#2-1-六种来源" class="headerlink" title="2.1 六种来源"></a>2.1 六种来源</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────┐</span><br><span class="line">│                   Skills 来源                        │</span><br><span class="line">├─────────────────────────────────────────────────────┤</span><br><span class="line">│                                                     │</span><br><span class="line">│  1. Bundled Skills（内置）                          │</span><br><span class="line">│     └─ src&#x2F;skills&#x2F;bundled&#x2F;*.md                     │</span><br><span class="line">│                                                     │</span><br><span class="line">│  2. Built-in Plugin Skills（内置插件）              │</span><br><span class="line">│     └─ src&#x2F;plugins&#x2F;bundled&#x2F;*&#x2F;skills&#x2F;*.md           │</span><br><span class="line">│                                                     │</span><br><span class="line">│  3. Managed Skills（管理）                          │</span><br><span class="line">│     └─ $&#123;MANAGED_PATH&#125;&#x2F;.claude&#x2F;skills&#x2F;             │</span><br><span class="line">│                                                     │</span><br><span class="line">│  4. User Skills（用户全局）                         │</span><br><span class="line">│     └─ ~&#x2F;.claude&#x2F;skills&#x2F;                           │</span><br><span class="line">│                                                     │</span><br><span class="line">│  5. Project Skills（项目级）                        │</span><br><span class="line">│     └─ .claude&#x2F;skills&#x2F;                             │</span><br><span class="line">│                                                     │</span><br><span class="line">│  6. Plugin Skills（插件）                           │</span><br><span class="line">│     └─ ~&#x2F;.claude&#x2F;plugins&#x2F;*&#x2F;skills&#x2F;                 │</span><br><span class="line">│                                                     │</span><br><span class="line">└─────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-2-加载优先级"><a href="#2-2-加载优先级" class="headerlink" title="2.2 加载优先级"></a>2.2 加载优先级</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/commands.ts</span></span><br><span class="line"><span class="keyword">const</span> loadAllCommands = memoize(<span class="keyword">async</span> (cwd: <span class="built_in">string</span>): <span class="built_in">Promise</span>&lt;Command[]&gt; =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> [</span><br><span class="line">    ...bundledSkills,        <span class="comment">// 1. 内置 Skills（最高优先级）</span></span><br><span class="line">    ...builtinPluginSkills,  <span class="comment">// 2. 内置插件 Skills</span></span><br><span class="line">    ...skillDirCommands,     <span class="comment">// 3. 目录 Skills（managed → user → project）</span></span><br><span class="line">    ...workflowCommands,     <span class="comment">// 4. Workflow 命令</span></span><br><span class="line">    ...pluginCommands,       <span class="comment">// 5. 插件命令</span></span><br><span class="line">    ...pluginSkills,         <span class="comment">// 6. 插件 Skills</span></span><br><span class="line">    ...COMMANDS(),           <span class="comment">// 7. 内建命令（最低优先级）</span></span><br><span class="line">  ]</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="2-3-去重机制"><a href="#2-3-去重机制" class="headerlink" title="2.3 去重机制"></a>2.3 去重机制</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/skills/loadSkillsDir.ts</span></span><br><span class="line"><span class="keyword">const</span> seenFileIds = <span class="keyword">new</span> Map&lt;<span class="built_in">string</span>, SettingSource&gt;()</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> entry of allSkillsWithPaths) &#123;</span><br><span class="line">  <span class="keyword">const</span> fileId = <span class="keyword">await</span> getFileIdentity(entry.filePath)  <span class="comment">// realpath() 解析符号链接</span></span><br><span class="line">  <span class="keyword">const</span> existingSource = seenFileIds.get(fileId)</span><br><span class="line">  <span class="keyword">if</span> (existingSource !== <span class="literal">undefined</span>) <span class="keyword">continue</span>  <span class="comment">// 跳过重复</span></span><br><span class="line">  seenFileIds.set(fileId, entry.skill.source)</span><br><span class="line">  deduplicatedSkills.push(entry.skill)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="三、Frontmatter-解析"><a href="#三、Frontmatter-解析" class="headerlink" title="三、Frontmatter 解析"></a>三、Frontmatter 解析</h2><h3 id="3-1-Frontmatter-字段"><a href="#3-1-Frontmatter-字段" class="headerlink" title="3.1 Frontmatter 字段"></a>3.1 Frontmatter 字段</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/frontmatterParser.ts</span></span><br><span class="line"><span class="keyword">type</span> FrontmatterData = &#123;</span><br><span class="line">  <span class="string">'allowed-tools'</span>?: <span class="built_in">string</span> | <span class="built_in">string</span>[] | <span class="literal">null</span></span><br><span class="line">  description?: <span class="built_in">string</span> | <span class="literal">null</span></span><br><span class="line">  <span class="string">'argument-hint'</span>?: <span class="built_in">string</span> | <span class="literal">null</span></span><br><span class="line">  when_to_use?: <span class="built_in">string</span> | <span class="literal">null</span></span><br><span class="line">  version?: <span class="built_in">string</span> | <span class="literal">null</span></span><br><span class="line">  model?: <span class="built_in">string</span> | <span class="literal">null</span>          <span class="comment">// haiku, sonnet, opus, inherit</span></span><br><span class="line">  <span class="string">'user-invocable'</span>?: <span class="built_in">string</span> | <span class="literal">null</span></span><br><span class="line">  <span class="string">'disable-model-invocation'</span>?: <span class="built_in">string</span> | <span class="literal">null</span></span><br><span class="line">  hooks?: HooksSettings | <span class="literal">null</span></span><br><span class="line">  effort?: <span class="built_in">string</span> | <span class="literal">null</span>         <span class="comment">// low, medium, high, max</span></span><br><span class="line">  context?: <span class="string">'inline'</span> | <span class="string">'fork'</span> | <span class="literal">null</span></span><br><span class="line">  agent?: <span class="built_in">string</span> | <span class="literal">null</span></span><br><span class="line">  paths?: <span class="built_in">string</span> | <span class="built_in">string</span>[] | <span class="literal">null</span>  <span class="comment">// 条件激活路径</span></span><br><span class="line">  shell?: <span class="built_in">string</span> | <span class="literal">null</span>          <span class="comment">// bash, powershell</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-解析流程"><a href="#3-2-解析流程" class="headerlink" title="3.2 解析流程"></a>3.2 解析流程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">SKILL.md 文件</span><br><span class="line">    ↓</span><br><span class="line">parseFrontmatter()              ← frontmatterParser.ts</span><br><span class="line">    ├─ 分离 YAML frontmatter 和 Markdown 内容</span><br><span class="line">    ├─ quoteProblematicValues()  ← 处理特殊字符</span><br><span class="line">    └─ parseYaml()               ← 解析 YAML</span><br><span class="line">    ↓</span><br><span class="line">parseSkillFrontmatterFields()   ← loadSkillsDir.ts</span><br><span class="line">    ├─ description 提取（frontmatter 或第一个 # 标题）</span><br><span class="line">    ├─ parseUserSpecifiedModel() ← 模型别名解析</span><br><span class="line">    ├─ parseEffortValue()        ← 力度级别解析</span><br><span class="line">    ├─ parseHooksFromFrontmatter() ← Hook 配置验证</span><br><span class="line">    └─ parseSlashCommandToolsFromFrontmatter() ← 工具列表解析</span><br><span class="line">    ↓</span><br><span class="line">createSkillCommand()            ← 生成 Command 对象</span><br></pre></td></tr></table></figure><hr><h2 id="四、条件激活机制"><a href="#四、条件激活机制" class="headerlink" title="四、条件激活机制"></a>四、条件激活机制</h2><h3 id="4-1-工作原理"><a href="#4-1-工作原理" class="headerlink" title="4.1 工作原理"></a>4.1 工作原理</h3><p>带 <code>paths</code> frontmatter 的 Skills 不会立即暴露给模型：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">name: react-component-test</span><br><span class="line">description: Generate tests for React components</span><br><span class="line">paths: </span><br><span class="line"><span class="bullet">  - </span>"src/components/<span class="emphasis">**/*</span>.tsx"</span><br><span class="line"><span class="bullet">  - </span>"src/components/<span class="emphasis">**/*</span>.jsx"</span><br><span class="line">allowed-tools: ['Read', 'Write', 'Bash']</span><br><span class="line">model: sonnet</span><br><span class="line">---</span><br><span class="line"></span><br><span class="line">Generate comprehensive tests for this React component...</span><br></pre></td></tr></table></figure><p>这个 Skill 只有在你操作 <code>src/components/</code> 下的文件时才会被发现。</p><h3 id="4-2-激活流程"><a href="#4-2-激活流程" class="headerlink" title="4.2 激活流程"></a>4.2 激活流程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">启动时</span><br><span class="line">├─ 加载所有 Skills</span><br><span class="line">├─ 有 paths 的 → conditionalSkills Map</span><br><span class="line">└─ 无 paths 的 → 立即可用</span><br><span class="line"></span><br><span class="line">运行时（文件操作触发）</span><br><span class="line">├─ activateConditionalSkillsForPaths(filePaths, cwd)</span><br><span class="line">│  ├─ 遍历 conditionalSkills Map</span><br><span class="line">│  ├─ 用 ignore 库匹配 paths 模式</span><br><span class="line">│  │  └─ filePath 转为 cwd 相对路径后匹配</span><br><span class="line">│  ├─ 匹配成功:</span><br><span class="line">│  │  ├─ 移入 dynamicSkills Map</span><br><span class="line">│  │  ├─ 从 conditionalSkills 删除</span><br><span class="line">│  │  └─ 记录遥测 tengu_dynamic_skills_changed</span><br><span class="line">│  └─ 一旦激活，会话内持续有效</span><br><span class="line">└─ 通知缓存失效 → skillsLoaded.emit()</span><br></pre></td></tr></table></figure><h3 id="4-3-动态发现"><a href="#4-3-动态发现" class="headerlink" title="4.3 动态发现"></a>4.3 动态发现</h3><p>当操作深层目录文件时，系统自动发现新的 Skills：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/skills/loadSkillsDir.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">discoverSkillDirsForPaths</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  filePaths: <span class="built_in">string</span>[],</span></span></span><br><span class="line"><span class="function"><span class="params">  cwd: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">string</span>[]&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> filePath of filePaths) &#123;</span><br><span class="line">    <span class="keyword">let</span> currentDir = dirname(filePath)</span><br><span class="line">    <span class="comment">// 从文件所在目录向上遍历到 cwd</span></span><br><span class="line">    <span class="keyword">while</span> (currentDir.startsWith(resolvedCwd + pathSep)) &#123;</span><br><span class="line">      <span class="keyword">const</span> skillDir = join(currentDir, <span class="string">'.claude'</span>, <span class="string">'skills'</span>)</span><br><span class="line">      <span class="keyword">if</span> (!dynamicSkillDirs.has(skillDir)) &#123;</span><br><span class="line">        dynamicSkillDirs.add(skillDir)</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">await</span> exists(skillDir) &amp;&amp; !<span class="keyword">await</span> isGitignored(currentDir)) &#123;</span><br><span class="line">          newDirs.push(skillDir)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      currentDir = dirname(currentDir)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 按深度排序（最深优先）</span></span><br><span class="line">  <span class="keyword">return</span> newDirs.sort(<span class="function">(<span class="params">a, b</span>) =&gt;</span> b.split(pathSep).length - a.split(pathSep).length)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="五、Skill-注入到对话"><a href="#五、Skill-注入到对话" class="headerlink" title="五、Skill 注入到对话"></a>五、Skill 注入到对话</h2><h3 id="5-1-注入流程"><a href="#5-1-注入流程" class="headerlink" title="5.1 注入流程"></a>5.1 注入流程</h3><p>Skills 通过 <code>system-reminder</code> 消息注入到对话中：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/attachments.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">getSkillListingAttachments</span>(<span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">AttachmentMessage</span>[]&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> commands = <span class="keyword">await</span> getSkillToolCommands(cwd)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 预算控制</span></span><br><span class="line">  <span class="keyword">const</span> budget = contextWindowTokens * <span class="number">4</span> * <span class="number">0.01</span>  <span class="comment">// 1% 上下文</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 格式化</span></span><br><span class="line">  <span class="keyword">const</span> formatted = formatCommandsWithinBudget(commands, budget)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> [&#123;</span><br><span class="line">    <span class="keyword">type</span>: <span class="string">'skill_listing'</span>,</span><br><span class="line">    content: formatted,</span><br><span class="line">    skillCount: commands.length,</span><br><span class="line">  &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-预算控制"><a href="#5-2-预算控制" class="headerlink" title="5.2 预算控制"></a>5.2 预算控制</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/SkillTool/prompt.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> SKILL_BUDGET_CONTEXT_PERCENT = <span class="number">0.01</span>  <span class="comment">// 上下文窗口的 1%</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> DEFAULT_CHAR_BUDGET = <span class="number">8</span>_000           <span class="comment">// 兜底预算</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> MAX_LISTING_DESC_CHARS = <span class="number">250</span>          <span class="comment">// 每条描述上限</span></span><br></pre></td></tr></table></figure><p><strong>截断策略</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">formatCommandsWithinBudget(commands, budget)</span><br><span class="line">├─ 计算总预算</span><br><span class="line">├─ 尝试全量描述</span><br><span class="line">│  └─ 总字符 ≤ 预算 → 全部输出</span><br><span class="line">│</span><br><span class="line">├─ 分区: Bundled（不截断） + 其余</span><br><span class="line">│  ├─ Bundled Skills 始终保留完整描述</span><br><span class="line">│  └─ 其余 Skills 平分剩余预算</span><br><span class="line">│</span><br><span class="line">└─ 截断描述 → maxDescLen 字符</span><br></pre></td></tr></table></figure><hr><h2 id="六、SkillTool-执行引擎"><a href="#六、SkillTool-执行引擎" class="headerlink" title="六、SkillTool 执行引擎"></a>六、SkillTool 执行引擎</h2><h3 id="6-1-执行流程"><a href="#6-1-执行流程" class="headerlink" title="6.1 执行流程"></a>6.1 执行流程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">SkillTool.call(&#123; skill, args &#125;)</span><br><span class="line">    │</span><br><span class="line">    ├─ 1. 标准化输入（去除前导 &#x2F;）</span><br><span class="line">    ├─ 2. 远程 Skill 检查（实验性）</span><br><span class="line">    ├─ 3. 查找 Command 对象</span><br><span class="line">    ├─ 4. 记录使用频率</span><br><span class="line">    │</span><br><span class="line">    ├─ 5. 判断执行路径</span><br><span class="line">    │   ├─ command.context &#x3D;&#x3D;&#x3D; &#39;fork&#39;</span><br><span class="line">    │   │  └─ → executeForkedSkill()</span><br><span class="line">    │   │</span><br><span class="line">    │   └─ 默认 inline</span><br><span class="line">    │      ├─ processPromptSlashCommand()</span><br><span class="line">    │      │  ├─ getPromptForCommand(args, context)</span><br><span class="line">    │      │  ├─ registerSkillHooks()</span><br><span class="line">    │      │  ├─ addInvokedSkill()</span><br><span class="line">    │      │  └─ 提取附件 → 创建消息</span><br><span class="line">    │      │</span><br><span class="line">    │      ├─ 提取 metadata: allowedTools, model, effort</span><br><span class="line">    │      ├─ tagMessagesWithToolUseID()</span><br><span class="line">    │      └─ 返回 &#123; newMessages, contextModifier &#125;</span><br><span class="line">    │</span><br><span class="line">    └─ 6. contextModifier() 闭包</span><br><span class="line">       ├─ 更新 allowedTools</span><br><span class="line">       ├─ 更新 model</span><br><span class="line">       └─ 更新 effort</span><br></pre></td></tr></table></figure><h3 id="6-2-Inline-vs-Fork-扔回"><a href="#6-2-Inline-vs-Fork-扔回" class="headerlink" title="6.2 Inline vs Fork 扔回"></a>6.2 Inline vs Fork 扔回</h3><p><strong>Inline 返回</strong>:<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  data: &#123;</span><br><span class="line">    success: <span class="literal">true</span>,</span><br><span class="line">    commandName: <span class="string">'commit'</span>,</span><br><span class="line">    allowedTools: [<span class="string">'Bash'</span>, <span class="string">'Read'</span>],</span><br><span class="line">    model: <span class="string">'sonnet'</span>,</span><br><span class="line">    status: <span class="string">'inline'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  newMessages: [...],        <span class="comment">// 注入到对话</span></span><br><span class="line">  contextModifier: <span class="function">(<span class="params">ctx</span>) =&gt;</span> &#123; ... &#125;,  <span class="comment">// 修改上下文</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p><strong>Fork 返回</strong>:<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  data: &#123;</span><br><span class="line">    success: <span class="literal">true</span>,</span><br><span class="line">    commandName: <span class="string">'verify'</span>,</span><br><span class="line">    status: <span class="string">'forked'</span>,</span><br><span class="line">    agentId: <span class="string">'agent_abc123'</span>,</span><br><span class="line">    result: <span class="string">'验证通过，所有测试已运行...'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 无 newMessages — 结果嵌入 tool_result block</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><hr><h2 id="七、Hook-集成"><a href="#七、Hook-集成" class="headerlink" title="七、Hook 集成"></a>七、Hook 集成</h2><h3 id="7-1-Hook-注册"><a href="#7-1-Hook-注册" class="headerlink" title="7.1 Hook 注册"></a>7.1 Hook 注册</h3><p>Skills 可以通过 frontmatter 声明 Hook：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">name: test-runner</span><br><span class="line">hooks:</span><br><span class="line">  PostToolUse:</span><br><span class="line"><span class="bullet">    - </span>matcher: "Edit"</span><br><span class="line"><span class="code">      hooks:</span></span><br><span class="line"><span class="bullet">        - </span>type: command</span><br><span class="line"><span class="code">          command: "npm test"</span></span><br><span class="line"><span class="code">          once: true</span></span><br><span class="line">---</span><br></pre></td></tr></table></figure><p>调用时自动注册为会话级 Hook：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/hooks/registerSkillHooks.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">registerSkillHooks</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  setAppState, sessionId, hooks, skillName, skillRoot</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> eventName of HOOK_EVENTS) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> matcher of hooks[eventName] || []) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">const</span> hook of matcher.hooks) &#123;</span><br><span class="line">        <span class="comment">// once: true → 执行一次后自动移除</span></span><br><span class="line">        <span class="keyword">const</span> onHookSuccess = hook.once</span><br><span class="line">          ? <span class="function"><span class="params">()</span> =&gt;</span> removeSessionHook(setAppState, sessionId, eventName, hook)</span><br><span class="line">          : <span class="literal">undefined</span></span><br><span class="line">        </span><br><span class="line">        addSessionHook(</span><br><span class="line">          setAppState, sessionId, eventName,</span><br><span class="line">          matcher.matcher || <span class="string">''</span>,</span><br><span class="line">          hook, onHookSuccess, skillRoot,</span><br><span class="line">        )</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="八、权限系统"><a href="#八、权限系统" class="headerlink" title="八、权限系统"></a>八、权限系统</h2><h3 id="8-1-检查流程"><a href="#8-1-检查流程" class="headerlink" title="8.1 检查流程"></a>8.1 检查流程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">checkPermissions(&#123; skill, args &#125;, context)</span><br><span class="line">    │</span><br><span class="line">    ├─ 1. Deny 规则检查（最高优先级）</span><br><span class="line">    │   └─ getRuleByContentsForTool(context, SkillTool, &#39;deny&#39;)</span><br><span class="line">    │       ├─ 精确匹配: &quot;commit&quot; &#x3D;&#x3D;&#x3D; commandName</span><br><span class="line">    │       └─ 前缀匹配: &quot;review:*&quot; → commandName.startsWith(&quot;review&quot;)</span><br><span class="line">    │</span><br><span class="line">    ├─ 2. 远程 Skill 自动允许</span><br><span class="line">    │</span><br><span class="line">    ├─ 3. Allow 规则检查</span><br><span class="line">    │</span><br><span class="line">    ├─ 4. 安全属性自动允许</span><br><span class="line">    │   └─ skillHasOnlySafeProperties(command)</span><br><span class="line">    │       └─ 无 hooks、无 allowedTools、无 fork</span><br><span class="line">    │</span><br><span class="line">    └─ 5. 默认: 询问用户</span><br></pre></td></tr></table></figure><h3 id="8-2-安全属性白名单"><a href="#8-2-安全属性白名单" class="headerlink" title="8.2 安全属性白名单"></a>8.2 安全属性白名单</h3><p>如果 Skill 只包含以下属性，自动允许：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">SAFE_SKILL_PROPERTIES &#x3D; &#123;</span><br><span class="line">  type, name, description, contentLength, source,</span><br><span class="line">  loadedFrom, progressMessage, userInvocable,</span><br><span class="line">  disableModelInvocation, hasUserSpecifiedDescription,</span><br><span class="line">  getPromptForCommand, userFacingName, ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="九、关键源文件索引"><a href="#九、关键源文件索引" class="headerlink" title="九、关键源文件索引"></a>九、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/tools/SkillTool/SkillTool.ts</code></td><td>SkillTool 定义、验证、权限、执行</td></tr><tr><td><code>src/tools/SkillTool/prompt.ts</code></td><td>工具提示词、Skill 列表格式化</td></tr><tr><td><code>src/skills/loadSkillsDir.ts</code></td><td>目录 Skill 发现、加载、去重、条件激活</td></tr><tr><td><code>src/skills/bundledSkills.ts</code></td><td>内置 Skill 注册系统</td></tr><tr><td><code>src/skills/bundled/index.ts</code></td><td>内置 Skills 初始化入口</td></tr><tr><td><code>src/commands.ts</code></td><td>命令聚合、排序、过滤</td></tr><tr><td><code>src/utils/forkedAgent.ts</code></td><td>Fork 上下文准备、结果提取</td></tr><tr><td><code>src/utils/hooks/registerSkillHooks.ts</code></td><td>Skill Hook 注册</td></tr></tbody></table></div><hr><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><p>Claude Code 的 Skills 系统体现了几个核心设计原则：</p><ol><li><strong>声明式定义</strong>：通过 Frontmatter 定义 AI 行为</li><li><strong>条件激活</strong>：基于文件路径的动态发现</li><li><strong>双模式执行</strong>：Inline（注入对话）和 Fork（独立子代理）</li><li><strong>上下文修改</strong>：动态调整工具池、模型、effort</li><li><strong>Hook 集成</strong>：将 Skill 行为扩展到工具生命周期</li><li><strong>权限控制</strong>：安全属性白名单自动允许</li></ol><p>这个设计使得用户可以轻松定义新的 AI 行为，而不需要修改代码。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-system-prompt/">System Prompt 工程：动态组装与缓存优化</a></li><li>下一篇：<a href="/claude-code-permission-security/">权限与安全：分层模型与人机协作</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Skills 是 Claude Code 最强大的扩展机制之一。它不是简单的”命令别名”，而是完整的 AI 行为定义：可以限制工具池、覆盖模型、注入 Hook、选择执行上下文（inline 或 fork）。更令人惊叹的是，Skills 支持条件激活——只有当你操作特定文件时才被发现。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Skills" scheme="https://donehub.github.io/tags/Skills/"/>
    
  </entry>
  
  <entry>
    <title>Terminal UI：React + Ink 的 TUI 实现</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-terminal-ui/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-terminal-ui/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:40.760Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Claude Code 的终端界面不是传统的 CLI——它是一个完整的 React 应用，运行在终端中。通过 Ink 框架（自定义 React Reconciler + Yoga 布局引擎），Claude Code 实现了组件化 UI、双缓冲渲染、交互式对话框等高级特性。这是 Terminal UI 开发的教科书级案例。</p></blockquote><a id="more"></a><h2 id="导读：终端里的-React-应用"><a href="#导读：终端里的-React-应用" class="headerlink" title="导读：终端里的 React 应用"></a>导读：终端里的 React 应用</h2><p>当你打开 Claude Code，看到的不是普通的命令行输出：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│ Claude Code                                                 │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│ ▶ What would you like me to help you with?                 │</span><br><span class="line">│                                                             │</span><br><span class="line">│ ┌─ Tools ─────────────────────────────────────────────────┐│</span><br><span class="line">│ │ Read  Edit  Write  Bash  Grep  Glob  WebSearch          ││</span><br><span class="line">│ └─────────────────────────────────────────────────────────┘│</span><br><span class="line">│                                                             │</span><br><span class="line">│ ┌─ Context ────────────────────────────────────────────────┐│</span><br><span class="line">│ │ Memory: 3 entries loaded                                 ││</span><br><span class="line">│ │ MCP: 2 servers connected                                 ││</span><br><span class="line">│ │ Token budget: 150,000                                    ││</span><br><span class="line">│ └─────────────────────────────────────────────────────────┘│</span><br><span class="line">│                                                             │</span><br><span class="line">│ [Type your message or press Enter for suggestions]         │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>这是一个<strong>完整的 GUI 应用</strong>，运行在终端中。背后是 React + Ink 的魔法。</p><hr><h2 id="一、Ink-框架基础"><a href="#一、Ink-框架基础" class="headerlink" title="一、Ink 框架基础"></a>一、Ink 框架基础</h2><h3 id="1-1-React-Reconciler-架构"><a href="#1-1-React-Reconciler-架构" class="headerlink" title="1.1 React Reconciler 架构"></a>1.1 React Reconciler 架构</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    React + Ink 架构                          │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  React Components                                           │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  React Reconciler（自定义）                                  │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Ink Host Config                                            │</span><br><span class="line">│       ├─ createInstance() → 创建 Yoga Node                 │</span><br><span class="line">│       ├─ appendChild() → 添加子节点                         │</span><br><span class="line">│       ├─ removeChild() → 删除子节点                         │</span><br><span class="line">│       └─ commitUpdate() → 更新属性                          │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Yoga Layout Engine                                         │</span><br><span class="line">│       ├─ Flexbox 布局计算                                   │</span><br><span class="line">│       ├─ 文字测量（基于终端字符）                            │</span><br><span class="line">│       └─ 位置计算                                           │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Terminal Renderer                                          │</span><br><span class="line">│       ├─ ANSI 转义序列                                      │</span><br><span class="line">│       ├─ 双缓冲渲染                                         │</span><br><span class="line">│       └─ 输出合并                                           │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  stdout                                                     │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-2-为什么选择-Ink？"><a href="#1-2-为什么选择-Ink？" class="headerlink" title="1.2 为什么选择 Ink？"></a>1.2 为什么选择 Ink？</h3><div class="table-container"><table><thead><tr><th>优势</th><th>说明</th></tr></thead><tbody><tr><td><strong>React 生态</strong></td><td>复用 React 的组件化思想、状态管理、生命周期</td></tr><tr><td><strong>Flexbox 布局</strong></td><td>Yoga 引擎提供完整的 Flexbox 支持</td></tr><tr><td><strong>跨平台</strong></td><td>Windows/macOS/Linux 终端一致性</td></tr><tr><td><strong>双缓冲</strong></td><td>避免闪烁，平滑更新</td></tr></tbody></table></div><hr><h2 id="二、核心组件设计"><a href="#二、核心组件设计" class="headerlink" title="二、核心组件设计"></a>二、核心组件设计</h2><h3 id="2-1-组件树结构"><a href="#2-1-组件树结构" class="headerlink" title="2.1 组件树结构"></a>2.1 组件树结构</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">&lt;App&gt;</span><br><span class="line">  &lt;Header&gt;</span><br><span class="line">    &lt;Title &#x2F;&gt;</span><br><span class="line">    &lt;StatusIndicator &#x2F;&gt;</span><br><span class="line">  &lt;&#x2F;Header&gt;</span><br><span class="line">  </span><br><span class="line">  &lt;Main&gt;</span><br><span class="line">    &lt;MessageList&gt;</span><br><span class="line">      &lt;UserMessage &#x2F;&gt;</span><br><span class="line">      &lt;AssistantMessage&gt;</span><br><span class="line">        &lt;ToolCall &#x2F;&gt;</span><br><span class="line">        &lt;ToolResult &#x2F;&gt;</span><br><span class="line">      &lt;&#x2F;AssistantMessage&gt;</span><br><span class="line">    &lt;&#x2F;MessageList&gt;</span><br><span class="line">    </span><br><span class="line">    &lt;ToolBar&gt;</span><br><span class="line">      &lt;ToolButton tool&#x3D;&quot;Read&quot; &#x2F;&gt;</span><br><span class="line">      &lt;ToolButton tool&#x3D;&quot;Edit&quot; &#x2F;&gt;</span><br><span class="line">      ...</span><br><span class="line">    &lt;&#x2F;ToolBar&gt;</span><br><span class="line">    </span><br><span class="line">    &lt;ContextPanel&gt;</span><br><span class="line">      &lt;MemoryStatus &#x2F;&gt;</span><br><span class="line">      &lt;MCPStatus &#x2F;&gt;</span><br><span class="line">      &lt;TokenBudget &#x2F;&gt;</span><br><span class="line">    &lt;&#x2F;ContextPanel&gt;</span><br><span class="line">  &lt;&#x2F;Main&gt;</span><br><span class="line">  </span><br><span class="line">  &lt;Footer&gt;</span><br><span class="line">    &lt;InputBox &#x2F;&gt;</span><br><span class="line">    &lt;Suggestions &#x2F;&gt;</span><br><span class="line">  &lt;&#x2F;Footer&gt;</span><br><span class="line">&lt;&#x2F;App&gt;</span><br></pre></td></tr></table></figure><h3 id="2-2-基础组件实现"><a href="#2-2-基础组件实现" class="headerlink" title="2.2 基础组件实现"></a>2.2 基础组件实现</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; src&#x2F;components&#x2F;App.tsx</span><br><span class="line">import &#123; Box, Text, useInput, useApp &#125; from &#39;ink&#39;</span><br><span class="line"></span><br><span class="line">function App() &#123;</span><br><span class="line">  const &#123; exit &#125; &#x3D; useApp()</span><br><span class="line">  const [messages, setMessages] &#x3D; useState&lt;Message[]&gt;([])</span><br><span class="line">  const [input, setInput] &#x3D; useState(&#39;&#39;)</span><br><span class="line"></span><br><span class="line">  useInput((char, key) &#x3D;&gt; &#123;</span><br><span class="line">    if (key.escape) &#123;</span><br><span class="line">      exit()</span><br><span class="line">    &#125; else if (key.return) &#123;</span><br><span class="line">      handleSubmit(input)</span><br><span class="line">      setInput(&#39;&#39;)</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      setInput(prev &#x3D;&gt; prev + char)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  return (</span><br><span class="line">    &lt;Box flexDirection&#x3D;&quot;column&quot; height&#x3D;&quot;100%&quot;&gt;</span><br><span class="line">      &lt;Header &#x2F;&gt;</span><br><span class="line">      &lt;Box flexGrow&#x3D;&#123;1&#125;&gt;</span><br><span class="line">        &lt;MessageList messages&#x3D;&#123;messages&#125; &#x2F;&gt;</span><br><span class="line">        &lt;ContextPanel &#x2F;&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">      &lt;Footer input&#x3D;&#123;input&#125; &#x2F;&gt;</span><br><span class="line">    &lt;&#x2F;Box&gt;</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="三、布局系统"><a href="#三、布局系统" class="headerlink" title="三、布局系统"></a>三、布局系统</h2><h3 id="3-1-Yoga-Flexbox"><a href="#3-1-Yoga-Flexbox" class="headerlink" title="3.1 Yoga Flexbox"></a>3.1 Yoga Flexbox</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; Flexbox 属性完全支持</span><br><span class="line">&lt;Box </span><br><span class="line">  flexDirection&#x3D;&quot;column&quot;    &#x2F;&#x2F; 垂直布局</span><br><span class="line">  justifyContent&#x3D;&quot;center&quot;   &#x2F;&#x2F; 居中</span><br><span class="line">  alignItems&#x3D;&quot;stretch&quot;      &#x2F;&#x2F; 拉伸</span><br><span class="line">  flexGrow&#x3D;&#123;1&#125;              &#x2F;&#x2F; 占满剩余空间</span><br><span class="line">  padding&#x3D;&#123;1&#125;               &#x2F;&#x2F; 1 字符边距</span><br><span class="line">  margin&#x3D;&#123;2&#125;                &#x2F;&#x2F; 2 字符外边距</span><br><span class="line">  borderStyle&#x3D;&quot;single&quot;      &#x2F;&#x2F; 单线边框</span><br><span class="line">&gt;</span><br><span class="line">  &lt;Text&gt;Content&lt;&#x2F;Text&gt;</span><br><span class="line">&lt;&#x2F;Box&gt;</span><br></pre></td></tr></table></figure><h3 id="3-2-文字测量"><a href="#3-2-文字测量" class="headerlink" title="3.2 文字测量"></a>3.2 文字测量</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ink/lib/measureText.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">measureText</span>(<span class="params">text: <span class="built_in">string</span></span>): </span>&#123; width: <span class="built_in">number</span>; height: <span class="built_in">number</span> &#125; &#123;</span><br><span class="line">  <span class="comment">// 1. 处理 ANSI 转义序列（不计入宽度）</span></span><br><span class="line">  <span class="keyword">const</span> cleanText = stripAnsi(text)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 处理多行文本</span></span><br><span class="line">  <span class="keyword">const</span> lines = cleanText.split(<span class="string">'\n'</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 每行宽度 = 字符数（考虑宽字符）</span></span><br><span class="line">  <span class="keyword">const</span> widths = lines.map(<span class="function"><span class="params">line</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 中文字符占 2 列</span></span><br><span class="line">    <span class="keyword">return</span> line.split(<span class="string">''</span>).reduce(<span class="function">(<span class="params">width, char</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> width + (isFullWidth(char) ? <span class="number">2</span> : <span class="number">1</span>)</span><br><span class="line">    &#125;, <span class="number">0</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    width: <span class="built_in">Math</span>.max(...widths),</span><br><span class="line">    height: lines.length,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="四、双缓冲渲染"><a href="#四、双缓冲渲染" class="headerlink" title="四、双缓冲渲染"></a>四、双缓冲渲染</h2><h3 id="4-1-渲染流程"><a href="#4-1-渲染流程" class="headerlink" title="4.1 渲染流程"></a>4.1 渲染流程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    双缓冲渲染流程                            │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  State Update                                               │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Reconciler 更新 Yoga Tree                                  │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Layout 计算                                                │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Render to Buffer A                                         │</span><br><span class="line">│       ├─ 遍历 Yoga Tree                                    │</span><br><span class="line">│       ├─ 生成 ANSI 序列                                    │</span><br><span class="line">│       └─ 写入 Buffer A                                     │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Swap Buffers                                               │</span><br><span class="line">│       ├─ Buffer A → Previous Frame                         │</span><br><span class="line">│       ├─ Buffer B → Current Frame                          │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Diff &amp; Output                                              │</span><br><span class="line">│       ├─ 对比 Previous vs Current                          │</span><br><span class="line">│       ├─ 只输出变化的区域                                   │</span><br><span class="line">│       └─ ANSI 光标移动 + 更新                               │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="4-2-Diff-算法"><a href="#4-2-Diff-算法" class="headerlink" title="4.2 Diff 算法"></a>4.2 Diff 算法</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ink/lib/diff.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">diffScreens</span>(<span class="params">prev: <span class="built_in">string</span>[], curr: <span class="built_in">string</span>[]</span>): <span class="title">DiffOutput</span>[] </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> outputs: DiffOutput[] = []</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> y = <span class="number">0</span>; y &lt; <span class="built_in">Math</span>.max(prev.length, curr.length); y++) &#123;</span><br><span class="line">    <span class="keyword">const</span> prevLine = prev[y] || <span class="string">''</span></span><br><span class="line">    <span class="keyword">const</span> currLine = curr[y] || <span class="string">''</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (prevLine !== currLine) &#123;</span><br><span class="line">      <span class="comment">// 移动光标到该行</span></span><br><span class="line">      outputs.push(&#123; <span class="keyword">type</span>: <span class="string">'move'</span>, x: <span class="number">0</span>, y &#125;)</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 清除该行</span></span><br><span class="line">      outputs.push(&#123; <span class="keyword">type</span>: <span class="string">'clear_line'</span> &#125;)</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 写入新内容</span></span><br><span class="line">      outputs.push(&#123; <span class="keyword">type</span>: <span class="string">'write'</span>, content: currLine &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> outputs</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="五、交互式组件"><a href="#五、交互式组件" class="headerlink" title="五、交互式组件"></a>五、交互式组件</h2><h3 id="5-1-InputBox-实现"><a href="#5-1-InputBox-实现" class="headerlink" title="5.1 InputBox 实现"></a>5.1 InputBox 实现</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; src&#x2F;components&#x2F;InputBox.tsx</span><br><span class="line">import &#123; Box, Text, useInput &#125; from &#39;ink&#39;</span><br><span class="line">import &#123; useState &#125; from &#39;react&#39;</span><br><span class="line"></span><br><span class="line">function InputBox(&#123; onSubmit &#125;) &#123;</span><br><span class="line">  const [value, setValue] &#x3D; useState(&#39;&#39;)</span><br><span class="line">  const [cursorPosition, setCursorPosition] &#x3D; useState(0)</span><br><span class="line"></span><br><span class="line">  useInput((char, key) &#x3D;&gt; &#123;</span><br><span class="line">    if (key.leftArrow) &#123;</span><br><span class="line">      setCursorPosition(Math.max(0, cursorPosition - 1))</span><br><span class="line">    &#125; else if (key.rightArrow) &#123;</span><br><span class="line">      setCursorPosition(Math.min(value.length, cursorPosition + 1))</span><br><span class="line">    &#125; else if (key.backspace) &#123;</span><br><span class="line">      setValue(prev &#x3D;&gt; prev.slice(0, cursorPosition - 1) + prev.slice(cursorPosition))</span><br><span class="line">      setCursorPosition(Math.max(0, cursorPosition - 1))</span><br><span class="line">    &#125; else if (key.return) &#123;</span><br><span class="line">      onSubmit(value)</span><br><span class="line">      setValue(&#39;&#39;)</span><br><span class="line">      setCursorPosition(0)</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      setValue(prev &#x3D;&gt; prev.slice(0, cursorPosition) + char + prev.slice(cursorPosition))</span><br><span class="line">      setCursorPosition(cursorPosition + 1)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  return (</span><br><span class="line">    &lt;Box borderStyle&#x3D;&quot;single&quot; padding&#x3D;&#123;1&#125;&gt;</span><br><span class="line">      &lt;Text bold&gt;▶ &lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;Text&gt;&#123;value.slice(0, cursorPosition)&#125;&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;Text backgroundColor&#x3D;&quot;cyan&quot;&gt;&#123;value[cursorPosition] || &#39; &#39;&#125;&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;Text&gt;&#123;value.slice(cursorPosition + 1)&#125;&lt;&#x2F;Text&gt;</span><br><span class="line">    &lt;&#x2F;Box&gt;</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-SelectMenu-实现"><a href="#5-2-SelectMenu-实现" class="headerlink" title="5.2 SelectMenu 实现"></a>5.2 SelectMenu 实现</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; src&#x2F;components&#x2F;SelectMenu.tsx</span><br><span class="line">import &#123; Box, Text, useInput &#125; from &#39;ink&#39;</span><br><span class="line"></span><br><span class="line">function SelectMenu(&#123; items, onSelect &#125;) &#123;</span><br><span class="line">  const [selectedIndex, setSelectedIndex] &#x3D; useState(0)</span><br><span class="line"></span><br><span class="line">  useInput((char, key) &#x3D;&gt; &#123;</span><br><span class="line">    if (key.upArrow) &#123;</span><br><span class="line">      setSelectedIndex(Math.max(0, selectedIndex - 1))</span><br><span class="line">    &#125; else if (key.downArrow) &#123;</span><br><span class="line">      setSelectedIndex(Math.min(items.length - 1, selectedIndex + 1))</span><br><span class="line">    &#125; else if (key.return) &#123;</span><br><span class="line">      onSelect(items[selectedIndex])</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  return (</span><br><span class="line">    &lt;Box flexDirection&#x3D;&quot;column&quot;&gt;</span><br><span class="line">      &#123;items.map((item, index) &#x3D;&gt; (</span><br><span class="line">        &lt;Box key&#x3D;&#123;item.value&#125;&gt;</span><br><span class="line">          &lt;Text color&#x3D;&#123;index &#x3D;&#x3D;&#x3D; selectedIndex ? &#39;cyan&#39; : &#39;gray&#39;&#125;&gt;</span><br><span class="line">            &#123;index &#x3D;&#x3D;&#x3D; selectedIndex ? &#39;▶ &#39; : &#39;  &#39;&#125;</span><br><span class="line">          &lt;&#x2F;Text&gt;</span><br><span class="line">          &lt;Text bold&#x3D;&#123;index &#x3D;&#x3D;&#x3D; selectedIndex&#125;&gt;&#123;item.label&#125;&lt;&#x2F;Text&gt;</span><br><span class="line">        &lt;&#x2F;Box&gt;</span><br><span class="line">      ))&#125;</span><br><span class="line">    &lt;&#x2F;Box&gt;</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="六、工具调用可视化"><a href="#六、工具调用可视化" class="headerlink" title="六、工具调用可视化"></a>六、工具调用可视化</h2><h3 id="6-1-ToolCall-组件"><a href="#6-1-ToolCall-组件" class="headerlink" title="6.1 ToolCall 组件"></a>6.1 ToolCall 组件</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; src&#x2F;components&#x2F;ToolCall.tsx</span><br><span class="line">function ToolCall(&#123; toolName, input, status &#125;) &#123;</span><br><span class="line">  const statusColor &#x3D; &#123;</span><br><span class="line">    pending: &#39;yellow&#39;,</span><br><span class="line">    running: &#39;blue&#39;,</span><br><span class="line">    success: &#39;green&#39;,</span><br><span class="line">    error: &#39;red&#39;,</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return (</span><br><span class="line">    &lt;Box flexDirection&#x3D;&quot;column&quot; borderStyle&#x3D;&quot;single&quot; borderColor&#x3D;&#123;statusColor[status]&#125;&gt;</span><br><span class="line">      &lt;Box&gt;</span><br><span class="line">        &lt;Text bold color&#x3D;&#123;statusColor[status]&#125;&gt;</span><br><span class="line">          ⚙ &#123;toolName&#125;</span><br><span class="line">        &lt;&#x2F;Text&gt;</span><br><span class="line">        &lt;Text dimColor&gt; (&#123;status&#125;)&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">      </span><br><span class="line">      &#123;status &#x3D;&#x3D;&#x3D; &#39;running&#39; &amp;&amp; (</span><br><span class="line">        &lt;Box marginLeft&#x3D;&#123;2&#125;&gt;</span><br><span class="line">          &lt;Text dimColor&gt;Input: &#123;JSON.stringify(input).slice(0, 100)&#125;&lt;&#x2F;Text&gt;</span><br><span class="line">        &lt;&#x2F;Box&gt;</span><br><span class="line">      )&#125;</span><br><span class="line">      </span><br><span class="line">      &#123;status &#x3D;&#x3D;&#x3D; &#39;success&#39; &amp;&amp; (</span><br><span class="line">        &lt;Box marginLeft&#x3D;&#123;2&#125;&gt;</span><br><span class="line">          &lt;Text color&#x3D;&quot;green&quot;&gt;✓ Completed in &#123;duration&#125;ms&lt;&#x2F;Text&gt;</span><br><span class="line">        &lt;&#x2F;Box&gt;</span><br><span class="line">      )&#125;</span><br><span class="line">      </span><br><span class="line">      &#123;status &#x3D;&#x3D;&#x3D; &#39;error&#39; &amp;&amp; (</span><br><span class="line">        &lt;Box marginLeft&#x3D;&#123;2&#125;&gt;</span><br><span class="line">          &lt;Text color&#x3D;&quot;red&quot;&gt;✗ &#123;error.message&#125;&lt;&#x2F;Text&gt;</span><br><span class="line">        &lt;&#x2F;Box&gt;</span><br><span class="line">      )&#125;</span><br><span class="line">    &lt;&#x2F;Box&gt;</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-ToolResult-组件"><a href="#6-2-ToolResult-组件" class="headerlink" title="6.2 ToolResult 组件"></a>6.2 ToolResult 组件</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; src&#x2F;components&#x2F;ToolResult.tsx</span><br><span class="line">function ToolResult(&#123; output, truncated &#125;) &#123;</span><br><span class="line">  const [expanded, setExpanded] &#x3D; useState(false)</span><br><span class="line">  </span><br><span class="line">  const displayOutput &#x3D; expanded ? output : output.slice(0, 500)</span><br><span class="line">  </span><br><span class="line">  return (</span><br><span class="line">    &lt;Box flexDirection&#x3D;&quot;column&quot;&gt;</span><br><span class="line">      &lt;Box borderStyle&#x3D;&quot;single&quot; borderColor&#x3D;&quot;gray&quot;&gt;</span><br><span class="line">        &lt;Text dimColor&gt;Output:&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">      </span><br><span class="line">      &lt;Box padding&#x3D;&#123;1&#125;&gt;</span><br><span class="line">        &lt;Text&gt;&#123;displayOutput&#125;&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">      </span><br><span class="line">      &#123;truncated &amp;&amp; !expanded &amp;&amp; (</span><br><span class="line">        &lt;Box&gt;</span><br><span class="line">          &lt;Text dimColor&gt;... (&#123;output.length - 500&#125; more characters)&lt;&#x2F;Text&gt;</span><br><span class="line">          &lt;Text color&#x3D;&quot;cyan&quot; bold&gt; [Press Enter to expand]&lt;&#x2F;Text&gt;</span><br><span class="line">        &lt;&#x2F;Box&gt;</span><br><span class="line">      )&#125;</span><br><span class="line">    &lt;&#x2F;Box&gt;</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、权限对话框"><a href="#七、权限对话框" class="headerlink" title="七、权限对话框"></a>七、权限对话框</h2><h3 id="7-1-PermissionDialog-组件"><a href="#7-1-PermissionDialog-组件" class="headerlink" title="7.1 PermissionDialog 组件"></a>7.1 PermissionDialog 组件</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; src&#x2F;components&#x2F;PermissionDialog.tsx</span><br><span class="line">function PermissionDialog(&#123; toolName, description, onAllow, onDeny &#125;) &#123;</span><br><span class="line">  const [selected, setSelected] &#x3D; useState&lt;&#39;allow&#39; | &#39;deny&#39;&gt;(&#39;deny&#39;)</span><br><span class="line"></span><br><span class="line">  useInput((char, key) &#x3D;&gt; &#123;</span><br><span class="line">    if (key.leftArrow || key.rightArrow) &#123;</span><br><span class="line">      setSelected(prev &#x3D;&gt; prev &#x3D;&#x3D;&#x3D; &#39;allow&#39; ? &#39;deny&#39; : &#39;allow&#39;)</span><br><span class="line">    &#125; else if (key.return) &#123;</span><br><span class="line">      if (selected &#x3D;&#x3D;&#x3D; &#39;allow&#39;) &#123;</span><br><span class="line">        onAllow()</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        onDeny()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  return (</span><br><span class="line">    &lt;Box </span><br><span class="line">      flexDirection&#x3D;&quot;column&quot; </span><br><span class="line">      borderStyle&#x3D;&quot;double&quot; </span><br><span class="line">      borderColor&#x3D;&quot;yellow&quot;</span><br><span class="line">      padding&#x3D;&#123;2&#125;</span><br><span class="line">    &gt;</span><br><span class="line">      &lt;Box&gt;</span><br><span class="line">        &lt;Text bold color&#x3D;&quot;yellow&quot;&gt;⚠ Permission Required&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">      </span><br><span class="line">      &lt;Box marginTop&#x3D;&#123;1&#125;&gt;</span><br><span class="line">        &lt;Text&gt;Tool: &lt;Text bold&gt;&#123;toolName&#125;&lt;&#x2F;Text&gt;&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">      </span><br><span class="line">      &lt;Box marginTop&#x3D;&#123;1&#125;&gt;</span><br><span class="line">        &lt;Text&gt;&#123;description&#125;&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">      </span><br><span class="line">      &lt;Box marginTop&#x3D;&#123;2&#125; justifyContent&#x3D;&quot;space-around&quot;&gt;</span><br><span class="line">        &lt;Box&gt;</span><br><span class="line">          &lt;Text </span><br><span class="line">            backgroundColor&#x3D;&#123;selected &#x3D;&#x3D;&#x3D; &#39;deny&#39; ? &#39;red&#39; : undefined&#125;</span><br><span class="line">            bold&#x3D;&#123;selected &#x3D;&#x3D;&#x3D; &#39;deny&#39;&#125;</span><br><span class="line">          &gt;</span><br><span class="line">            [Deny]</span><br><span class="line">          &lt;&#x2F;Text&gt;</span><br><span class="line">        &lt;&#x2F;Box&gt;</span><br><span class="line">        &lt;Box&gt;</span><br><span class="line">          &lt;Text </span><br><span class="line">            backgroundColor&#x3D;&#123;selected &#x3D;&#x3D;&#x3D; &#39;allow&#39; ? &#39;green&#39; : undefined&#125;</span><br><span class="line">            bold&#x3D;&#123;selected &#x3D;&#x3D;&#x3D; &#39;allow&#39;&#125;</span><br><span class="line">          &gt;</span><br><span class="line">            [Allow]</span><br><span class="line">          &lt;&#x2F;Text&gt;</span><br><span class="line">        &lt;&#x2F;Box&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">      </span><br><span class="line">      &lt;Box marginTop&#x3D;&#123;1&#125;&gt;</span><br><span class="line">        &lt;Text dimColor&gt;← → to select, Enter to confirm&lt;&#x2F;Text&gt;</span><br><span class="line">      &lt;&#x2F;Box&gt;</span><br><span class="line">    &lt;&#x2F;Box&gt;</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="八、非交互模式"><a href="#八、非交互模式" class="headerlink" title="八、非交互模式"></a>八、非交互模式</h2><h3 id="8-1-模式切换"><a href="#8-1-模式切换" class="headerlink" title="8.1 模式切换"></a>8.1 模式切换</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/cli.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">detectInteractiveMode</span>(<span class="params"></span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 检查 stdout 是否是 TTY</span></span><br><span class="line">  <span class="keyword">if</span> (!process.stdout.isTTY) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 检查 CI 环境</span></span><br><span class="line">  <span class="keyword">if</span> (process.env.CI === <span class="string">'true'</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3. 检查 --non-interactive 参数</span></span><br><span class="line">  <span class="keyword">if</span> (process.argv.includes(<span class="string">'--non-interactive'</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="8-2-非交互输出"><a href="#8-2-非交互输出" class="headerlink" title="8.2 非交互输出"></a>8.2 非交互输出</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/renderers/nonInteractive.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">renderNonInteractive</span>(<span class="params">message: Message</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">switch</span> (message.type) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'user'</span>:</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`\n&gt; <span class="subst">$&#123;message.content&#125;</span>`</span>)</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">      </span><br><span class="line">    <span class="keyword">case</span> <span class="string">'assistant'</span>:</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`\n<span class="subst">$&#123;message.content&#125;</span>`</span>)</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">      </span><br><span class="line">    <span class="keyword">case</span> <span class="string">'tool_use'</span>:</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`\n[Tool: <span class="subst">$&#123;message.toolName&#125;</span>]`</span>)</span><br><span class="line">      <span class="keyword">if</span> (message.input) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`  Input: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(message.input)&#125;</span>`</span>)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">      </span><br><span class="line">    <span class="keyword">case</span> <span class="string">'tool_result'</span>:</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`  Result: <span class="subst">$&#123;message.output.slice(<span class="number">0</span>, <span class="number">500</span>)&#125;</span>`</span>)</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">      </span><br><span class="line">    <span class="keyword">case</span> <span class="string">'error'</span>:</span><br><span class="line">      <span class="built_in">console</span>.error(<span class="string">`\n[Error] <span class="subst">$&#123;message.message&#125;</span>`</span>)</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="九、颜色和样式系统"><a href="#九、颜色和样式系统" class="headerlink" title="九、颜色和样式系统"></a>九、颜色和样式系统</h2><h3 id="9-1-ANSI-颜色映射"><a href="#9-1-ANSI-颜色映射" class="headerlink" title="9.1 ANSI 颜色映射"></a>9.1 ANSI 颜色映射</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/styles/colors.ts</span></span><br><span class="line"><span class="keyword">const</span> COLORS = &#123;</span><br><span class="line">  <span class="comment">// 用户消息</span></span><br><span class="line">  user: <span class="string">'cyan'</span>,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// AI 响应</span></span><br><span class="line">  assistant: <span class="string">'white'</span>,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 工具调用</span></span><br><span class="line">  tool_pending: <span class="string">'yellow'</span>,</span><br><span class="line">  tool_running: <span class="string">'blue'</span>,</span><br><span class="line">  tool_success: <span class="string">'green'</span>,</span><br><span class="line">  tool_error: <span class="string">'red'</span>,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 状态指示</span></span><br><span class="line">  status_active: <span class="string">'green'</span>,</span><br><span class="line">  status_idle: <span class="string">'gray'</span>,</span><br><span class="line">  status_error: <span class="string">'red'</span>,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 强调</span></span><br><span class="line">  emphasis: <span class="string">'bold'</span>,</span><br><span class="line">  dim: <span class="string">'dim'</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ANSI 转义序列</span></span><br><span class="line"><span class="keyword">const</span> ANSI_COLORS = &#123;</span><br><span class="line">  cyan: <span class="string">'\x1b[36m'</span>,</span><br><span class="line">  green: <span class="string">'\x1b[32m'</span>,</span><br><span class="line">  red: <span class="string">'\x1b[31m'</span>,</span><br><span class="line">  yellow: <span class="string">'\x1b[33m'</span>,</span><br><span class="line">  blue: <span class="string">'\x1b[34m'</span>,</span><br><span class="line">  gray: <span class="string">'\x1b[90m'</span>,</span><br><span class="line">  bold: <span class="string">'\x1b[1m'</span>,</span><br><span class="line">  dim: <span class="string">'\x1b[2m'</span>,</span><br><span class="line">  reset: <span class="string">'\x1b[0m'</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="9-2-边框样式"><a href="#9-2-边框样式" class="headerlink" title="9.2 边框样式"></a>9.2 边框样式</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ink/lib/borders.ts</span></span><br><span class="line"><span class="keyword">const</span> BORDERS = &#123;</span><br><span class="line">  single: &#123;</span><br><span class="line">    topLeft: <span class="string">'┌'</span>, top: <span class="string">'─'</span>, topRight: <span class="string">'┐'</span>,</span><br><span class="line">    left: <span class="string">'│'</span>, right: <span class="string">'│'</span>,</span><br><span class="line">    bottomLeft: <span class="string">'└'</span>, bottom: <span class="string">'─'</span>, bottomRight: <span class="string">'┘'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  double: &#123;</span><br><span class="line">    topLeft: <span class="string">'╔'</span>, top: <span class="string">'═'</span>, topRight: <span class="string">'╗'</span>,</span><br><span class="line">    left: <span class="string">'║'</span>, right: <span class="string">'║'</span>,</span><br><span class="line">    bottomLeft: <span class="string">'╚'</span>, bottom: <span class="string">'═'</span>, bottomRight: <span class="string">'╝'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  rounded: &#123;</span><br><span class="line">    topLeft: <span class="string">'╭'</span>, top: <span class="string">'─'</span>, topRight: <span class="string">'╮'</span>,</span><br><span class="line">    left: <span class="string">'│'</span>, right: <span class="string">'│'</span>,</span><br><span class="line">    bottomLeft: <span class="string">'╰'</span>, bottom: <span class="string">'─'</span>, bottomRight: <span class="string">'╯'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="十、性能优化"><a href="#十、性能优化" class="headerlink" title="十、性能优化"></a>十、性能优化</h2><h3 id="10-1-渲染节流"><a href="#10-1-渲染节流" class="headerlink" title="10.1 渲染节流"></a>10.1 渲染节流</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ink/lib/renderer.ts</span></span><br><span class="line"><span class="keyword">const</span> RENDER_INTERVAL = <span class="number">16</span>  <span class="comment">// ~60fps</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">scheduleRender</span>(<span class="params">callback: () =&gt; <span class="built_in">void</span></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (renderScheduled) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  renderScheduled = <span class="literal">true</span></span><br><span class="line">  setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    callback()</span><br><span class="line">    renderScheduled = <span class="literal">false</span></span><br><span class="line">  &#125;, RENDER_INTERVAL)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="10-2-虚拟滚动"><a href="#10-2-虚拟滚动" class="headerlink" title="10.2 虚拟滚动"></a>10.2 虚拟滚动</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/components/VirtualList.tsx</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">VirtualList</span>(<span class="params">&#123; items, height &#125;</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> [scrollTop, setScrollTop] = useState(<span class="number">0</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 只渲染可见区域</span></span><br><span class="line">  <span class="keyword">const</span> visibleStart = scrollTop</span><br><span class="line">  <span class="keyword">const</span> visibleEnd = scrollTop + height</span><br><span class="line">  <span class="keyword">const</span> visibleItems = items.slice(visibleStart, visibleEnd)</span><br><span class="line"></span><br><span class="line">  useInput(<span class="function">(<span class="params">char, key</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (key.upArrow) &#123;</span><br><span class="line">      setScrollTop(<span class="built_in">Math</span>.max(<span class="number">0</span>, scrollTop - <span class="number">1</span>))</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.downArrow) &#123;</span><br><span class="line">      setScrollTop(<span class="built_in">Math</span>.min(items.length - height, scrollTop + <span class="number">1</span>))</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    &lt;Box flexDirection=<span class="string">"column"</span> height=&#123;height&#125;&gt;</span><br><span class="line">      &#123;visibleItems.map(<span class="function">(<span class="params">item, index</span>) =&gt;</span> (</span><br><span class="line">        &lt;Box key=&#123;visibleStart + index&#125;&gt;</span><br><span class="line">          &lt;Text&gt;&#123;item.content&#125;&lt;<span class="regexp">/Text&gt;</span></span><br><span class="line"><span class="regexp">        &lt;/</span>Box&gt;</span><br><span class="line">      ))&#125;</span><br><span class="line">    &lt;<span class="regexp">/Box&gt;</span></span><br><span class="line"><span class="regexp">  )</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br></pre></td></tr></table></figure><hr><h2 id="十一、关键源文件索引"><a href="#十一、关键源文件索引" class="headerlink" title="十一、关键源文件索引"></a>十一、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/components/App.tsx</code></td><td>主应用入口</td></tr><tr><td><code>src/components/Header.tsx</code></td><td>标题栏和状态指示</td></tr><tr><td><code>src/components/MessageList.tsx</code></td><td>消息列表渲染</td></tr><tr><td><code>src/components/ToolCall.tsx</code></td><td>工具调用可视化</td></tr><tr><td><code>src/components/InputBox.tsx</code></td><td>输入框组件</td></tr><tr><td><code>src/components/PermissionDialog.tsx</code></td><td>权限对话框</td></tr><tr><td><code>src/renderers/nonInteractive.ts</code></td><td>非交互模式渲染</td></tr><tr><td><code>src/styles/colors.ts</code></td><td>颜色系统</td></tr><tr><td><code>ink/lib/renderer.ts</code></td><td>双缓冲渲染引擎</td></tr><tr><td><code>ink/lib/measureText.ts</code></td><td>文字测量</td></tr></tbody></table></div><hr><h2 id="十二、总结"><a href="#十二、总结" class="headerlink" title="十二、总结"></a>十二、总结</h2><p>Claude Code 的 Terminal UI 系统体现了几个核心设计原则：</p><ol><li><strong>React 组件化</strong>：复用 React 生态，组件化 UI 设计</li><li><strong>Flexbox 布局</strong>：Yoga 引擎提供完整的 Flexbox 支持</li><li><strong>双缓冲渲染</strong>：避免闪烁，只更新变化的区域</li><li><strong>交互式组件</strong>：InputBox、SelectMenu、PermissionDialog</li><li><strong>非交互模式</strong>：自动检测环境，降级为简单输出</li><li><strong>性能优化</strong>：渲染节流、虚拟滚动</li></ol><p>这个设计让终端应用拥有了 GUI 级别的交互体验，是 Terminal UI 开发的教科书级案例。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-computer-use/">Computer Use：桌面控制的九层安全关卡</a></li><li>系列完结</li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Claude Code 的终端界面不是传统的 CLI——它是一个完整的 React 应用，运行在终端中。通过 Ink 框架（自定义 React Reconciler + Yoga 布局引擎），Claude Code 实现了组件化 UI、双缓冲渲染、交互式对话框等高级特性。这是 Terminal UI 开发的教科书级案例。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Terminal UI" scheme="https://donehub.github.io/tags/Terminal-UI/"/>
    
  </entry>
  
  <entry>
    <title>System Prompt 工程：动态组装与缓存优化</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-system-prompt/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-system-prompt/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:25.235Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Claude Code 的系统提示词不是一个静态字符串，而是一个动态组装的管道。通过分层构建、缓存边界、Section 类型等设计，实现了跨会话的缓存复用，大幅降低了延迟和成本。这是 Prompt 工程的教科书级案例。</p></blockquote><a id="more"></a><h2 id="导读：系统提示词的挑战"><a href="#导读：系统提示词的挑战" class="headerlink" title="导读：系统提示词的挑战"></a>导读：系统提示词的挑战</h2><p>系统提示词是 Agent 的”操作系统”——它定义了 Agent 的角色、规则、能力和约束。但系统提示词面临几个挑战：</p><p><strong>挑战一：长度</strong><br>Claude Code 的系统提示词约 20k tokens，每次 API 调用都要发送。</p><p><strong>挑战二：动态性</strong><br>系统提示词需要包含：</p><ul><li>当前日期</li><li>项目结构</li><li>Git 状态</li><li>MCP 服务器指令</li><li>用户自定义指令（CLAUDE.md）</li></ul><p>这些内容会变化，无法静态缓存。</p><p><strong>挑战三：优先级</strong><br>多个来源的指令需要按优先级合并：</p><ul><li>用户全局指令</li><li>项目级指令</li><li>本地私有指令</li></ul><p>Claude Code 的解决方案：<strong>分层管道 + 缓存边界 + Section 类型</strong>。</p><hr><h2 id="一、分层构建架构"><a href="#一、分层构建架构" class="headerlink" title="一、分层构建架构"></a>一、分层构建架构</h2><h3 id="1-1-提示词管道"><a href="#1-1-提示词管道" class="headerlink" title="1.1 提示词管道"></a>1.1 提示词管道</h3><p>系统提示词通过分层管道动态组装：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    静态可缓存区域                              │</span><br><span class="line">│  ┌───────────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 角色定义  │  系统规则  │  任务指导  │  工具说明  │  风格  │  │</span><br><span class="line">│  └───────────────────────────────────────────────────────┘  │</span><br><span class="line">├─────────────────────── 缓存边界 ────────────────────────────┤</span><br><span class="line">│                    动态可变区域                                │</span><br><span class="line">│  ┌───────────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 会话指引 │ 记忆系统 │ 环境信息 │ MCP 指令 │ Token 预算 │  │</span><br><span class="line">│  └───────────────────────────────────────────────────────┘  │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-2-缓存边界"><a href="#1-2-缓存边界" class="headerlink" title="1.2 缓存边界"></a>1.2 缓存边界</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/constants/prompts.ts:114-116</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> SYSTEM_PROMPT_DYNAMIC_BOUNDARY =</span><br><span class="line">  <span class="string">'__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'</span></span><br></pre></td></tr></table></figure><p><strong>缓存边界的作用</strong>：</p><ul><li><strong>边界之上</strong>：跨用户、跨组织通用的内容，使用 <code>scope: &#39;global&#39;</code> 缓存</li><li><strong>边界之下</strong>：用户/会话特定的内容，使用 <code>scope: &#39;ephemeral&#39;</code> 缓存</li></ul><p>这意味着 Claude Code 的系统提示词<strong>不需要每次都重新处理</strong>——静态部分在全球范围内共享缓存，大幅降低延迟和成本。</p><hr><h2 id="二、两种-Section-类型"><a href="#二、两种-Section-类型" class="headerlink" title="二、两种 Section 类型"></a>二、两种 Section 类型</h2><h3 id="2-1-缓存-Section"><a href="#2-1-缓存-Section" class="headerlink" title="2.1 缓存 Section"></a>2.1 缓存 Section</h3><p>计算一次，整个会话复用：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/constants/systemPromptSections.ts</span></span><br><span class="line">systemPromptSection(<span class="string">'memory'</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> buildMemoryLines()  <span class="comment">// 读取 CLAUDE.md、记忆文件等</span></span><br><span class="line">&#125;, &#123; scope: <span class="string">'ephemeral'</span> &#125;)  <span class="comment">// 会话级缓存</span></span><br></pre></td></tr></table></figure><h3 id="2-2-缓存破坏-Section"><a href="#2-2-缓存破坏-Section" class="headerlink" title="2.2 缓存破坏 Section"></a>2.2 缓存破坏 Section</h3><p>每轮重新计算：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/constants/systemPromptSections.ts</span></span><br><span class="line">DANGEROUS_uncachedSystemPromptSection(<span class="string">'mcp_instructions'</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> getMcpInstructions()  <span class="comment">// MCP 服务器可能中途连接/断开</span></span><br><span class="line">&#125;, <span class="string">'MCP servers can connect/disconnect mid-session'</span>)</span><br></pre></td></tr></table></figure><p><strong>何时使用缓存破坏 Section</strong>：</p><ul><li>MCP 指令：服务器可能动态连接/断开</li><li>当前日期：每轮都不同</li><li>Git 状态：可能快速变化</li><li>Token 预算：每轮重新计算</li></ul><hr><h2 id="三、优先级解析链"><a href="#三、优先级解析链" class="headerlink" title="三、优先级解析链"></a>三、优先级解析链</h2><h3 id="3-1-系统提示词优先级"><a href="#3-1-系统提示词优先级" class="headerlink" title="3.1 系统提示词优先级"></a>3.1 系统提示词优先级</h3><p>最终的系统提示词通过 <code>buildEffectiveSystemPrompt()</code> 按优先级决定：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Override System Prompt     ← 最高优先级，完全替换</span><br><span class="line">  ↓</span><br><span class="line">Coordinator System Prompt  ← 协调者模式专用</span><br><span class="line">  ↓</span><br><span class="line">Agent System Prompt        ← agentDefinition.getSystemPrompt()</span><br><span class="line">  ↓                          - proactive 模式：追加到默认</span><br><span class="line">  ↓                          - 其他：替换默认</span><br><span class="line">Custom System Prompt       ← --system-prompt 参数</span><br><span class="line">  ↓</span><br><span class="line">Default System Prompt      ← Claude Code 标准提示词</span><br><span class="line">  ↓</span><br><span class="line">Append System Prompt       ← 始终追加到末尾</span><br></pre></td></tr></table></figure><h3 id="3-2-代码实现"><a href="#3-2-代码实现" class="headerlink" title="3.2 代码实现"></a>3.2 代码实现</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/systemPrompt.ts:41-123</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">buildEffectiveSystemPrompt</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  options: BuildSystemPromptOptions,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">SystemPrompt</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> sections: SystemPromptSection[] = []</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1. 检查 Override</span></span><br><span class="line">  <span class="keyword">if</span> (options.overrideSystemPrompt) &#123;</span><br><span class="line">    <span class="keyword">return</span> asSystemPrompt(options.overrideSystemPrompt)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 检查 Coordinator</span></span><br><span class="line">  <span class="keyword">if</span> (isCoordinatorMode() &amp;&amp; options.coordinatorSystemPrompt) &#123;</span><br><span class="line">    <span class="keyword">return</span> asSystemPrompt(options.coordinatorSystemPrompt)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 构建 Section 序列</span></span><br><span class="line">  <span class="keyword">const</span> defaultSections = <span class="keyword">await</span> buildDefaultSections()</span><br><span class="line">  sections.push(...defaultSections)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 处理 Agent 提示词</span></span><br><span class="line">  <span class="keyword">if</span> (options.agentDefinition?.getSystemPrompt) &#123;</span><br><span class="line">    <span class="keyword">const</span> agentPrompt = <span class="keyword">await</span> options.agentDefinition.getSystemPrompt(options)</span><br><span class="line">    <span class="keyword">if</span> (options.agentDefinition.promptMode === <span class="string">'proactive'</span>) &#123;</span><br><span class="line">      <span class="comment">// 追加到默认提示词后</span></span><br><span class="line">      sections.push(&#123; <span class="keyword">type</span>: <span class="string">'text'</span>, text: agentPrompt &#125;)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 替换默认提示词</span></span><br><span class="line">      <span class="keyword">return</span> asSystemPrompt(agentPrompt)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. 追加 Custom 和 Append</span></span><br><span class="line">  <span class="keyword">if</span> (options.customSystemPrompt) &#123;</span><br><span class="line">    sections.push(&#123; <span class="keyword">type</span>: <span class="string">'text'</span>, text: options.customSystemPrompt &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (options.appendSystemPrompt) &#123;</span><br><span class="line">    sections.push(&#123; <span class="keyword">type</span>: <span class="string">'text'</span>, text: options.appendSystemPrompt &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> resolveSystemPromptSections(sections)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="四、CLAUDE-md-加载机制"><a href="#四、CLAUDE-md-加载机制" class="headerlink" title="四、CLAUDE.md 加载机制"></a>四、CLAUDE.md 加载机制</h2><h3 id="4-1-加载优先级"><a href="#4-1-加载优先级" class="headerlink" title="4.1 加载优先级"></a>4.1 加载优先级</h3><p>CLAUDE.md 是用户自定义指令系统，按优先级从低到高加载：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;etc&#x2F;claude-code&#x2F;CLAUDE.md          ← 全局管理配置（最低优先级）</span><br><span class="line">  ↓</span><br><span class="line">~&#x2F;.claude&#x2F;CLAUDE.md                 ← 用户全局指令</span><br><span class="line">  ↓</span><br><span class="line">项目根目录&#x2F;CLAUDE.md                 ← 项目级指令</span><br><span class="line">项目根目录&#x2F;.claude&#x2F;CLAUDE.md</span><br><span class="line">项目根目录&#x2F;.claude&#x2F;rules&#x2F;*.md</span><br><span class="line">  ↓</span><br><span class="line">项目根目录&#x2F;CLAUDE.local.md           ← 本地私有指令（最高优先级）</span><br></pre></td></tr></table></figure><h3 id="4-2-递归引用"><a href="#4-2-递归引用" class="headerlink" title="4.2 递归引用"></a>4.2 递归引用</h3><p>支持 <code>@path</code> 语法递归引用其他文件：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 项目配置</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 编码规范</span></span><br><span class="line">@./docs/coding-standards.md</span><br><span class="line"></span><br><span class="line"><span class="section">## API 文档</span></span><br><span class="line">@./docs/api-spec.md</span><br></pre></td></tr></table></figure><h3 id="4-3-循环引用检测"><a href="#4-3-循环引用检测" class="headerlink" title="4.3 循环引用检测"></a>4.3 循环引用检测</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/claudemd.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">loadClaudeMdFile</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  path: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  visited: Set&lt;<span class="built_in">string</span>&gt; = <span class="keyword">new</span> Set(),</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">string</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 检测循环引用</span></span><br><span class="line">  <span class="keyword">if</span> (visited.has(path)) &#123;</span><br><span class="line">    <span class="built_in">console</span>.warn(<span class="string">`Circular reference detected: <span class="subst">$&#123;path&#125;</span>`</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="string">''</span></span><br><span class="line">  &#125;</span><br><span class="line">  visited.add(path)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">let</span> content = <span class="keyword">await</span> readFile(path, <span class="string">'utf-8'</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 处理 @path 引用</span></span><br><span class="line">  <span class="keyword">const</span> references = extractReferences(content)</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> ref of references) &#123;</span><br><span class="line">    <span class="keyword">const</span> refPath = resolveReference(path, ref)</span><br><span class="line">    <span class="keyword">const</span> refContent = <span class="keyword">await</span> loadClaudeMdFile(refPath, visited)</span><br><span class="line">    content = content.replace(<span class="string">`@<span class="subst">$&#123;ref&#125;</span>`</span>, refContent)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> content</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="五、Agent-特有的提示词增强"><a href="#五、Agent-特有的提示词增强" class="headerlink" title="五、Agent 特有的提示词增强"></a>五、Agent 特有的提示词增强</h2><h3 id="5-1-环境详情注入"><a href="#5-1-环境详情注入" class="headerlink" title="5.1 环境详情注入"></a>5.1 环境详情注入</h3><p>子代理会额外注入环境详情：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/AgentTool/runAgent.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getAgentSystemPrompt</span>(<span class="params">agentDef, toolUseContext</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> prompt = agentDef.getSystemPrompt(&#123; toolUseContext &#125;)</span><br><span class="line">  prompt = enhanceSystemPromptWithEnvDetails(prompt)</span><br><span class="line">  <span class="comment">// 添加：工作目录、启用工具列表、模型信息、环境变量</span></span><br><span class="line">  <span class="keyword">return</span> prompt</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-Fork-约束注入"><a href="#5-2-Fork-约束注入" class="headerlink" title="5.2 Fork 约束注入"></a>5.2 Fork 约束注入</h3><p>Fork Agent 会注入行为约束：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> FORK_BOILERPLATE_TAG = <span class="string">`</span></span><br><span class="line"><span class="string">You are a forked worker process. Your job is to execute tasks efficiently.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">CRITICAL RULES:</span></span><br><span class="line"><span class="string">1. You are NOT the main agent. Do not engage in conversation or ask follow-up questions.</span></span><br><span class="line"><span class="string">2. Use tools directly (Bash, Read, Write, etc.) to complete your assigned task.</span></span><br><span class="line"><span class="string">3. If you modify files, commit your changes before reporting.</span></span><br><span class="line"><span class="string">4. Do NOT output text between tool calls - just use tools.</span></span><br><span class="line"><span class="string">5. Stay strictly within the scope of your directive.</span></span><br><span class="line"><span class="string">6. Keep your final report under 500 words.</span></span><br><span class="line"><span class="string">7. Your response MUST start with "Scope:" followed by what you accomplished.</span></span><br><span class="line"><span class="string">`</span></span><br></pre></td></tr></table></figure><hr><h2 id="六、缓存策略详解"><a href="#六、缓存策略详解" class="headerlink" title="六、缓存策略详解"></a>六、缓存策略详解</h2><h3 id="6-1-三级缓存"><a href="#6-1-三级缓存" class="headerlink" title="6.1 三级缓存"></a>6.1 三级缓存</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Global Cache（跨组织）    ← 静态系统提示词</span><br><span class="line">  ↓  scope: &#39;global&#39;</span><br><span class="line">Ephemeral Cache（会话级） ← 动态系统提示词</span><br><span class="line">  ↓  scope: &#39;ephemeral&#39;</span><br><span class="line">Section Cache（轮级）     ← systemPromptSection 记忆化</span><br><span class="line">     每个 Section 独立缓存</span><br></pre></td></tr></table></figure><h3 id="6-2-缓存失效"><a href="#6-2-缓存失效" class="headerlink" title="6.2 缓存失效"></a>6.2 缓存失效</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 触发缓存的场景</span></span><br><span class="line"><span class="keyword">const</span> cacheTriggers = &#123;</span><br><span class="line">  <span class="comment">// Global Cache</span></span><br><span class="line">  <span class="string">'static_system_prompt'</span>: <span class="string">'never'</span>,  <span class="comment">// 永不失效</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// Ephemeral Cache</span></span><br><span class="line">  <span class="string">'memory_content'</span>: <span class="string">'on_claudemd_change'</span>,  <span class="comment">// CLAUDE.md 变化时</span></span><br><span class="line">  <span class="string">'mcp_instructions'</span>: <span class="string">'on_mcp_connection'</span>,  <span class="comment">// MCP 连接/断开时</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// Section Cache</span></span><br><span class="line">  <span class="string">'user_context'</span>: <span class="string">'per_turn'</span>,  <span class="comment">// 每轮重新计算（但有 memoize）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-3-缓存命中优化"><a href="#6-3-缓存命中优化" class="headerlink" title="6.3 缓存命中优化"></a>6.3 缓存命中优化</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/api/claude.ts:3213-3237</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildSystemPromptBlocks</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  systemPrompt: SystemPrompt,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">ContentBlockParam</span>[] </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> blocks: ContentBlockParam[] = []</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 在缓存边界处分割</span></span><br><span class="line">  <span class="keyword">const</span> [staticPart, dynamicPart] = splitAtBoundary(systemPrompt)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 静态部分使用 global 缓存</span></span><br><span class="line">  <span class="keyword">if</span> (staticPart) &#123;</span><br><span class="line">    blocks.push(&#123;</span><br><span class="line">      <span class="keyword">type</span>: <span class="string">'text'</span>,</span><br><span class="line">      text: staticPart,</span><br><span class="line">      cache_control: &#123; <span class="keyword">type</span>: <span class="string">'ephemeral'</span> &#125;,  <span class="comment">// API 会自动识别为 global</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 动态部分使用 ephemeral 缓存</span></span><br><span class="line">  <span class="keyword">if</span> (dynamicPart) &#123;</span><br><span class="line">    blocks.push(&#123;</span><br><span class="line">      <span class="keyword">type</span>: <span class="string">'text'</span>,</span><br><span class="line">      text: dynamicPart,</span><br><span class="line">      cache_control: &#123; <span class="keyword">type</span>: <span class="string">'ephemeral'</span> &#125;,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> blocks</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、提示词结构示例"><a href="#七、提示词结构示例" class="headerlink" title="七、提示词结构示例"></a>七、提示词结构示例</h2><h3 id="7-1-完整系统提示词结构"><a href="#7-1-完整系统提示词结构" class="headerlink" title="7.1 完整系统提示词结构"></a>7.1 完整系统提示词结构</h3><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># Claude Code System Prompt</span></span><br><span class="line"></span><br><span class="line"><span class="section">## Role</span></span><br><span class="line">You are an interactive agent that helps users with software engineering tasks.</span><br><span class="line"></span><br><span class="line"><span class="section">## System Rules</span></span><br><span class="line"><span class="bullet">- </span>All text you output is displayed to the user</span><br><span class="line"><span class="bullet">- </span>Tools are executed in a permission mode</span><br><span class="line"><span class="bullet">- </span>The conversation has unlimited context through automatic summarization</span><br><span class="line"></span><br><span class="line"><span class="section">## Doing Tasks</span></span><br><span class="line"><span class="bullet">- </span>Use tools available to you to assist the user</span><br><span class="line"><span class="bullet">- </span>When creating new code, follow the conventions of the existing codebase</span><br><span class="line"><span class="bullet">- </span>After completing a task, do not summarize what you did</span><br><span class="line"></span><br><span class="line"><span class="section">## Tools</span></span><br><span class="line">You have access to the following tools:</span><br><span class="line"><span class="bullet">- </span>Read: Read a file from the filesystem</span><br><span class="line"><span class="bullet">- </span>Edit: Make edits to a file</span><br><span class="line"><span class="bullet">- </span>Write: Write a new file</span><br><span class="line"><span class="bullet">- </span>Bash: Execute a bash command</span><br><span class="line"><span class="bullet">- </span>Grep: Search for patterns in files</span><br><span class="line"><span class="bullet">- </span>...</span><br><span class="line"></span><br><span class="line">--- SYSTEM_PROMPT_DYNAMIC_BOUNDARY ---</span><br><span class="line"></span><br><span class="line"><span class="section">## Current Date</span></span><br><span class="line">Today's date is 2026-04-06.</span><br><span class="line"></span><br><span class="line"><span class="section">## Project Context</span></span><br><span class="line">Working directory: /Users/dev/my-project</span><br><span class="line">Git branch: main</span><br><span class="line">Git status: 2 modified files</span><br><span class="line"></span><br><span class="line"><span class="section">## User Instructions</span></span><br><span class="line">(CLADE.md content here)</span><br><span class="line"></span><br><span class="line"><span class="section">## MCP Instructions</span></span><br><span class="line"><span class="bullet">- </span>Filesystem MCP: provides file operations</span><br><span class="line"><span class="bullet">- </span>GitHub MCP: provides issue and PR operations</span><br><span class="line"></span><br><span class="line"><span class="section">## Token Budget</span></span><br><span class="line">You have approximately 150,000 tokens available for this turn.</span><br></pre></td></tr></table></figure><hr><h2 id="八、关键源文件索引"><a href="#八、关键源文件索引" class="headerlink" title="八、关键源文件索引"></a>八、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/constants/prompts.ts</code></td><td>系统提示词组装</td></tr><tr><td><code>src/constants/systemPromptSections.ts</code></td><td>Section 定义和缓存</td></tr><tr><td><code>src/utils/systemPrompt.ts</code></td><td>优先级解析</td></tr><tr><td><code>src/utils/claudemd.ts</code></td><td>CLAUDE.md 加载</td></tr><tr><td><code>src/context.ts</code></td><td>系统和用户上下文</td></tr><tr><td><code>src/utils/api.ts</code></td><td>缓存边界分割</td></tr><tr><td><code>src/services/api/claude.ts</code></td><td>API 缓存块构建</td></tr></tbody></table></div><hr><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>Claude Code 的 System Prompt 工程体现了几个核心设计原则：</p><ol><li><strong>分层构建</strong>：静态和动态分离，最大化缓存利用</li><li><strong>优先级解析</strong>：多来源指令按优先级合并</li><li><strong>缓存边界</strong>：明确的缓存策略，降低延迟和成本</li><li><strong>Section 类型</strong>：缓存和缓存破坏两种类型，适应不同需求</li><li><strong>递归引用</strong>：支持 @path 语法，模块化指令组织</li></ol><p>这个设计是 Prompt 工程的教科书级案例——既满足了动态性需求，又最大化了缓存效率。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-context-compression/">Context 管理：四级压缩与无限对话的秘密</a></li><li>下一篇：<a href="/claude-code-skills-system/">Skills 系统：条件激活与动态发现</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Claude Code 的系统提示词不是一个静态字符串，而是一个动态组装的管道。通过分层构建、缓存边界、Section 类型等设计，实现了跨会话的缓存复用，大幅降低了延迟和成本。这是 Prompt 工程的教科书级案例。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="System Prompt" scheme="https://donehub.github.io/tags/System-Prompt/"/>
    
  </entry>
  
  <entry>
    <title>工具系统设计：从定义到执行的七步管道</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-tool-system/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-tool-system/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:22.161Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Claude Code 有 48+ 个内置工具，每个工具都是一个完整的生命周期管理单元。从定义到执行，工具要经过七步管道：查找、解析、验证、钩子、权限、执行、后处理。这个设计使得每个工具都是自描述、自验证、自渲染的——框架不需要了解工具的内部逻辑，只需调用标准接口。</p></blockquote><a id="more"></a><h2 id="导读：工具不只是函数调用"><a href="#导读：工具不只是函数调用" class="headerlink" title="导读：工具不只是函数调用"></a>导读：工具不只是函数调用</h2><p>在很多 AI Agent 框架中，工具只是一个简单的函数：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@tool</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">read_file</span><span class="params">(path: str)</span> -&gt; str:</span></span><br><span class="line">    <span class="keyword">with</span> open(path) <span class="keyword">as</span> f:</span><br><span class="line">        <span class="keyword">return</span> f.read()</span><br></pre></td></tr></table></figure><p>但在 Claude Code 中，工具是一个<strong>完整的生命周期管理单元</strong>：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Tool&lt;Input, Output&gt; = &#123;</span><br><span class="line">  <span class="comment">// 身份</span></span><br><span class="line">  name: <span class="built_in">string</span></span><br><span class="line">  aliases?: <span class="built_in">string</span>[]</span><br><span class="line">  searchHint?: <span class="built_in">string</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 能力声明</span></span><br><span class="line">  isEnabled(): <span class="built_in">boolean</span></span><br><span class="line">  isConcurrencySafe(input): <span class="built_in">boolean</span></span><br><span class="line">  isReadOnly(input): <span class="built_in">boolean</span></span><br><span class="line">  isDestructive(input): <span class="built_in">boolean</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 生命周期</span></span><br><span class="line">  validateInput(input, context)</span><br><span class="line">  checkPermissions(input, context)</span><br><span class="line">  call(input, context, ...)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 输出与渲染</span></span><br><span class="line">  renderToolUseMessage(input)</span><br><span class="line">  renderToolResultMessage(content)</span><br><span class="line">  mapToolResultToToolResultBlockParam()</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 智能特性</span></span><br><span class="line">  inputSchema: Zod schema</span><br><span class="line">  maxResultSizeChars: <span class="built_in">number</span></span><br><span class="line">  getToolUseSummary?(input): <span class="built_in">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种设计使得每个工具都是<strong>自描述、自验证、自渲染</strong>的——框架不需要了解工具的内部逻辑，只需调用标准接口。</p><hr><h2 id="一、工具的定义：不只是名称和函数"><a href="#一、工具的定义：不只是名称和函数" class="headerlink" title="一、工具的定义：不只是名称和函数"></a>一、工具的定义：不只是名称和函数</h2><h3 id="1-1-Tool-接口详解"><a href="#1-1-Tool-接口详解" class="headerlink" title="1.1 Tool 接口详解"></a>1.1 Tool 接口详解</h3><p><code>src/Tool.ts</code>（约 792 行）定义了工具的完整接口：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> Tool&lt;Input, Output, P <span class="keyword">extends</span> ToolProgressData&gt; = &#123;</span><br><span class="line">  <span class="comment">// ===== 身份 =====</span></span><br><span class="line">  name: <span class="built_in">string</span>                    <span class="comment">// 工具名称</span></span><br><span class="line">  aliases?: <span class="built_in">string</span>[]              <span class="comment">// 向后兼容的旧名称</span></span><br><span class="line">  searchHint?: <span class="built_in">string</span>             <span class="comment">// ToolSearch 关键词匹配</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// ===== 能力声明 =====</span></span><br><span class="line">  isEnabled(): <span class="built_in">boolean</span>            <span class="comment">// 是否可用</span></span><br><span class="line">  isConcurrencySafe(input): <span class="built_in">boolean</span>   <span class="comment">// 是否可并行执行</span></span><br><span class="line">  isReadOnly(input): <span class="built_in">boolean</span>          <span class="comment">// 是否只读操作</span></span><br><span class="line">  isDestructive?(input): <span class="built_in">boolean</span>      <span class="comment">// 是否破坏性操作</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// ===== 生命周期 =====</span></span><br><span class="line">  validateInput?(input, context): <span class="built_in">Promise</span>&lt;ValidationResult&gt;  <span class="comment">// 输入验证</span></span><br><span class="line">  checkPermissions?(input, context): <span class="built_in">Promise</span>&lt;PermissionResult&gt;  <span class="comment">// 权限检查</span></span><br><span class="line">  call(input, context, canUseTool, parentMessage, onProgress): <span class="built_in">Promise</span>&lt;ToolResult&lt;Output&gt;&gt;  <span class="comment">// 执行</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// ===== 输出与渲染 =====</span></span><br><span class="line">  renderToolUseMessage?(input): ReactNode     <span class="comment">// 渲染调用信息</span></span><br><span class="line">  renderToolResultMessage?(content): ReactNode  <span class="comment">// 渲染结果</span></span><br><span class="line">  renderToolUseProgressMessage?(...): ReactNode  <span class="comment">// 渲染进度</span></span><br><span class="line">  mapToolResultToToolResultBlockParam?(...): ToolResultBlockParam  <span class="comment">// 映射为 API 格式</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// ===== 智能特性 =====</span></span><br><span class="line">  inputSchema: Input                        <span class="comment">// Zod schema</span></span><br><span class="line">  outputSchema?: z.ZodType&lt;Output&gt;          <span class="comment">// 输出 schema</span></span><br><span class="line">  maxResultSizeChars?: <span class="built_in">number</span>               <span class="comment">// 结果大小阈值</span></span><br><span class="line">  getToolUseSummary?(input): <span class="built_in">string</span>         <span class="comment">// 工具使用摘要</span></span><br><span class="line">  shouldDefer?: <span class="built_in">boolean</span>                     <span class="comment">// 是否延迟加载</span></span><br><span class="line">  alwaysLoad?: <span class="built_in">boolean</span>                      <span class="comment">// 是否始终加载</span></span><br><span class="line">  toAutoClassifierInput?(input): <span class="built_in">string</span>     <span class="comment">// 安全分类器输入</span></span><br><span class="line">  preparePermissionMatcher?<span class="function">(<span class="params">input</span>): <span class="params">Promise</span>&lt;(<span class="params">pattern: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">boolean</span>&gt;  <span class="comment">// 权限匹配器</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-2-buildTool-工厂函数"><a href="#1-2-buildTool-工厂函数" class="headerlink" title="1.2 buildTool 工厂函数"></a>1.2 buildTool 工厂函数</h3><p><code>buildTool()</code> 函数提供了工具定义的便捷方式：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> BashTool = buildTool(&#123;</span><br><span class="line">  name: <span class="string">'Bash'</span>,</span><br><span class="line">  description: <span class="keyword">async</span> (input) =&gt; <span class="string">`Execute command: <span class="subst">$&#123;input.command&#125;</span>`</span>,</span><br><span class="line">  inputSchema: z.object(&#123;</span><br><span class="line">    command: z.string(),</span><br><span class="line">    timeout: z.number().optional(),</span><br><span class="line">  &#125;),</span><br><span class="line">  isConcurrencySafe: <span class="function">(<span class="params">input</span>) =&gt;</span> <span class="literal">false</span>,  <span class="comment">// Bash 可能修改状态</span></span><br><span class="line">  isReadOnly: <span class="function">(<span class="params">input</span>) =&gt;</span> isReadOnlyCommand(input.command),</span><br><span class="line">  isDestructive: <span class="function">(<span class="params">input</span>) =&gt;</span> isDestructiveCommand(input.command),</span><br><span class="line">  validateInput: <span class="keyword">async</span> (input, context) =&gt; &#123; <span class="comment">/* ... */</span> &#125;,</span><br><span class="line">  checkPermissions: <span class="keyword">async</span> (input, context) =&gt; &#123; <span class="comment">/* ... */</span> &#125;,</span><br><span class="line">  call: <span class="keyword">async</span> (input, context, canUseTool, parentMessage, onProgress) =&gt; &#123; <span class="comment">/* ... */</span> &#125;,</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><hr><h2 id="二、工具注册：三阶段流水线"><a href="#二、工具注册：三阶段流水线" class="headerlink" title="二、工具注册：三阶段流水线"></a>二、工具注册：三阶段流水线</h2><p>工具的发现和注册分三个阶段（<code>src/tools.ts</code>）：</p><h3 id="2-1-阶段1：基础工具池"><a href="#2-1-阶段1：基础工具池" class="headerlink" title="2.1 阶段1：基础工具池"></a>2.1 阶段1：基础工具池</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools.ts:50-150</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">getAllBaseTools</span>(<span class="params"></span>): <span class="title">Tools</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> [</span><br><span class="line">    <span class="comment">// 文件操作</span></span><br><span class="line">    FileReadTool,</span><br><span class="line">    FileEditTool,</span><br><span class="line">    FileWriteTool,</span><br><span class="line">    GlobTool,</span><br><span class="line">    GrepTool,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Shell 执行</span></span><br><span class="line">    BashTool,</span><br><span class="line">    PowerShellTool,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 网络</span></span><br><span class="line">    WebFetchTool,</span><br><span class="line">    WebSearchTool,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Agent 编排</span></span><br><span class="line">    AgentTool,</span><br><span class="line">    TeamCreateTool,</span><br><span class="line">    SendMessageTool,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 任务管理</span></span><br><span class="line">    TaskCreateTool,</span><br><span class="line">    TaskOutputTool,</span><br><span class="line">    TodoWriteTool,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 其他...</span></span><br><span class="line">    AskUserQuestionTool,</span><br><span class="line">    SkillTool,</span><br><span class="line">    SleepTool,</span><br><span class="line">    <span class="comment">// ... 共 48+ 个</span></span><br><span class="line">  ].filter(<span class="function"><span class="params">tool</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// Feature Flag 过滤</span></span><br><span class="line">    <span class="keyword">if</span> (tool.name === <span class="string">'Agent'</span> &amp;&amp; !feature(<span class="string">'FORK_SUBAGENT'</span>)) <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-阶段2：过滤"><a href="#2-2-阶段2：过滤" class="headerlink" title="2.2 阶段2：过滤"></a>2.2 阶段2：过滤</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools.ts:200-280</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">getTools</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  baseTools: Tools,</span></span></span><br><span class="line"><span class="function"><span class="params">  permissionContext: ToolPermissionContext,</span></span></span><br><span class="line"><span class="function"><span class="params">  options: GetToolsOptions,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Tools</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> baseTools.filter(<span class="function"><span class="params">tool</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 权限模式过滤</span></span><br><span class="line">    <span class="keyword">if</span> (permissionContext.mode === <span class="string">'dontAsk'</span> &amp;&amp; !tool.isReadOnly?.()) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. REPL 模式过滤</span></span><br><span class="line">    <span class="keyword">if</span> (options.isReplMode &amp;&amp; !isReplCompatible(tool)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. isEnabled 检查</span></span><br><span class="line">    <span class="keyword">if</span> (!tool.isEnabled()) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-阶段3：MCP-合并"><a href="#2-3-阶段3：MCP-合并" class="headerlink" title="2.3 阶段3：MCP 合并"></a>2.3 阶段3：MCP 合并</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools.ts:300-350</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">assembleToolPool</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  baseTools: Tools,</span></span></span><br><span class="line"><span class="function"><span class="params">  mcpClients: MCPServerConnection[],</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Tools</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> mcpTools: Tools = []</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> client of mcpClients) &#123;</span><br><span class="line">    <span class="keyword">if</span> (client.type !== <span class="string">'connected'</span>) <span class="keyword">continue</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> mcpTool of client.tools) &#123;</span><br><span class="line">      <span class="comment">// MCP 工具命名：mcp__&#123;serverName&#125;__&#123;toolName&#125;</span></span><br><span class="line">      <span class="keyword">const</span> name = <span class="string">`mcp__<span class="subst">$&#123;normalizeNameForMCP(client.name)&#125;</span>__<span class="subst">$&#123;mcpTool.name&#125;</span>`</span></span><br><span class="line">      mcpTools.push(convertMcpToolToTool(name, mcpTool, client))</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 合并：内置优先，去重，排序（缓存稳定性）</span></span><br><span class="line">  <span class="keyword">return</span> mergeAndDeduplicate(baseTools, mcpTools)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>工具池构建流程图：</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">getAllBaseTools()</span><br><span class="line">  │  48+ 个内置工具</span><br><span class="line">  │  + Feature Flag 过滤</span><br><span class="line">  │</span><br><span class="line">  ▼</span><br><span class="line">getTools()</span><br><span class="line">  │  权限模式过滤</span><br><span class="line">  │  REPL 模式过滤</span><br><span class="line">  │  isEnabled() 过滤</span><br><span class="line">  │</span><br><span class="line">  ▼</span><br><span class="line">assembleToolPool()</span><br><span class="line">  │  + MCP 工具</span><br><span class="line">  │  去重（内置优先）</span><br><span class="line">  │  排序（缓存稳定性）</span><br><span class="line">  │</span><br><span class="line">  ▼</span><br><span class="line">最终工具池</span><br></pre></td></tr></table></figure><hr><h2 id="三、七步执行管道"><a href="#三、七步执行管道" class="headerlink" title="三、七步执行管道"></a>三、七步执行管道</h2><p>一次工具调用要经过<strong>7 步管道</strong>（<code>src/services/tools/toolExecution.ts</code>）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    工具执行管道                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Step 1: 工具查找                                           │</span><br><span class="line">│    └─ findToolByName(name) → 支持别名回退                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Step 2: 输入解析（Zod）                                     │</span><br><span class="line">│    └─ inputSchema.safeParse(input) → 类型验证               │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Step 3: 自定义验证                                         │</span><br><span class="line">│    └─ tool.validateInput?(input, context)                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Step 4: Pre-Tool 钩子                                      │</span><br><span class="line">│    └─ runPreToolUseHooks(tool, input, context)              │</span><br><span class="line">│       ├─ 退出码 0: 成功，继续                               │</span><br><span class="line">│       ├─ 退出码 2: 阻塞，展示错误                           │</span><br><span class="line">│       └─ 其他: 展示给用户                                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Step 5: 权限检查                                           │</span><br><span class="line">│    └─ hasPermissionsToUseTool(tool, input, context)         │</span><br><span class="line">│       ├─ behavior: &#39;allow&#39; → 继续                           │</span><br><span class="line">│       ├─ behavior: &#39;deny&#39; → 返回拒绝                        │</span><br><span class="line">│       └─ behavior: &#39;ask&#39; → 弹出确认对话框                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Step 6: 实际执行                                           │</span><br><span class="line">│    └─ tool.call(input, context, canUseTool, ...)            │</span><br><span class="line">│       ├─ 成功 → ToolResult                                  │</span><br><span class="line">│       └─ 失败 → ToolError                                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Step 7: Post-Tool 钩子                                     │</span><br><span class="line">│    └─ runPostToolUseHooks(tool, input, result)              │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="3-1-Step-1-工具查找"><a href="#3-1-Step-1-工具查找" class="headerlink" title="3.1 Step 1: 工具查找"></a>3.1 Step 1: 工具查找</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/Tool.ts:500-530</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">findToolByName</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  name: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  tools: Tools,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Tool</span>&lt;<span class="title">unknown</span>, <span class="title">unknown</span>, <span class="title">ToolProgressData</span>&gt; | <span class="title">undefined</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 精确匹配</span></span><br><span class="line">  <span class="keyword">const</span> exact = tools.find(<span class="function"><span class="params">t</span> =&gt;</span> t.name === name)</span><br><span class="line">  <span class="keyword">if</span> (exact) <span class="keyword">return</span> exact</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 别名匹配（向后兼容）</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> tool of tools) &#123;</span><br><span class="line">    <span class="keyword">if</span> (tool.aliases?.includes(name)) &#123;</span><br><span class="line">      <span class="keyword">return</span> tool</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. MCP 工具特殊处理</span></span><br><span class="line">  <span class="keyword">if</span> (name.startsWith(<span class="string">'mcp__'</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span> findMcpTool(name, tools)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="literal">undefined</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-Step-2-输入解析"><a href="#3-2-Step-2-输入解析" class="headerlink" title="3.2 Step 2: 输入解析"></a>3.2 Step 2: 输入解析</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/tools/toolExecution.ts:200-230</span></span><br><span class="line"><span class="keyword">const</span> parseResult = tool.inputSchema.safeParse(input)</span><br><span class="line"><span class="keyword">if</span> (!parseResult.success) &#123;</span><br><span class="line">  <span class="keyword">const</span> formattedError = formatZodValidationError(parseResult.error)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="keyword">type</span>: <span class="string">'tool_result'</span>,</span><br><span class="line">    content: formattedError,</span><br><span class="line">    is_error: <span class="literal">true</span>,</span><br><span class="line">    tool_use_id: toolUseId,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> validatedInput = parseResult.data</span><br></pre></td></tr></table></figure><h3 id="3-3-Step-3-自定义验证"><a href="#3-3-Step-3-自定义验证" class="headerlink" title="3.3 Step 3: 自定义验证"></a>3.3 Step 3: 自定义验证</h3><p>某些工具需要额外的验证逻辑：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/FileEditTool/FileEditTool.ts:150-200</span></span><br><span class="line"><span class="keyword">async</span> validateInput(input, context): <span class="built_in">Promise</span>&lt;ValidationResult&gt; &#123;</span><br><span class="line">  <span class="comment">// 1. 文件存在性检查</span></span><br><span class="line">  <span class="keyword">if</span> (!<span class="keyword">await</span> fileExists(input.file_path)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; result: <span class="literal">false</span>, message: <span class="string">'File does not exist'</span>, errorCode: <span class="number">1</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 文件大小限制</span></span><br><span class="line">  <span class="keyword">const</span> stats = <span class="keyword">await</span> stat(input.file_path)</span><br><span class="line">  <span class="keyword">if</span> (stats.size &gt; <span class="number">1</span>_000_000_000) &#123;  <span class="comment">// 1 GiB</span></span><br><span class="line">    <span class="keyword">return</span> &#123; result: <span class="literal">false</span>, message: <span class="string">'File too large'</span>, errorCode: <span class="number">2</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 必须先读取</span></span><br><span class="line">  <span class="keyword">const</span> readState = context.readFileState.get(input.file_path)</span><br><span class="line">  <span class="keyword">if</span> (!readState) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; result: <span class="literal">false</span>, message: <span class="string">'Must read file before editing'</span>, errorCode: <span class="number">3</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 文件未被修改</span></span><br><span class="line">  <span class="keyword">const</span> currentMtime = (<span class="keyword">await</span> stat(input.file_path)).mtimeMs</span><br><span class="line">  <span class="keyword">if</span> (currentMtime &gt; readState.timestamp) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; result: <span class="literal">false</span>, message: <span class="string">'File was modified after reading'</span>, errorCode: <span class="number">4</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; result: <span class="literal">true</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4-Step-4-Pre-Tool-钩子"><a href="#3-4-Step-4-Pre-Tool-钩子" class="headerlink" title="3.4 Step 4: Pre-Tool 钩子"></a>3.4 Step 4: Pre-Tool 钩子</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/tools/toolHooks.ts:50-100</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">runPreToolUseHooks</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  tool: Tool,</span></span></span><br><span class="line"><span class="function"><span class="params">  input: unknown,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">HookResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> hooks = getHooksForTool(tool.name, context)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> hook of hooks) &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> executeHook(hook, &#123;</span><br><span class="line">      tool_name: tool.name,</span><br><span class="line">      tool_input: input,</span><br><span class="line">    &#125;)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (result.exitCode === <span class="number">2</span>) &#123;</span><br><span class="line">      <span class="comment">// 阻塞：展示错误给模型</span></span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        blocked: <span class="literal">true</span>,</span><br><span class="line">        message: result.stderr,</span><br><span class="line">        modifiedInput: parseModifiedInput(result.stdout),</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; blocked: <span class="literal">false</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-5-Step-5-权限检查"><a href="#3-5-Step-5-权限检查" class="headerlink" title="3.5 Step 5: 权限检查"></a>3.5 Step 5: 权限检查</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/permissions/permissions.ts:200-300</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">hasPermissionsToUseTool</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  tool: Tool,</span></span></span><br><span class="line"><span class="function"><span class="params">  input: unknown,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">PermissionResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 检查 deny 规则（最高优先级）</span></span><br><span class="line">  <span class="keyword">const</span> denyResult = checkDenyRules(tool.name, input, context)</span><br><span class="line">  <span class="keyword">if</span> (denyResult) <span class="keyword">return</span> &#123; behavior: <span class="string">'deny'</span>, ...denyResult &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 检查工具特定权限</span></span><br><span class="line">  <span class="keyword">if</span> (tool.checkPermissions) &#123;</span><br><span class="line">    <span class="keyword">const</span> toolResult = <span class="keyword">await</span> tool.checkPermissions(input, context)</span><br><span class="line">    <span class="keyword">if</span> (toolResult.behavior !== <span class="string">'passthrough'</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> toolResult</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 检查 bypass 模式</span></span><br><span class="line">  <span class="keyword">if</span> (context.permissionContext.mode === <span class="string">'bypassPermissions'</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span>, decisionReason: &#123; <span class="keyword">type</span>: <span class="string">'mode'</span> &#125; &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 检查 allow 规则</span></span><br><span class="line">  <span class="keyword">const</span> allowResult = checkAllowRules(tool.name, input, context)</span><br><span class="line">  <span class="keyword">if</span> (allowResult) <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span>, ...allowResult &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. 默认：询问用户</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    behavior: <span class="string">'ask'</span>,</span><br><span class="line">    message: generatePermissionMessage(tool, input),</span><br><span class="line">    suggestions: generateSuggestions(tool, input),</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-6-Step-6-实际执行"><a href="#3-6-Step-6-实际执行" class="headerlink" title="3.6 Step 6: 实际执行"></a>3.6 Step 6: 实际执行</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/tools/toolExecution.ts:400-500</span></span><br><span class="line"><span class="keyword">const</span> startTime = <span class="built_in">Date</span>.now()</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> result = <span class="keyword">await</span> tool.call(</span><br><span class="line">    validatedInput,</span><br><span class="line">    context,</span><br><span class="line">    canUseTool,</span><br><span class="line">    parentMessage,</span><br><span class="line">    onProgress,</span><br><span class="line">  )</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> durationMs = <span class="built_in">Date</span>.now() - startTime</span><br><span class="line">  addToToolDuration(durationMs)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="keyword">type</span>: <span class="string">'tool_result'</span>,</span><br><span class="line">    content: result.content,</span><br><span class="line">    tool_use_id: toolUseId,</span><br><span class="line">  &#125;</span><br><span class="line">&#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">  <span class="keyword">const</span> classifiedError = classifyToolError(error)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="keyword">type</span>: <span class="string">'tool_result'</span>,</span><br><span class="line">    content: <span class="string">`Error: <span class="subst">$&#123;classifiedError&#125;</span>`</span>,</span><br><span class="line">    is_error: <span class="literal">true</span>,</span><br><span class="line">    tool_use_id: toolUseId,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-7-Step-7-Post-Tool-钩子"><a href="#3-7-Step-7-Post-Tool-钩子" class="headerlink" title="3.7 Step 7: Post-Tool 钩子"></a>3.7 Step 7: Post-Tool 钩子</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/tools/toolHooks.ts:150-200</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">runPostToolUseHooks</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  tool: Tool,</span></span></span><br><span class="line"><span class="function"><span class="params">  input: unknown,</span></span></span><br><span class="line"><span class="function"><span class="params">  result: ToolResult,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">void</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> hooks = getPostToolUseHooks(tool.name, context)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> hook of hooks) &#123;</span><br><span class="line">    <span class="keyword">await</span> executeHook(hook, &#123;</span><br><span class="line">      tool_name: tool.name,</span><br><span class="line">      tool_input: input,</span><br><span class="line">      tool_result: result.content,</span><br><span class="line">      tool_result_is_error: result.is_error,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="四、工具延迟加载"><a href="#四、工具延迟加载" class="headerlink" title="四、工具延迟加载"></a>四、工具延迟加载</h2><p>Claude Code 有 48+ 个内置工具。如果每次 API 调用都把所有工具定义发给模型，会浪费大量 token。</p><h3 id="4-1-延迟加载设计"><a href="#4-1-延迟加载设计" class="headerlink" title="4.1 延迟加载设计"></a>4.1 延迟加载设计</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 工具可以标记为"延迟加载"</span></span><br><span class="line">&#123;</span><br><span class="line">  shouldDefer: <span class="literal">true</span>,       <span class="comment">// 只在 ToolSearch 中列出名称</span></span><br><span class="line">  alwaysLoad: <span class="literal">false</span>,       <span class="comment">// 不在初始提示词中包含完整 schema</span></span><br><span class="line">  searchHint: <span class="string">"notebook"</span>   <span class="comment">// 搜索关键词</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-ToolSearch-工具"><a href="#4-2-ToolSearch-工具" class="headerlink" title="4.2 ToolSearch 工具"></a>4.2 ToolSearch 工具</h3><p>当模型需要使用延迟加载的工具时：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">模型看到:</span><br><span class="line">  &quot;The following tools are available but deferred: NotebookEdit, ...&quot;</span><br><span class="line"></span><br><span class="line">模型调用:</span><br><span class="line">  ToolSearch(&#123; query: &quot;notebook&quot; &#125;)</span><br><span class="line"></span><br><span class="line">返回:</span><br><span class="line">  NotebookEdit 的完整 schema 和使用说明</span><br><span class="line"></span><br><span class="line">模型调用:</span><br><span class="line">  NotebookEdit(&#123; ... &#125;)</span><br></pre></td></tr></table></figure><p><strong>Token 节省</strong>：</p><ul><li>默认情况下，48 个工具的 schema 约 15000 tokens</li><li>延迟加载后，初始提示词只包含核心工具，约 5000 tokens</li><li>节省约 66% 的工具相关 token</li></ul><hr><h2 id="五、工具结果处理"><a href="#五、工具结果处理" class="headerlink" title="五、工具结果处理"></a>五、工具结果处理</h2><h3 id="5-1-结果大小限制"><a href="#5-1-结果大小限制" class="headerlink" title="5.1 结果大小限制"></a>5.1 结果大小限制</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/toolResultStorage.ts</span></span><br><span class="line"><span class="keyword">const</span> TOOL_RESULT_PERSIST_THRESHOLD_CHARS = <span class="number">20</span>_000</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">processToolResultBlock</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  tool: Tool,</span></span></span><br><span class="line"><span class="function"><span class="params">  result: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  toolUseID: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">ToolResultBlockParam</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (result.length &gt; TOOL_RESULT_PERSIST_THRESHOLD_CHARS) &#123;</span><br><span class="line">    <span class="comment">// 保存到磁盘</span></span><br><span class="line">    <span class="keyword">const</span> filePath = getToolResultPath(toolUseID)</span><br><span class="line">    <span class="keyword">await</span> writeFile(filePath, result)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 返回预览</span></span><br><span class="line">    <span class="keyword">const</span> preview = result.slice(<span class="number">0</span>, <span class="number">4096</span>)</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      content: <span class="string">`<span class="subst">$&#123;preview&#125;</span>\n\n[Output saved to <span class="subst">$&#123;filePath&#125;</span>. Use Read tool to view full output.]`</span>,</span><br><span class="line">      tool_use_id: toolUseID,</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123; content: result, tool_use_id: toolUseID &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-文件读取缓存"><a href="#5-2-文件读取缓存" class="headerlink" title="5.2 文件读取缓存"></a>5.2 文件读取缓存</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/fileStateCache.ts</span></span><br><span class="line"><span class="keyword">class</span> FileStateCache &#123;</span><br><span class="line">  <span class="keyword">private</span> cache = <span class="keyword">new</span> Map&lt;<span class="built_in">string</span>, &#123;</span><br><span class="line">    timestamp: <span class="built_in">number</span></span><br><span class="line">    content: <span class="built_in">string</span></span><br><span class="line">    offset?: <span class="built_in">number</span></span><br><span class="line">    limit?: <span class="built_in">number</span></span><br><span class="line">  &#125;&gt;()</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">get</span>(path: <span class="built_in">string</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.cache.get(path)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">set</span>(path: <span class="built_in">string</span>, state: &#123; timestamp: <span class="built_in">number</span>, content: <span class="built_in">string</span> &#125;) &#123;</span><br><span class="line">    <span class="keyword">this</span>.cache.set(path, state)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 用于检测并发修改</span></span><br><span class="line">  validate(path: <span class="built_in">string</span>): <span class="built_in">boolean</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> cached = <span class="keyword">this</span>.cache.get(path)</span><br><span class="line">    <span class="keyword">if</span> (!cached) <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> currentMtime = statSync(path).mtimeMs</span><br><span class="line">    <span class="keyword">return</span> currentMtime &lt;= cached.timestamp</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="六、关键设计原则"><a href="#六、关键设计原则" class="headerlink" title="六、关键设计原则"></a>六、关键设计原则</h2><h3 id="6-1-自描述工具"><a href="#6-1-自描述工具" class="headerlink" title="6.1 自描述工具"></a>6.1 自描述工具</h3><p>每个工具通过接口暴露所有必要信息，框架无需了解内部：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 框架只知道接口，不知道实现</span></span><br><span class="line"><span class="keyword">const</span> isReadOnly = tool.isReadOnly?.(input)</span><br><span class="line"><span class="keyword">const</span> isDestructive = tool.isDestructive?.(input)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 工具自己决定</span></span><br><span class="line">tool.checkPermissions(input, context)</span><br></pre></td></tr></table></figure><h3 id="6-2-验证前置"><a href="#6-2-验证前置" class="headerlink" title="6.2 验证前置"></a>6.2 验证前置</h3><p>输入验证在权限检查之前：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">输入 → Zod 解析 → 自定义验证 → Pre-Tool 钩子 → 权限检查 → 执行</span><br></pre></td></tr></table></figure><p>这确保了权限检查不会因为无效输入而触发。</p><h3 id="6-3-钩子可扩展"><a href="#6-3-钩子可扩展" class="headerlink" title="6.3 钩子可扩展"></a>6.3 钩子可扩展</h3><p>钩子系统允许用户在任何阶段注入自定义逻辑：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// settings.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">"hooks"</span>: &#123;</span><br><span class="line">    <span class="attr">"PreToolUse"</span>: [&#123;</span><br><span class="line">      <span class="attr">"matcher"</span>: <span class="string">"Bash"</span>,</span><br><span class="line">      <span class="attr">"hooks"</span>: [&#123; <span class="attr">"type"</span>: <span class="string">"command"</span>, <span class="attr">"command"</span>: <span class="string">"echo 'Bash called'"</span> &#125;]</span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、关键源文件索引"><a href="#七、关键源文件索引" class="headerlink" title="七、关键源文件索引"></a>七、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>行数</th><th>职责</th></tr></thead><tbody><tr><td><code>src/Tool.ts</code></td><td>~792</td><td>Tool 类型定义和构建器</td></tr><tr><td><code>src/tools.ts</code></td><td>~389</td><td>工具发现和注册</td></tr><tr><td><code>src/services/tools/toolExecution.ts</code></td><td>~1500</td><td>执行管道</td></tr><tr><td><code>src/services/tools/toolOrchestration.ts</code></td><td>~200</td><td>并行/串行策略</td></tr><tr><td><code>src/services/tools/toolHooks.ts</code></td><td>~300</td><td>钩子执行</td></tr><tr><td><code>src/utils/toolResultStorage.ts</code></td><td>~200</td><td>结果存储</td></tr><tr><td><code>src/utils/fileStateCache.ts</code></td><td>~100</td><td>文件状态缓存</td></tr></tbody></table></div><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Claude Code 的工具系统设计体现了几个核心原则：</p><ol><li><strong>接口驱动</strong>：统一的 Tool 接口，框架无需了解实现</li><li><strong>管道模式</strong>：七步执行管道，每步职责清晰</li><li><strong>延迟加载</strong>：减少初始 token 消耗</li><li><strong>钩子扩展</strong>：用户可在任意阶段注入逻辑</li><li><strong>结果管理</strong>：自动处理大型结果</li></ol><p>这个设计使得添加新工具变得简单——只需实现 Tool 接口，框架会自动处理验证、权限、执行和结果处理。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-async-generator-state-machine/">打破 ReAct 迷思：Async Generator 状态机</a></li><li>下一篇：<a href="/claude-code-multi-agent/">多 Agent 编排：四种代理类型与协作机制</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Claude Code 有 48+ 个内置工具，每个工具都是一个完整的生命周期管理单元。从定义到执行，工具要经过七步管道：查找、解析、验证、钩子、权限、执行、后处理。这个设计使得每个工具都是自描述、自验证、自渲染的——框架不需要了解工具的内部逻辑，只需调用标准接口。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Tool System" scheme="https://donehub.github.io/tags/Tool-System/"/>
    
  </entry>
  
</feed>
