Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

tools/AgentTool/ 实现 Agent(别名 Task)工具:主线程模型将子任务委派给独立 query 循环。核心文件 AgentTool.tsx(1300+ 行)处理输入 schema、teammate spawn、fork 实验、worktree/remote 隔离与 sync/async 生命周期;runAgent.ts 创建子 context 并调用 query();loadAgentsDir.ts 加载 .claude/agents 定义。本章要求你能从一次 Agent tool_use 追踪到 runAgent → query 递归、以及 async 后台任务的输出文件路径。

学完本章你应该能

  • 说明 AgentToolInput schema 各字段与 feature gate 的 omit 关系
  • 解释 sync vs async_launched 输出分支
  • 理解 createSubagentContext 如何收窄 tools 与权限
  • 掌握 teammate spawn(name + team_name)与 fork 路径差异
  • 能在 assembleToolPool 语境下理解子 agent 工具池过滤

核心概念(先读懂这些)

Agent 是嵌套 query

AgentTool.call 最终调用 runAgent → query(),形成递归。子 context 有独立 agentId、messages 子集、fileStateCache 克隆、sidechain transcript。forceAsync / run_in_background 走 LocalAgentTask 注册,主线程立即返回 async_launched。同步路径在 call 内 await 子 query 完成后返回 completed status。

工具池在子 agent 侧过滤

constants/tools.ts 定义 ALL_AGENT_DISALLOWED_TOOLS 等;runAgent 内 filterToolsForAgent 移除 Agent 自身(防无限递归)及策略禁止工具。coordinator 模式 worker 仅保留 COORDINATOR_MODE_ALLOWED_TOOLS。assembleToolPool 保证与 REPL 一致的内置+MCP 合并后再过滤。

Fork 与 general-purpose 路由

isForkSubagentEnabled 时 omit subagent_type 走 FORK_AGENT(共享父 system prompt cache)。fork 子 agent 内再次 Agent(fork) 被 querySource / isInForkChild 拒绝。legacy 路径 default subagent_type 为 general-purpose。

建议学习步骤

  1. 阅读 constants.ts 工具名与 ONE_SHOT 类型
  2. 阅读 inputSchema lazySchema 与 feature omit
  3. 阅读 call() 入口:teammate / fork / agent 选择
  4. 阅读 runAgent.ts 子 context 创建
  5. 阅读 async 分支与 LocalAgentTask 注册
  6. 对照 UI.tsx 进度与 grouped 渲染

常见误区

注意

in-process teammate 不能 spawn background agent 或 nested teammate

注意

requiredMcpServers 需等待 pending MCP 连接(最多 30s poll)

注意

Agent deny 规则语法 Agent(Explore) 与工具名 Task 别名并存

目录结构与职责

AgentTool/ 核心文件:

文件职责
AgentTool.tsxbuildTool 主实现、call 路由、schema
runAgent.ts子 query 入口、context 克隆、tool 过滤
agentToolUtils.tsasync 生命周期、handoff、finalize
loadAgentsDir.ts磁盘 agent 定义、built-in agents
forkSubagent.tsFORK_AGENT 实验、cache-safe prompt
prompt.ts动态 agent 列表 prompt
UI.tsx进度、grouped Agent 行、remote 状态
constants.tsAGENT_TOOL_NAME、LEGACY_AGENT_TOOL_NAME

AgentTool maxResultSizeChars=100_000,高于 Bash。searchHint: delegate work to a subagent。

工具名与常量

constants.ts 定义:

  • AGENT_TOOL_NAME = 'Agent' — 当前 wire name
  • LEGACY_AGENT_TOOL_NAME = 'Task' — 权限规则、hooks、resume 会话兼容
  • ONE_SHOT_BUILTIN_AGENT_TYPES — Explore、Plan 等一次性 agent,省略 agentId trailer 省 token

AgentTool 声明 aliases: [LEGACY_AGENT_TOOL_NAME],findToolByName 双名命中。

permissions 中 Agent(Explore) deny 规则通过 filterDeniedAgents / getDenyRuleForAgent 生效,call 内 selectedAgent 找不到时检查 agentExistsButDenied 抛明确错误。

