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

本章总览

Agent Swarm 把多队友会话映射到 AppState 的 tasks、viewingAgentTaskId、teamContext 与 expandedView。本章以 teammateViewHelpers.ts 为核心,讲解 enter/exit/stop 三类 setState 转换;并串联 useSwarmInitialization(resume 恢复 team 文件)、useTeammateViewAutoExit(异常状态自动退出)、teamHelpers(磁盘 team 文件与 AppState.teamContext 对齐)。读完后应能解释 retain / evictAfter / diskLoaded 如何控制 transcript 内存与磁盘 bootstrap。

学完本章你应该能

  • 说明 enterTeammateView 的 switching 与 needsRetain 逻辑
  • 解释 release() 如何清 messages 并设置 evictAfter
  • 描述 stopOrDismissAgent 对 running vs terminal 的分支
  • 理解 useSwarmInitialization 的 resume vs fresh spawn 路径
  • 说出 teamContext 与 viewingAgentTaskId 的职责差异
  • 能在 REPL.tsx 找到 viewed local agent disk bootstrap effect

核心概念(先读懂这些)

viewingAgentTaskId 是 UI 视图指针

不是「当前活跃 agent」——活跃任务由 tasks 内 status 决定。viewingAgentTaskId 仅表示用户正在看哪条 task 的 transcript。输入路由由 selectors.getActiveAgentForInput 读取此指针 + task 类型。

retain 阻止 eviction 并触发 disk load

LocalAgentTask 默认 stub(无 messages)以省内存。enterTeammateView 对 local_agent 设 retain: true、清 evictAfter,REPL effect 见 needsBootstrap 时从 sessionStorage 拉 transcript。exitTeammateView 调用 release() 恢复 stub。

循环依赖与 inline type guard

teammateViewHelpers 刻意不 import LocalAgentTask 模块——会经 BackgroundTasksDialog 形成 cycle。isLocalAgent 内联 type === local_agent 检查;PANEL_GRACE_MS 与 framework.ts 数值同步注释。

建议学习步骤

  1. 阅读 release / enterTeammateView / exitTeammateView
  2. 阅读 stopOrDismissAgent abort vs dismiss
  3. 阅读 useTeammateViewAutoExit effect 条件
  4. 阅读 useSwarmInitialization resume 分支
  5. 浏览 teamHelpers TeamFile 结构与 readTeamFile
  6. 在 REPL 搜索 viewingAgentTaskId needsBootstrap

常见误区

注意

useTeammateViewAutoExit 用 taskExists 而非 viewedTask 判断 evict——local_agent 不会误退出

注意

completed teammate 不 auto-exit——用户可 review 全 transcript

注意

enterTeammateView 内 logEvent 无 PII,analytics 用 tengu_transcript_view_*

注意

teamContext.teammates 与 tasks 可能短暂不一致——以 tasks 为 UI 真相源

在架构中的位置

Teammate UI 状态机:

BackgroundTasksDialog 用户按 Enter 查看
  → enterTeammateView(taskId, setAppState)
  → viewingAgentTaskId = taskId, viewSelectionMode = viewing-agent
  → local_agent: retain=true → REPL disk bootstrap effect
  → 用户输入 → getActiveAgentForInput → viewed / named_agent
用户按 Esc / x
  → exitTeammateView 或 stopOrDismissAgent
  → release(): messages=undefined, evictAfter 可选
Swarm resume
  → useSwarmInitialization → initializeTeammateContextFromSession
  → setAppState teamContext + initializeTeammateHooks

AppState.teamContext 描述 swarm 成员元数据(tmux pane、cwd、color);tasks 持有运行时 TaskState(messages、status、abortController)。

release:stub 回收

release(task: LocalAgentTaskState) 共享逻辑:

  • retain: false — 允许 LRU eviction
  • messages: undefined — drop 内存 transcript
  • diskLoaded: false — 下次 view 重新 bootstrap
  • evictAfter — 若 status terminal,设为 Date.now() + PANEL_GRACE_MS(30s);否则 undefined

PANEL_GRACE_MS 与 BackgroundTasksDialog framework 一致——终端任务行在面板里短暂残留,避免「闪灭」。

release 不 abort running task——stopOrDismissAgent 负责 running 分支。