源码引用: src/tools/AgentTool/constants.ts · 第 1–12 行(共 13 行)

   1| export const AGENT_TOOL_NAME = 'Agent'
   2| // Legacy wire name for backward compat (permission rules, hooks, resumed sessions)
   3| export const LEGACY_AGENT_TOOL_NAME = 'Task'
   4| export const VERIFICATION_AGENT_TYPE = 'verification'
   5| 
   6| // Built-in agents that run once and return a report — the parent never
   7| // SendMessages back to continue them. Skip the agentId/SendMessage/usage
   8| // trailer for these to save tokens (~135 chars × 34M Explore runs/week).
   9| export const ONE_SHOT_BUILTIN_AGENT_TYPES: ReadonlySet<string> = new Set([
  10|   'Explore',
  11|   'Plan',
  12| ])

inputSchema 与 feature gating

inputSchema 使用 lazySchema 组合 base + multi-agent + isolation:

baseInputSchema: description、prompt、subagent_type、model、run_in_background

multi-agent(feature): name、team_name、mode — teammate 寻址

isolation: worktree(git 临时 worktree)、remote(ant-only CCR)

运行时 omit:

  • KAIROS off → omit cwd
  • background disabled 或 fork enabled → omit run_in_background
  • GrowthBook tengu_auto_background_agents 影响 getAutoBackgroundMs

AgentToolInput 显式类型 widen omit 后字段,call 解构始终安全。