源码引用: src/state/teammateViewHelpers.ts · 第 23–38 行(共 142 行)

  23| /**
  24|  * Return the task released back to stub form: retain dropped, messages
  25|  * cleared, evictAfter set if terminal. Shared by exitTeammateView and
  26|  * the switch-away path in enterTeammateView.
  27|  */
  28| function release(task: LocalAgentTaskState): LocalAgentTaskState {
  29|   return {
  30|     ...task,
  31|     retain: false,
  32|     messages: undefined,
  33|     diskLoaded: false,
  34|     evictAfter: isTerminalTaskStatus(task.status)
  35|       ? Date.now() + PANEL_GRACE_MS
  36|       : undefined,
  37|   }
  38| }

enterTeammateView

参数:taskId、setAppState。

setState 内优化:

  1. switching — 从另一 retained local_agent 切走 → 对 prevId release
  2. needsRetain — 目标 task 是 local_agent 且未 retain 或有 evictAfter → 设 retain true
  3. needsView — viewingAgentTaskId 或 viewSelectionMode 变化
  4. 三者皆否 → return prev(Object.is bail-out)

返回新 state:viewingAgentTaskId、viewSelectionMode: 'viewing-agent'、可能更新的 tasks 浅拷贝。

logEvent('tengu_transcript_view_enter') analytics。

源码引用: src/state/teammateViewHelpers.ts · 第 40–81 行(共 142 行)

  40| /**
  41|  * Transitions the UI to view a teammate's transcript.
  42|  * Sets viewingAgentTaskId and, for local_agent, retain: true (blocks eviction,
  43|  * enables stream-append, triggers disk bootstrap) and clears evictAfter.
  44|  * If switching from another agent, releases the previous one back to stub.
  45|  */
  46| export function enterTeammateView(
  47|   taskId: string,
  48|   setAppState: (updater: (prev: AppState) => AppState) => void,
  49| ): void {
  50|   logEvent('tengu_transcript_view_enter', {})
  51|   setAppState(prev => {
  52|     const task = prev.tasks[taskId]
  53|     const prevId = prev.viewingAgentTaskId
  54|     const prevTask = prevId !== undefined ? prev.tasks[prevId] : undefined
  55|     const switching =
  56|       prevId !== undefined &&
  57|       prevId !== taskId &&
  58|       isLocalAgent(prevTask) &&
  59|       prevTask.retain
  60|     const needsRetain =
  61|       isLocalAgent(task) && (!task.retain || task.evictAfter !== undefined)
  62|     const needsView =
  63|       prev.viewingAgentTaskId !== taskId ||
  64|       prev.viewSelectionMode !== 'viewing-agent'
  65|     if (!needsRetain && !needsView && !switching) return prev
  66|     let tasks = prev.tasks
  67|     if (switching || needsRetain) {
  68|       tasks = { ...prev.tasks }
  69|       if (switching) tasks[prevId] = release(prevTask)
  70|       if (needsRetain) {
  71|         tasks[taskId] = { ...task, retain: true, evictAfter: undefined }
  72|       }
  73|     }
  74|     return {
  75|       ...prev,
  76|       viewingAgentTaskId: taskId,
  77|       viewSelectionMode: 'viewing-agent',
  78|       tasks,
  79|     }
  80|   })
  81| }

exitTeammateView 与 stopOrDismissAgent

exitTeammateView:

  • 清 viewingAgentTaskId、viewSelectionMode → none
  • 若当前 viewed task 是 retained local_agent → release 写回 tasks
  • 若 id undefined 但 mode 非 none,仍清 mode(防御)

stopOrDismissAgent(taskId) — 面板 x 键:

task.status行为
runningabortController.abort(),不改 tasks
terminalrelease + evictAfter=0 立即隐藏;若正在 view 则同时 exit

evictAfter=0 使 filter 立刻隐藏行;比 grace 更激进。

源码引用: src/state/teammateViewHelpers.ts · 第 83–109 行(共 142 行)

  83| /**
  84|  * Exit teammate transcript view and return to leader's view.
  85|  * Drops retain and clears messages back to stub form; if terminal,
  86|  * schedules eviction via evictAfter so the row lingers briefly.
  87|  */
  88| export function exitTeammateView(
  89|   setAppState: (updater: (prev: AppState) => AppState) => void,
  90| ): void {
  91|   logEvent('tengu_transcript_view_exit', {})
  92|   setAppState(prev => {
  93|     const id = prev.viewingAgentTaskId
  94|     const cleared = {
  95|       ...prev,
  96|       viewingAgentTaskId: undefined,
  97|       viewSelectionMode: 'none' as const,
  98|     }
  99|     if (id === undefined) {
 100|       return prev.viewSelectionMode === 'none' ? prev : cleared
 101|     }
 102|     const task = prev.tasks[id]
 103|     if (!isLocalAgent(task) || !task.retain) return cleared
 104|     return {
 105|       ...cleared,
 106|       tasks: { ...prev.tasks, [id]: release(task) },
 107|     }
 108|   })
 109| }

源码引用: src/state/teammateViewHelpers.ts · 第 111–141 行(共 142 行)

 111| /**
 112|  * Context-sensitive x: running → abort, terminal → dismiss.
 113|  * Dismiss sets evictAfter=0 so the filter hides immediately.
 114|  * If viewing the dismissed agent, also exits to leader.
 115|  */
 116| export function stopOrDismissAgent(
 117|   taskId: string,
 118|   setAppState: (updater: (prev: AppState) => AppState) => void,
 119| ): void {
 120|   setAppState(prev => {
 121|     const task = prev.tasks[taskId]
 122|     if (!isLocalAgent(task)) return prev
 123|     if (task.status === 'running') {
 124|       task.abortController?.abort()
 125|       return prev
 126|     }
 127|     if (task.evictAfter === 0) return prev
 128|     const viewingThis = prev.viewingAgentTaskId === taskId
 129|     return {
 130|       ...prev,
 131|       tasks: {
 132|         ...prev.tasks,
 133|         [taskId]: { ...release(task), evictAfter: 0 },
 134|       },
 135|       ...(viewingThis && {
 136|         viewingAgentTaskId: undefined,
 137|         viewSelectionMode: 'none',
 138|       }),
 139|     }
 140|   })
 141| }

useTeammateViewAutoExit

Hook 订阅:

  • viewingAgentTaskId
  • tasks[viewingAgentTaskId] 单 task 切片(非整 map——避免 streaming 重渲染)

effect 退出条件:

  1. task 从 map 消失(evict)→ exitTeammateView
  2. in-process teammate status killed/failed/有 error
  3. status 非 running/completed/pending 的异常态

不对 completed 退出——用户可读完 transcript。

local_agent:viewedTask 窄化为 undefined,status 检查跳过——不会误 exit。注释强调检查 raw taskExists。

源码引用: src/hooks/useTeammateViewAutoExit.ts · 第 6–63 行(共 64 行)

   6| /**
   7|  * Auto-exits teammate viewing mode when the viewed teammate
   8|  * is killed or encounters an error. Users stay viewing completed
   9|  * teammates so they can review the full transcript.
  10|  */
  11| export function useTeammateViewAutoExit(): void {
  12|   const setAppState = useSetAppState()
  13|   const viewingAgentTaskId = useAppState(s => s.viewingAgentTaskId)
  14|   // Select only the viewed task, not the full tasks map — otherwise every
  15|   // streaming update from any teammate re-renders this hook.
  16|   const task = useAppState(s =>
  17|     s.viewingAgentTaskId ? s.tasks[s.viewingAgentTaskId] : undefined,
  18|   )
  19| 
  20|   const viewedTask = task && isInProcessTeammateTask(task) ? task : undefined
  21|   const viewedStatus = viewedTask?.status
  22|   const viewedError = viewedTask?.error
  23|   const taskExists = task !== undefined
  24| 
  25|   useEffect(() => {
  26|     // Not viewing any teammate
  27|     if (!viewingAgentTaskId) {
  28|       return
  29|     }
  30| 
  31|     // Task no longer exists in the map — evicted out from under us.
  32|     // Check raw `task` not teammate-narrowed `viewedTask`; local_agent
  33|     // tasks exist but narrow to undefined, which would eject immediately.
  34|     if (!taskExists) {
  35|       exitTeammateView(setAppState)
  36|       return
  37|     }
  38|     // Status checks below are teammate-only (viewedTask is teammate-narrowed).
  39|     // For local_agent, viewedStatus is undefined → all checks falsy → no eject.
  40|     if (!viewedTask) return
  41| 
  42|     // Auto-exit if teammate is killed, stopped, has error, or is no longer running
  43|     // This handles shutdown scenarios where teammate becomes inactive
  44|     if (
  45|       viewedStatus === 'killed' ||
  46|       viewedStatus === 'failed' ||
  47|       viewedError ||
  48|       (viewedStatus !== 'running' &&
  49|         viewedStatus !== 'completed' &&
  50|         viewedStatus !== 'pending')
  51|     ) {
  52|       exitTeammateView(setAppState)
  53|       return
  54|     }
  55|   }, [
  56|     viewingAgentTaskId,
  57|     taskExists,
  58|     viewedTask,
  59|     viewedStatus,
  60|     viewedError,
  61|     setAppState,
  62|   ])
  63| }