源码引用: src/tools/AgentTool/AgentTool.tsx · 第 82–125 行(共 1835 行)

  82| import {
  83|   createAgentWorktree,
  84|   hasWorktreeChanges,
  85|   removeAgentWorktree,
  86| } from '../../utils/worktree.js'
  87| import { BASH_TOOL_NAME } from '../BashTool/toolName.js'
  88| import { BackgroundHint } from '../BashTool/UI.js'
  89| import { FILE_READ_TOOL_NAME } from '../FileReadTool/prompt.js'
  90| import { spawnTeammate } from '../shared/spawnMultiAgent.js'
  91| import { setAgentColor } from './agentColorManager.js'
  92| import {
  93|   agentToolResultSchema,
  94|   classifyHandoffIfNeeded,
  95|   emitTaskProgress,
  96|   extractPartialResult,
  97|   finalizeAgentTool,
  98|   getLastToolUseName,
  99|   runAsyncAgentLifecycle,
 100| } from './agentToolUtils.js'
 101| import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js'
 102| import {
 103|   AGENT_TOOL_NAME,
 104|   LEGACY_AGENT_TOOL_NAME,
 105|   ONE_SHOT_BUILTIN_AGENT_TYPES,
 106| } from './constants.js'
 107| import {
 108|   buildForkedMessages,
 109|   buildWorktreeNotice,
 110|   FORK_AGENT,
 111|   isForkSubagentEnabled,
 112|   isInForkChild,
 113| } from './forkSubagent.js'
 114| import type { AgentDefinition } from './loadAgentsDir.js'
 115| import {
 116|   filterAgentsByMcpRequirements,
 117|   hasRequiredMcpServers,
 118|   isBuiltInAgent,
 119| } from './loadAgentsDir.js'
 120| import { getPrompt } from './prompt.js'
 121| import { runAgent } from './runAgent.js'
 122| import {
 123|   renderGroupedAgentToolUse,
 124|   renderToolResultMessage,
 125|   renderToolUseErrorMessage,

源码引用: src/tools/AgentTool/AgentTool.tsx · 第 140–155 行(共 1835 行)

 140| 
 141| // Progress display constants (for showing background hint)
 142| const PROGRESS_THRESHOLD_MS = 2000 // Show background hint after 2 seconds
 143| 
 144| // Check if background tasks are disabled at module load time
 145| const isBackgroundTasksDisabled =
 146|   // eslint-disable-next-line custom-rules/no-process-env-top-level -- Intentional: schema must be defined at module load
 147|   isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS)
 148| 
 149| // Auto-background agent tasks after this many ms (0 = disabled)
 150| // Enabled by env var OR GrowthBook gate (checked lazily since GB may not be ready at module load)
 151| function getAutoBackgroundMs(): number {
 152|   if (
 153|     isEnvTruthy(process.env.CLAUDE_AUTO_BACKGROUND_TASKS) ||
 154|     getFeatureValue_CACHED_MAY_BE_STALE('tengu_auto_background_agents', false)
 155|   ) {

prompt():动态 agent 列表

AgentTool.prompt 异步构建系统 prompt 片段:

  1. 扫描 tools 中 mcp__ 前缀收集已连接 MCP server
  2. filterAgentsByMcpRequirements — agent 定义 requiredMcpServers 前置条件
  3. filterDeniedAgents — 权限 deny Agent(type)
  4. getPrompt(filteredAgents, isCoordinator, allowedAgentTypes)

coordinator 模式(CLAUDE_CODE_COORDINATOR_MODE)改变可选 agent 列表与描述。allowedAgentTypes 来自 Agent(x,y) 工具规格 restrict。

阅读建议: 对照 loadAgentsDir 的 built-in generalPurpose、Explore、Plan 定义与 frontmatter model/color。

源码引用: src/tools/AgentTool/AgentTool.tsx · 第 196–225 行(共 1835 行)

 196|       .describe(
 197|         'Name for the spawned agent. Makes it addressable via SendMessage({to: name}) while running.',
 198|       ),
 199|     team_name: z
 200|       .string()
 201|       .optional()
 202|       .describe(
 203|         'Team name for spawning. Uses current team context if omitted.',
 204|       ),
 205|     mode: permissionModeSchema()
 206|       .optional()
 207|       .describe(
 208|         'Permission mode for spawned teammate (e.g., "plan" to require plan approval).',
 209|       ),
 210|   })
 211| 
 212|   return baseInputSchema()
 213|     .merge(multiAgentInputSchema)
 214|     .extend({
 215|       isolation: ("external" === 'ant'
 216|         ? z.enum(['worktree', 'remote'])
 217|         : z.enum(['worktree'])
 218|       )
 219|         .optional()
 220|         .describe(
 221|           "external" === 'ant'
 222|             ? 'Isolation mode. "worktree" creates a temporary git worktree so the agent works on an isolated copy of the repo. "remote" launches the agent in a remote CCR environment (always runs in background).'
 223|             : 'Isolation mode. "worktree" creates a temporary git worktree so the agent works on an isolated copy of the repo.',
 224|         ),
 225|       cwd: z

call():teammate 与 fork 路由

AgentTool.call() 是路由器而非单一执行函数。若同时有 team_name 与 name,走 spawnTeammate 返回 teammate 元数据。否则若 fork gate 开启且未指定 subagent_type,进入 FORK_AGENT 路径,共享父 renderedSystemPrompt 以保持 prompt cache。常规路径按 subagent_type 查 activeAgents,默认 general-purpose。

选定 agent 后,call 会继续做几类约束检查:

  • isAgentSwarmsEnabled — team_name 需 plan 支持
  • isInProcessTeammate — 禁止 nested teammate、background spawn
  • requiredMcpServers — poll pending MCP 最多 30s
  • effectiveIsolation remote — teleportToRemote(ant-only)
渲染图表中…

logEvent tengu_agent_tool_selected 记录 model、source、is_fork、is_async 等 telemetry,用来回放 Agent 选择路径。

源码引用: src/tools/AgentTool/AgentTool.tsx · 第 385–550 行(共 1835 行)

 385|   async call(
 386|     {
 387|       prompt,
 388|       subagent_type,
 389|       description,
 390|       model: modelParam,
 391|       run_in_background,
 392|       name,
 393|       team_name,
 394|       mode: spawnMode,
 395|       isolation,
 396|       cwd,
 397|     }: AgentToolInput,
 398|     toolUseContext,
 399|     canUseTool,
 400|     assistantMessage,
 401|     onProgress?,
 402|   ) {
 403|     const startTime = Date.now()
 404|     const model = isCoordinatorMode() ? undefined : modelParam
 405| 
 406|     // Get app state for permission mode and agent filtering
 407|     const appState = toolUseContext.getAppState()
 408|     const permissionMode = appState.toolPermissionContext.mode
 409|     // In-process teammates get a no-op setAppState; setAppStateForTasks
 410|     // reaches the root store so task registration/progress/kill stay visible.
 411|     const rootSetAppState =
 412|       toolUseContext.setAppStateForTasks ?? toolUseContext.setAppState
 413| 
 414|     // Check if user is trying to use agent teams without access
 415|     if (team_name && !isAgentSwarmsEnabled()) {
 416|       throw new Error('Agent Teams is not yet available on your plan.')
 417|     }
 418| 
 419|     // Teammates (in-process or tmux) passing `name` would trigger spawnTeammate()
 420|     // below, but TeamFile.members is a flat array with one leadAgentId — nested
 421|     // teammates land in the roster with no provenance and confuse the lead.
 422|     const teamName = resolveTeamName({ team_name }, appState)
 423|     if (isTeammate() && teamName && name) {
 424|       throw new Error(
 425|         'Teammates cannot spawn other teammates — the team roster is flat. To spawn a subagent instead, omit the `name` parameter.',
 426|       )
 427|     }
 428|     // In-process teammates cannot spawn background agents (their lifecycle is
 429|     // tied to the leader's process). Tmux teammates are separate processes and
 430|     // can manage their own background agents.
 431|     if (isInProcessTeammate() && teamName && run_in_background === true) {
 432|       throw new Error(
 433|         'In-process teammates cannot spawn background agents. Use run_in_background=false for synchronous subagents.',
 434|       )
 435|     }
 436| 
 437|     // Check if this is a multi-agent spawn request
 438|     // Spawn is triggered when team_name is set (from param or context) and name is provided
 439|     if (teamName && name) {
 440|       // Set agent definition color for grouped UI display before spawning
 441|       const agentDef = subagent_type
 442|         ? toolUseContext.options.agentDefinitions.activeAgents.find(
 443|             a => a.agentType === subagent_type,
 444|           )
 445|         : undefined
 446|       if (agentDef?.color) {
 447|         setAgentColor(subagent_type!, agentDef.color)
 448|       }
 449|       const result = await spawnTeammate(
 450|         {
 451|           name,
 452|           prompt,
 453|           description,
 454|           team_name: teamName,
 455|           use_splitpane: true,
 456|           plan_mode_required: spawnMode === 'plan',
 457|           model: model ?? agentDef?.model,
 458|           agent_type: subagent_type,
 459|           invokingRequestId: assistantMessage?.requestId,
 460|         },
 461|         toolUseContext,
 462|       )
 463| 
 464|       // Type assertion uses TeammateSpawnedOutput (defined above) instead of any.
 465|       // This type is excluded from the exported outputSchema for dead code elimination.
 466|       // Cast through unknown because TeammateSpawnedOutput is intentionally
 467|       // not part of the exported Output union (for dead code elimination purposes).
 468|       const spawnResult: TeammateSpawnedOutput = {
 469|         status: 'teammate_spawned' as const,
 470|         prompt,
 471|         ...result.data,
 472|       }
 473|       return { data: spawnResult } as unknown as { data: Output }
 474|     }
 475| 
 476|     // Fork subagent experiment routing:
 477|     // - subagent_type set: use it (explicit wins)
 478|     // - subagent_type omitted, gate on: fork path (undefined)
 479|     // - subagent_type omitted, gate off: default general-purpose
 480|     const effectiveType =
 481|       subagent_type ??
 482|       (isForkSubagentEnabled() ? undefined : GENERAL_PURPOSE_AGENT.agentType)
 483|     const isForkPath = effectiveType === undefined
 484| 
 485|     let selectedAgent: AgentDefinition
 486|     if (isForkPath) {
 487|       // Recursive fork guard: fork children keep the Agent tool in their
 488|       // pool for cache-identical tool defs, so reject fork attempts at call
 489|       // time. Primary check is querySource (compaction-resistant — set on
 490|       // context.options at spawn time, survives autocompact's message
 491|       // rewrite). Message-scan fallback catches any path where querySource
 492|       // wasn't threaded.
 493|       if (
 494|         toolUseContext.options.querySource ===
 495|           `agent:builtin:${FORK_AGENT.agentType}` ||
 496|         isInForkChild(toolUseContext.messages)
 497|       ) {
 498|         throw new Error(
 499|           'Fork is not available inside a forked worker. Complete your task directly using your tools.',
 500|         )
 501|       }
 502|       selectedAgent = FORK_AGENT
 503|     } else {
 504|       // Filter agents to exclude those denied via Agent(AgentName) syntax
 505|       const allAgents = toolUseContext.options.agentDefinitions.activeAgents
 506|       const { allowedAgentTypes } = toolUseContext.options.agentDefinitions
 507|       const agents = filterDeniedAgents(
 508|         // When allowedAgentTypes is set (from Agent(x,y) tool spec), restrict to those types
 509|         allowedAgentTypes
 510|           ? allAgents.filter(a => allowedAgentTypes.includes(a.agentType))
 511|           : allAgents,
 512|         appState.toolPermissionContext,
 513|         AGENT_TOOL_NAME,
 514|       )
 515| 
 516|       const found = agents.find(agent => agent.agentType === effectiveType)
 517|       if (!found) {
 518|         // Check if the agent exists but is denied by permission rules
 519|         const agentExistsButDenied = allAgents.find(
 520|           agent => agent.agentType === effectiveType,
 521|         )
 522|         if (agentExistsButDenied) {
 523|           const denyRule = getDenyRuleForAgent(
 524|             appState.toolPermissionContext,
 525|             AGENT_TOOL_NAME,
 526|             effectiveType,
 527|           )
 528|           throw new Error(
 529|             `Agent type '${effectiveType}' has been denied by permission rule '${AGENT_TOOL_NAME}(${effectiveType})' from ${denyRule?.source ?? 'settings'}.`,
 530|           )
 531|         }
 532|         throw new Error(
 533|           `Agent type '${effectiveType}' not found. Available agents: ${agents
 534|             .map(a => a.agentType)
 535|             .join(', ')}`,
 536|         )
 537|       }
 538|       selectedAgent = found
 539|     }
 540| 
 541|     // Same lifecycle constraint as the run_in_background guard above, but for
 542|     // agent definitions that force background via `background: true`. Checked
 543|     // here because selectedAgent is only now resolved.
 544|     if (
 545|       isInProcessTeammate() &&
 546|       teamName &&
 547|       selectedAgent.background === true
 548|     ) {
 549|       throw new Error(
 550|         `In-process teammates cannot spawn background agents. Agent '${selectedAgent.agentType}' has background: true in its definition.`,

call():MCP 等待与 isolation

requiredMcpServers 检查避免 race:agent 启动时 MCP 连接可能仍 pending,但 agent 定义已经要求这些 server 可用。

poll 500ms 间隔,deadline 30s。failed required server 提前 break;stillPending 清除后进入 hasRequiredMcpServers 校验。

worktree isolation: createAgentWorktree 创建临时 git worktree,子 agent cwd 隔离,完成后 removeAgentWorktree。

remote isolation: checkRemoteAgentEligibility → teleportToRemote → registerRemoteAgentTask,返回 remote_launched。

cwd override: 与 isolation worktree 互斥;绝对路径覆盖子 agent 工作目录。

源码引用: src/tools/AgentTool/AgentTool.tsx · 第 556–627 行(共 1835 行)

 556|     const requiredMcpServers = selectedAgent.requiredMcpServers
 557| 
 558|     // Check if required MCP servers have tools available
 559|     // A server that's connected but not authenticated won't have any tools
 560|     if (requiredMcpServers?.length) {
 561|       // If any required servers are still pending (connecting), wait for them
 562|       // before checking tool availability. This avoids a race condition where
 563|       // the agent is invoked before MCP servers finish connecting.
 564|       const hasPendingRequiredServers = appState.mcp.clients.some(
 565|         c =>
 566|           c.type === 'pending' &&
 567|           requiredMcpServers.some(pattern =>
 568|             c.name.toLowerCase().includes(pattern.toLowerCase()),
 569|           ),
 570|       )
 571| 
 572|       let currentAppState = appState
 573|       if (hasPendingRequiredServers) {
 574|         const MAX_WAIT_MS = 30_000
 575|         const POLL_INTERVAL_MS = 500
 576|         const deadline = Date.now() + MAX_WAIT_MS
 577| 
 578|         while (Date.now() < deadline) {
 579|           await sleep(POLL_INTERVAL_MS)
 580|           currentAppState = toolUseContext.getAppState()
 581| 
 582|           // Early exit: if any required server has already failed, no point
 583|           // waiting for other pending servers — the check will fail regardless.
 584|           const hasFailedRequiredServer = currentAppState.mcp.clients.some(
 585|             c =>
 586|               c.type === 'failed' &&
 587|               requiredMcpServers.some(pattern =>
 588|                 c.name.toLowerCase().includes(pattern.toLowerCase()),
 589|               ),
 590|           )
 591|           if (hasFailedRequiredServer) break
 592| 
 593|           const stillPending = currentAppState.mcp.clients.some(
 594|             c =>
 595|               c.type === 'pending' &&
 596|               requiredMcpServers.some(pattern =>
 597|                 c.name.toLowerCase().includes(pattern.toLowerCase()),
 598|               ),
 599|           )
 600|           if (!stillPending) break
 601|         }
 602|       }
 603| 
 604|       // Get servers that actually have tools (meaning they're connected AND authenticated)
 605|       const serversWithTools: string[] = []
 606|       for (const tool of currentAppState.mcp.tools) {
 607|         if (tool.name?.startsWith('mcp__')) {
 608|           // Extract server name from tool name (format: mcp__serverName__toolName)
 609|           const parts = tool.name.split('__')
 610|           const serverName = parts[1]
 611|           if (serverName && !serversWithTools.includes(serverName)) {
 612|             serversWithTools.push(serverName)
 613|           }
 614|         }
 615|       }
 616| 
 617|       if (!hasRequiredMcpServers(selectedAgent, serversWithTools)) {
 618|         const missing = requiredMcpServers.filter(
 619|           pattern =>
 620|             !serversWithTools.some(server =>
 621|               server.toLowerCase().includes(pattern.toLowerCase()),
 622|             ),
 623|         )
 624|         throw new Error(
 625|           `Agent '${selectedAgent.agentType}' requires MCP servers matching: ${missing.join(', ')}. ` +
 626|             `MCP servers with tools: ${serversWithTools.length > 0 ? serversWithTools.join(', ') : 'none'}. ` +
 627|             `Use /mcp to configure and authenticate the required MCP servers.`,

源码引用: src/tools/AgentTool/AgentTool.tsx · 第 430–450 行(共 1835 行)

 430|     // can manage their own background agents.
 431|     if (isInProcessTeammate() && teamName && run_in_background === true) {
 432|       throw new Error(
 433|         'In-process teammates cannot spawn background agents. Use run_in_background=false for synchronous subagents.',
 434|       )
 435|     }
 436| 
 437|     // Check if this is a multi-agent spawn request
 438|     // Spawn is triggered when team_name is set (from param or context) and name is provided
 439|     if (teamName && name) {
 440|       // Set agent definition color for grouped UI display before spawning
 441|       const agentDef = subagent_type
 442|         ? toolUseContext.options.agentDefinitions.activeAgents.find(
 443|             a => a.agentType === subagent_type,
 444|           )
 445|         : undefined
 446|       if (agentDef?.color) {
 447|         setAgentColor(subagent_type!, agentDef.color)
 448|       }
 449|       const result = await spawnTeammate(
 450|         {

runAgent:子 query 入口

runAgent.ts 是同步 Agent 执行核心:

  1. createSubagentContext — 克隆 ToolUseContext,设置 agentId、收窄 permission、clone readFileState
  2. filterToolsForAgent — 移除 DISALLOWED 工具;coordinator worker 用 ALLOWED 白名单
  3. assembleToolPool — 与 REPL 相同方式合并 MCP
  4. getSystemPrompt / enhanceSystemPromptWithEnvDetails — 子 agent 系统 prompt
  5. query(...) — 递归调用主 query 循环
  6. sidechain transcript — recordSidechainTranscript、writeAgentMetadata

registerFrontmatterHooks、executeSubagentStartHooks 在 query 前运行。abort 时 killShellTasksForAgent 清理 Bash 子任务。

Cache 优化: fork 路径使用 parent renderedSystemPrompt,避免 GrowthBook 冷启动 divergence bust cache。

源码引用: src/tools/AgentTool/runAgent.ts · 第 1–80 行(共 974 行)

   1| import { feature } from 'bun:bundle'
   2| import type { UUID } from 'crypto'
   3| import { randomUUID } from 'crypto'
   4| import uniqBy from 'lodash-es/uniqBy.js'
   5| import { logForDebugging } from 'src/utils/debug.js'
   6| import { getProjectRoot, getSessionId } from '../../bootstrap/state.js'
   7| import { getCommand, getSkillToolCommands, hasCommand } from '../../commands.js'
   8| import {
   9|   DEFAULT_AGENT_PROMPT,
  10|   enhanceSystemPromptWithEnvDetails,
  11| } from '../../constants/prompts.js'
  12| import type { QuerySource } from '../../constants/querySource.js'
  13| import { getSystemContext, getUserContext } from '../../context.js'
  14| import type { CanUseToolFn } from '../../hooks/useCanUseTool.js'
  15| import { query } from '../../query.js'
  16| import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'
  17| import { getDumpPromptsPath } from '../../services/api/dumpPrompts.js'
  18| import { cleanupAgentTracking } from '../../services/api/promptCacheBreakDetection.js'
  19| import {
  20|   connectToServer,
  21|   fetchToolsForClient,
  22| } from '../../services/mcp/client.js'
  23| import { getMcpConfigByName } from '../../services/mcp/config.js'
  24| import type {
  25|   MCPServerConnection,
  26|   ScopedMcpServerConfig,
  27| } from '../../services/mcp/types.js'
  28| import type { Tool, Tools, ToolUseContext } from '../../Tool.js'
  29| import { killShellTasksForAgent } from '../../tasks/LocalShellTask/killShellTasks.js'
  30| import type { Command } from '../../types/command.js'
  31| import type { AgentId } from '../../types/ids.js'
  32| import type {
  33|   AssistantMessage,
  34|   Message,
  35|   ProgressMessage,
  36|   RequestStartEvent,
  37|   StreamEvent,
  38|   SystemCompactBoundaryMessage,
  39|   TombstoneMessage,
  40|   ToolUseSummaryMessage,
  41|   UserMessage,
  42| } from '../../types/message.js'
  43| import { createAttachmentMessage } from '../../utils/attachments.js'
  44| import { AbortError } from '../../utils/errors.js'
  45| import { getDisplayPath } from '../../utils/file.js'
  46| import {
  47|   cloneFileStateCache,
  48|   createFileStateCacheWithSizeLimit,
  49|   READ_FILE_STATE_CACHE_SIZE,
  50| } from '../../utils/fileStateCache.js'
  51| import {
  52|   type CacheSafeParams,
  53|   createSubagentContext,
  54| } from '../../utils/forkedAgent.js'
  55| import { registerFrontmatterHooks } from '../../utils/hooks/registerFrontmatterHooks.js'
  56| import { clearSessionHooks } from '../../utils/hooks/sessionHooks.js'
  57| import { executeSubagentStartHooks } from '../../utils/hooks.js'
  58| import { createUserMessage } from '../../utils/messages.js'
  59| import { getAgentModel } from '../../utils/model/agent.js'
  60| import type { ModelAlias } from '../../utils/model/aliases.js'
  61| import {
  62|   clearAgentTranscriptSubdir,
  63|   recordSidechainTranscript,
  64|   setAgentTranscriptSubdir,
  65|   writeAgentMetadata,
  66| } from '../../utils/sessionStorage.js'
  67| import {
  68|   isRestrictedToPluginOnly,
  69|   isSourceAdminTrusted,
  70| } from '../../utils/settings/pluginOnlyPolicy.js'
  71| import {
  72|   asSystemPrompt,
  73|   type SystemPrompt,
  74| } from '../../utils/systemPromptType.js'
  75| import {
  76|   isPerfettoTracingEnabled,
  77|   registerAgent as registerPerfettoAgent,
  78|   unregisterAgent as unregisterPerfettoAgent,
  79| } from '../../utils/telemetry/perfettoTracing.js'
  80| import type { ContentReplacementState } from '../../utils/toolResultStorage.js'

sync vs async 输出

outputSchema union:

completed(sync): agentToolResultSchema + status completed + prompt 回显

async_launched: agentId、description、prompt、outputFile(getTaskOutputPath)、canReadOutputFile

触发 async:run_in_background=true、agent def background:true、getAutoBackgroundMs 超时、forceAsync 等。

async 路径 registerAsyncAgent / runAsyncAgentLifecycle,主线程立即返回;Completion 时 enqueueAgentNotification。TaskOutputTool 可读 outputFile 拉进度。

finalizeAgentTool 处理 handoff、classifyHandoffIfNeeded、token 统计上报。

源码引用: src/tools/AgentTool/AgentTool.tsx · 第 271–294 行(共 1835 行)

 271| export const outputSchema = lazySchema(() => {
 272|   const syncOutputSchema = agentToolResultSchema().extend({
 273|     status: z.literal('completed'),
 274|     prompt: z.string(),
 275|   })
 276| 
 277|   const asyncOutputSchema = z.object({
 278|     status: z.literal('async_launched'),
 279|     agentId: z.string().describe('The ID of the async agent'),
 280|     description: z.string().describe('The description of the task'),
 281|     prompt: z.string().describe('The prompt for the agent'),
 282|     outputFile: z
 283|       .string()
 284|       .describe('Path to the output file for checking agent progress'),
 285|     canReadOutputFile: z
 286|       .boolean()
 287|       .optional()
 288|       .describe(
 289|         'Whether the calling agent has Read/Bash tools to check progress',
 290|       ),
 291|   })
 292| 
 293|   return z.union([syncOutputSchema, asyncOutputSchema])
 294| })

源码引用: src/tools/AgentTool/agentToolUtils.ts · 第 1–50 行(共 687 行)

   1| import { feature } from 'bun:bundle'
   2| import { z } from 'zod/v4'
   3| import { clearInvokedSkillsForAgent } from '../../bootstrap/state.js'
   4| import {
   5|   ALL_AGENT_DISALLOWED_TOOLS,
   6|   ASYNC_AGENT_ALLOWED_TOOLS,
   7|   CUSTOM_AGENT_DISALLOWED_TOOLS,
   8|   IN_PROCESS_TEAMMATE_ALLOWED_TOOLS,
   9| } from '../../constants/tools.js'
  10| import { startAgentSummarization } from '../../services/AgentSummary/agentSummary.js'
  11| import {
  12|   type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  13|   logEvent,
  14| } from '../../services/analytics/index.js'
  15| import { clearDumpState } from '../../services/api/dumpPrompts.js'
  16| import type { AppState } from '../../state/AppState.js'
  17| import type {
  18|   Tool,
  19|   ToolPermissionContext,
  20|   Tools,
  21|   ToolUseContext,
  22| } from '../../Tool.js'
  23| import { toolMatchesName } from '../../Tool.js'
  24| import {
  25|   completeAgentTask as completeAsyncAgent,
  26|   createActivityDescriptionResolver,
  27|   createProgressTracker,
  28|   enqueueAgentNotification,
  29|   failAgentTask as failAsyncAgent,
  30|   getProgressUpdate,
  31|   getTokenCountFromTracker,
  32|   isLocalAgentTask,
  33|   killAsyncAgent,
  34|   type ProgressTracker,
  35|   updateAgentProgress as updateAsyncAgentProgress,
  36|   updateProgressFromMessage,
  37| } from '../../tasks/LocalAgentTask/LocalAgentTask.js'
  38| import { asAgentId } from '../../types/ids.js'
  39| import type { Message as MessageType } from '../../types/message.js'
  40| import { isAgentSwarmsEnabled } from '../../utils/agentSwarmsEnabled.js'
  41| import { logForDebugging } from '../../utils/debug.js'
  42| import { isInProtectedNamespace } from '../../utils/envUtils.js'
  43| import { AbortError, errorMessage } from '../../utils/errors.js'
  44| import type { CacheSafeParams } from '../../utils/forkedAgent.js'
  45| import { lazySchema } from '../../utils/lazySchema.js'
  46| import {
  47|   extractTextContent,
  48|   getLastAssistantMessage,
  49| } from '../../utils/messages.js'
  50| import type { PermissionMode } from '../../utils/permissions/PermissionMode.js'

与工具注册表的协作

AgentTool 在 getAllBaseTools 数组首位附近(AgentTool, TaskOutputTool, BashTool...)。

tools.ts 导出:

  • ALL_AGENT_DISALLOWED_TOOLS — 子 agent 禁止工具名集合
  • ASYNC_AGENT_ALLOWED_TOOLS — 后台 agent 白名单
  • COORDINATOR_MODE_ALLOWED_TOOLS — coordinator worker 工具

runAgent 过滤后传入 toolUseContext.options.tools,子 StreamingToolExecutor 使用同一池。

TaskOutputTool 与 Agent 配对:async agent 进度查询。TaskStopTool 终止后台 agent。

源码引用: src/tools.ts · 第 98–103 行(共 390 行)

  98| export {
  99|   ALL_AGENT_DISALLOWED_TOOLS,
 100|   CUSTOM_AGENT_DISALLOWED_TOOLS,
 101|   ASYNC_AGENT_ALLOWED_TOOLS,
 102|   COORDINATOR_MODE_ALLOWED_TOOLS,
 103| } from './constants/tools.js'

源码引用: src/tools.ts · 第 194–198 行(共 390 行)

 194|   return [
 195|     AgentTool,
 196|     TaskOutputTool,
 197|     BashTool,
 198|     // Ant-native builds have bfs/ugrep embedded in the bun binary (same ARGV0

源码目录

关联 utils/forkedAgent.ts、tasks/LocalAgentTask/、coordinator/coordinatorMode.ts。点击 AgentTool.tsx 跳回本章源码块。

动手练习

  1. 列出 ONE_SHOT_BUILTIN_AGENT_TYPES,理解为何 Explore 结果省略 agentId trailer
  2. grep createSubagentContext,画出子 agent 与父 agent 的 setAppState 差异
  3. 对比 sync Agent 与 run_in_background 的 transcript 消息形态
  4. 阅读 filterDeniedAgents,构造 Agent(Explore) deny 规则并预测 prompt 列表
  5. 追踪 fork 路径 querySource 设置点,验证 isInForkChild 拒绝逻辑

本章小结与延伸

AgentTool = 递归 query + 任务编排。执行调度仍经 StreamingToolExecutor;权限见 utils/permissions filterDeniedAgents。 继续学习:

  • tool-interface
  • streaming-executor
Prev
streaming-executor · 流式工具并发调度
Next
command-registry · commands.ts 注册与分派