useSwarmInitialization

条件:isAgentSwarmsEnabled() 且 enabled prop。

Resume 路径(initialMessages[0] 含 teamName + agentName):

  1. initializeTeammateContextFromSession(setAppState, teamName, agentName)
  2. readTeamFile(teamName) 找 member.agentId
  3. initializeTeammateHooks(setAppState, sessionId, { teamName, agentId, agentName })

Fresh spawn:

  • teamContext 已在 main.tsx computeInitialTeamContext 写入 initialState
  • 仅需 getDynamicTeamContext() + initializeTeammateHooks

依赖 [setAppState, initialMessages, enabled]——resume 只跑一次 mount effect。

源码引用: src/hooks/useSwarmInitialization.ts · 第 22–81 行(共 82 行)

  22| /**
  23|  * Hook that initializes swarm features when ENABLE_AGENT_SWARMS is true.
  24|  *
  25|  * Handles both:
  26|  * - Resumed teammate sessions (from --resume or /resume) where teamName/agentName
  27|  *   are stored in transcript messages
  28|  * - Fresh spawns where context is read from environment variables
  29|  */
  30| export function useSwarmInitialization(
  31|   setAppState: SetAppState,
  32|   initialMessages: Message[] | undefined,
  33|   { enabled = true }: { enabled?: boolean } = {},
  34| ): void {
  35|   useEffect(() => {
  36|     if (!enabled) return
  37|     if (isAgentSwarmsEnabled()) {
  38|       // Check if this is a resumed agent session (from --resume or /resume)
  39|       // Resumed sessions have teamName/agentName stored in transcript messages
  40|       const firstMessage = initialMessages?.[0]
  41|       const teamName =
  42|         firstMessage && 'teamName' in firstMessage
  43|           ? (firstMessage.teamName as string | undefined)
  44|           : undefined
  45|       const agentName =
  46|         firstMessage && 'agentName' in firstMessage
  47|           ? (firstMessage.agentName as string | undefined)
  48|           : undefined
  49| 
  50|       if (teamName && agentName) {
  51|         // Resumed agent session - set up team context from stored info
  52|         initializeTeammateContextFromSession(setAppState, teamName, agentName)
  53| 
  54|         // Get agentId from team file for hook initialization
  55|         const teamFile = readTeamFile(teamName)
  56|         const member = teamFile?.members.find(
  57|           (m: { name: string }) => m.name === agentName,
  58|         )
  59|         if (member) {
  60|           initializeTeammateHooks(setAppState, getSessionId(), {
  61|             teamName,
  62|             agentId: member.agentId,
  63|             agentName,
  64|           })
  65|         }
  66|       } else {
  67|         // Fresh spawn or standalone session
  68|         // teamContext is already computed in main.tsx via computeInitialTeamContext()
  69|         // and included in initialState, so we only need to initialize hooks here
  70|         const context = getDynamicTeamContext?.()
  71|         if (context?.teamName && context?.agentId && context?.agentName) {
  72|           initializeTeammateHooks(setAppState, getSessionId(), {
  73|             teamName: context.teamName,
  74|             agentId: context.agentId,
  75|             agentName: context.agentName,
  76|           })
  77|         }
  78|       }
  79|     }
  80|   }, [setAppState, initialMessages, enabled])
  81| }

teamHelpers 与 AppState.teamContext

TeamFile(磁盘 JSON)字段:name、leadAgentId、members[](agentId、tmuxPaneId、cwd、worktreePath、mode…)。

readTeamFile / writeTeamFile 在 ~/.claude/teams/(getTeamsDir)。

getSessionCreatedTeams(bootstrap)跟踪本 session TeamCreate 创建的团队,gracefulShutdown cleanup。

AppState teamContext 镜像运行时:

teamName, teamFilePath, leadAgentId
selfAgentId / selfAgentName / isLeader  // 队友进程身份
teammates: { [id]: { name, tmuxPaneId, cwd, ... } }

SendMessage、sandbox permission mailbox 读 teamContext.teamName 路由。tmux 后端 spawn 时 populate teammates 字典。

源码引用: src/utils/swarm/teamHelpers.ts · 第 64–90 行(共 684 行)

  64| export type TeamFile = {
  65|   name: string
  66|   description?: string
  67|   createdAt: number
  68|   leadAgentId: string
  69|   leadSessionId?: string // Actual session UUID of the leader (for discovery)
  70|   hiddenPaneIds?: string[] // Pane IDs that are currently hidden from the UI
  71|   teamAllowedPaths?: TeamAllowedPath[] // Paths all teammates can edit without asking
  72|   members: Array<{
  73|     agentId: string
  74|     name: string
  75|     agentType?: string
  76|     model?: string
  77|     prompt?: string
  78|     color?: string
  79|     planModeRequired?: boolean
  80|     joinedAt: number
  81|     tmuxPaneId: string
  82|     cwd: string
  83|     worktreePath?: string
  84|     sessionId?: string
  85|     subscriptions: string[]
  86|     backendType?: BackendType
  87|     isActive?: boolean // false when idle, undefined/true when active
  88|     mode?: PermissionMode // Current permission mode for this teammate
  89|   }>
  90| }

源码引用: src/state/AppStateStore.ts · 第 323–345 行(共 570 行)

 323|   teamContext?: {
 324|     teamName: string
 325|     teamFilePath: string
 326|     leadAgentId: string
 327|     // Self-identity for swarm members (separate processes in tmux panes)
 328|     // Note: This is different from toolUseContext.agentId which is for in-process subagents
 329|     selfAgentId?: string // Swarm member's own ID (same as leadAgentId for leaders)
 330|     selfAgentName?: string // Swarm member's name ('team-lead' for leaders)
 331|     isLeader?: boolean // True if this swarm member is the team leader
 332|     selfAgentColor?: string // Assigned color for UI (used by dynamically joined sessions)
 333|     teammates: {
 334|       [teammateId: string]: {
 335|         name: string
 336|         agentType?: string
 337|         color?: string
 338|         tmuxSessionName: string
 339|         tmuxPaneId: string
 340|         cwd: string
 341|         worktreePath?: string
 342|         spawnedAt: number
 343|       }
 344|     }
 345|   }

REPL 与 expandedView 联动

expandedView 取值 'none' | 'tasks' | 'teammates':

  • tasks → CoordinatorTaskPanel / todo 展开
  • teammates → spinner tree 队友树

onChangeAppState 把 expandedView 映射到 legacy globalConfig(showExpandedTodos、showSpinnerTree)。

showTeammateMessagePreview 可选字段,ENABLE_AGENT_SWARMS feature DCE。

coordinatorTaskIndex、selectedIPAgentIndex 在 AppState 而非 PromptInput local state——避免 footer 与 panel prop drilling。

调试「看不到队友消息」:查 teamContext 是否初始化、tasks 是否有对应 agentId、viewingAgentTaskId 是否挡在 leader 输入路由。

源码引用: src/state/AppStateStore.ts · 第 95–108 行(共 570 行)

  95|   expandedView: 'none' | 'tasks' | 'teammates'
  96|   isBriefOnly: boolean
  97|   // Optional - only present when ENABLE_AGENT_SWARMS is true (for dead code elimination)
  98|   showTeammateMessagePreview?: boolean
  99|   selectedIPAgentIndex: number
 100|   // CoordinatorTaskPanel selection: -1 = pill, 0 = main, 1..N = agent rows.
 101|   // AppState (not local) so the panel can read it directly without prop-drilling
 102|   // through PromptInput → PromptInputFooter.
 103|   coordinatorTaskIndex: number
 104|   viewSelectionMode: 'none' | 'selecting-agent' | 'viewing-agent'
 105|   // Which footer pill is focused (arrow-key navigation below the prompt).
 106|   // Lives in AppState so pill components rendered outside PromptInput
 107|   // (CompanionSprite in REPL.tsx) can read their own focused state.
 108|   footerSelection: FooterItem | null

本章小结与延伸

teammate-state = transcript 视图状态机 + swarm 初始化。下一章 state-boundaries,读 AppState 之外的 bootstrap 与持久化层。 继续学习:

  • state-boundaries
  • app-state-selectors
Prev
app-state-selectors · selectors 与 onChangeAppState
Next
state-boundaries · bootstrap、sessionStorage、FileStateCache