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

本章总览

PermissionRequest.tsx 是工具权限 UI 的统一入口:根据 tool 实例选择 Bash/FileEdit/WebFetch 等子组件,并处理系统通知、interrupt 快捷键与用户交互防 auto-approve。本章衔接 useCanUseTool,要求你能从 ask 分支追到具体 Permission 子目录。

学完本章你应该能

  • 解释 permissionComponentForTool 的 switch 映射表
  • 说明 ToolUseConfirm 回调字段各自触发的引擎行为
  • 描述 setStickyFooter 在全屏长计划场景的作用
  • 理解 BashPermissionRequest 与 classifier 动画的性能拆分
  • 能在 REPL 中定位 overlay 挂载条件

核心概念(先读懂这些)

门面模式:PermissionRequest 不渲染具体 UI

PermissionRequest 只做路由、通知、快捷键绑定,真正 JSX 在 BashPermissionRequest、FileEditPermissionRequest 等子目录。feature() 门控 ReviewArtifact、Workflow、Monitor 等 ant-only 工具。default 走 FallbackPermissionRequest,保证新工具未适配时仍有通用 Allow/Deny。

onUserInteraction 防 classifier 抢焦点

ToolUseConfirm.onUserInteraction 在用户按方向键、Tab、输入时调用,阻止 bash classifier 异步自动批准在用户阅读对话框时关闭弹窗。这是「自动化」与「可读性」之间的显式 Mutex。

建议学习步骤

  1. 阅读 permissionComponentForTool(源码块 A)
  2. 阅读 ToolUseConfirm 类型(源码块 B)
  3. 阅读 PermissionRequest 主函数(源码块 C)
  4. 打开 BashPermissionRequest 的 ClassifierCheckingSubtitle(源码块 D)
  5. 对照 REPL toolPermissionOverlay(源码块 E)

常见误区

注意

onDone 与 onReject 在 interrupt 时都会被调用,注意队列出队顺序

注意

Fallback 与专用组件 API 相同,但 diff 预览能力不同

注意

workerBadge 仅在 swarm worker 路径传入,本地 REPL 常为 undefined

权限 UI 全链路

hasPermissionsToUseTool() → ask
  → useCanUseTool push ToolUseConfirm
  → REPL toolUseConfirmQueue[0]
  → focusedInputDialog = 'tool-permission'
  → <PermissionRequest toolUseConfirm={...} />
  → permissionComponentForTool(tool)
  → BashPermissionRequest | FileEditPermissionRequest | ...
  → onAllow / onReject → resolve Promise → query 继续

components/permissions/ 约 53 文件,除请求组件外还有:

  • PermissionDialog.tsx、PermissionPrompt.tsx:共享对话框框架
  • rules/*:/permissions 设置界面的规则编辑
  • hooks.ts、utils.ts:日志与 unary 事件
  • SandboxPermissionRequest.tsx:网络 sandbox 独立队列(REPL 另一路径)

permissionComponentForTool 映射

下列 switch 以 Tool 单例引用(===)分支,而非字符串 name——重构 Tool 导出时必须同步更新。

Tool组件
FileEditToolFileEditPermissionRequest
FileWriteToolFileWritePermissionRequest
BashToolBashPermissionRequest
PowerShellToolPowerShellPermissionRequest
Glob/Grep/FileReadFilesystemPermissionRequest
ExitPlanModeV2ExitPlanModePermissionRequest
EnterPlanModeEnterPlanModePermissionRequest
defaultFallbackPermissionRequest

ReviewArtifact、Workflow、Monitor 通过 feature + require 动态加载,编译期可从 external 构建剔除。

源码引用: src/components/permissions/PermissionRequest.tsx · 第 47–82 行(共 233 行)

  47| const WorkflowTool = feature('WORKFLOW_SCRIPTS')
  48|   ? (
  49|       require('../../tools/WorkflowTool/WorkflowTool.js') as typeof import('../../tools/WorkflowTool/WorkflowTool.js')
  50|     ).WorkflowTool
  51|   : null
  52| 
  53| const WorkflowPermissionRequest = feature('WORKFLOW_SCRIPTS')
  54|   ? (
  55|       require('../../tools/WorkflowTool/WorkflowPermissionRequest.js') as typeof import('../../tools/WorkflowTool/WorkflowPermissionRequest.js')
  56|     ).WorkflowPermissionRequest
  57|   : null
  58| 
  59| const MonitorTool = feature('MONITOR_TOOL')
  60|   ? (
  61|       require('../../tools/MonitorTool/MonitorTool.js') as typeof import('../../tools/MonitorTool/MonitorTool.js')
  62|     ).MonitorTool
  63|   : null
  64| 
  65| const MonitorPermissionRequest = feature('MONITOR_TOOL')
  66|   ? (
  67|       require('./MonitorPermissionRequest/MonitorPermissionRequest.js') as typeof import('./MonitorPermissionRequest/MonitorPermissionRequest.js')
  68|     ).MonitorPermissionRequest
  69|   : null
  70| 
  71| import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs'
  72| /* eslint-enable @typescript-eslint/no-require-imports */
  73| import type { z } from 'zod/v4'
  74| import type { PermissionUpdate } from '../../utils/permissions/PermissionUpdateSchema.js'
  75| import type { WorkerBadgeProps } from './WorkerBadge.js'
  76| 
  77| function permissionComponentForTool(
  78|   tool: Tool,
  79| ): React.ComponentType<PermissionRequestProps> {
  80|   switch (tool) {
  81|     case FileEditTool:
  82|       return FileEditPermissionRequest

源码引用: src/components/permissions/PermissionRequest.tsx · 第 35–42 行(共 233 行)

  35| const ReviewArtifactTool = feature('REVIEW_ARTIFACT')
  36|   ? (
  37|       require('../../tools/ReviewArtifactTool/ReviewArtifactTool.js') as typeof import('../../tools/ReviewArtifactTool/ReviewArtifactTool.js')
  38|     ).ReviewArtifactTool
  39|   : null
  40| 
  41| const ReviewArtifactPermissionRequest = feature('REVIEW_ARTIFACT')
  42|   ? (

ToolUseConfirm 数据结构

ToolUseConfirm 是队列元素,连接引擎与 UI:

  • assistantMessage / tool / input / toolUseID:渲染与回调标识
  • permissionResult:含 behavior 与 rules 提示,供 PermissionRuleExplanation 展示
  • onAllow(updatedInput, permissionUpdates, feedback, contentBlocks):可改写 input(如用户编辑命令)并持久化 allow 规则
  • onReject(feedback?, contentBlocks?):拒绝并可带用户说明
  • recheckPermission():classifier 完成后重新求值
  • classifierCheckInProgress / classifierAutoApproved:UI 展示自动批准进度

PermissionRequestProps 额外接收 toolUseContext、verbose、workerBadge、setStickyFooter。

源码引用: src/components/permissions/PermissionRequest.tsx · 第 83–127 行(共 233 行)

  83|     case FileWriteTool:
  84|       return FileWritePermissionRequest
  85|     case BashTool:
  86|       return BashPermissionRequest
  87|     case PowerShellTool:
  88|       return PowerShellPermissionRequest
  89|     case ReviewArtifactTool:
  90|       return ReviewArtifactPermissionRequest ?? FallbackPermissionRequest
  91|     case WebFetchTool:
  92|       return WebFetchPermissionRequest
  93|     case NotebookEditTool:
  94|       return NotebookEditPermissionRequest
  95|     case ExitPlanModeV2Tool:
  96|       return ExitPlanModePermissionRequest
  97|     case EnterPlanModeTool:
  98|       return EnterPlanModePermissionRequest
  99|     case SkillTool:
 100|       return SkillPermissionRequest
 101|     case AskUserQuestionTool:
 102|       return AskUserQuestionPermissionRequest
 103|     case WorkflowTool:
 104|       return WorkflowPermissionRequest ?? FallbackPermissionRequest
 105|     case MonitorTool:
 106|       return MonitorPermissionRequest ?? FallbackPermissionRequest
 107|     case GlobTool:
 108|     case GrepTool:
 109|     case FileReadTool:
 110|       return FilesystemPermissionRequest
 111|     default:
 112|       return FallbackPermissionRequest
 113|   }
 114| }
 115| 
 116| export type PermissionRequestProps<Input extends AnyObject = AnyObject> = {
 117|   toolUseConfirm: ToolUseConfirm<Input>
 118|   toolUseContext: ToolUseContext
 119|   onDone(): void
 120|   onReject(): void
 121|   verbose: boolean
 122|   workerBadge: WorkerBadgeProps | undefined
 123|   /**
 124|    * Register JSX to render in a sticky footer below the scrollable area.
 125|    * Fullscreen mode only (non-fullscreen has no sticky area — terminal
 126|    * scrollback moves everything together). Call with null to clear.
 127|    *

PermissionRequest 主组件行为

主函数职责极简但关键:

  1. app:interrupt 键:串联 onDone、onReject、toolUseConfirm.onReject(Esc/Ctrl+C 统一拒绝)
  2. getNotificationMessage:生成 OS 通知文案(Plan 模式、ReviewArtifact 有特殊句)
  3. useNotifyAfterTimeout:长时间未操作提醒用户
  4. 渲染 &lt;PermissionComponent ... /&gt;

TODO 注释提到未来可能下放到 Tool.renderPermissionRequest,当前集中 switch 便于审计所有工具的权限 UI。

源码引用: src/components/permissions/PermissionRequest.tsx · 第 128–216 行(共 233 行)

 128|    * Used by ExitPlanModePermissionRequest to keep response options visible
 129|    * while the user scrolls through a long plan. The callback is stable —
 130|    * JSX passed should use refs for callbacks that close over component state
 131|    * to avoid stale closures (React reconciles the JSX, preserving Select's
 132|    * internal focus/input state).
 133|    */
 134|   setStickyFooter?: (jsx: React.ReactNode | null) => void
 135| }
 136| 
 137| export type ToolUseConfirm<Input extends AnyObject = AnyObject> = {
 138|   assistantMessage: AssistantMessage
 139|   tool: Tool<Input>
 140|   description: string
 141|   input: z.infer<Input>
 142|   toolUseContext: ToolUseContext
 143|   toolUseID: string
 144|   permissionResult: PermissionDecision
 145|   permissionPromptStartTimeMs: number
 146|   /**
 147|    * Called when user interacts with the permission dialog (e.g., arrow keys, tab, typing).
 148|    * This prevents async auto-approval mechanisms (like the bash classifier) from
 149|    * dismissing the dialog while the user is actively engaging with it.
 150|    */
 151|   classifierCheckInProgress?: boolean
 152|   classifierAutoApproved?: boolean
 153|   classifierMatchedRule?: string
 154|   workerBadge?: WorkerBadgeProps
 155|   onUserInteraction(): void
 156|   onAbort(): void
 157|   onDismissCheckmark?(): void
 158|   onAllow(
 159|     updatedInput: z.infer<Input>,
 160|     permissionUpdates: PermissionUpdate[],
 161|     feedback?: string,
 162|     contentBlocks?: ContentBlockParam[],
 163|   ): void
 164|   onReject(feedback?: string, contentBlocks?: ContentBlockParam[]): void
 165|   recheckPermission(): Promise<void>
 166| }
 167| 
 168| function getNotificationMessage(toolUseConfirm: ToolUseConfirm): string {
 169|   const toolName = toolUseConfirm.tool.userFacingName(
 170|     toolUseConfirm.input as never,
 171|   )
 172| 
 173|   if (toolUseConfirm.tool === ExitPlanModeV2Tool) {
 174|     return 'Claude Code needs your approval for the plan'
 175|   }
 176| 
 177|   if (toolUseConfirm.tool === EnterPlanModeTool) {
 178|     return 'Claude Code wants to enter plan mode'
 179|   }
 180| 
 181|   if (
 182|     feature('REVIEW_ARTIFACT') &&
 183|     toolUseConfirm.tool === ReviewArtifactTool
 184|   ) {
 185|     return 'Claude needs your approval for a review artifact'
 186|   }
 187| 
 188|   if (!toolName || toolName.trim() === '') {
 189|     return 'Claude Code needs your attention'
 190|   }
 191| 
 192|   return `Claude needs your permission to use ${toolName}`
 193| }
 194| 
 195| // TODO: Move this to Tool.renderPermissionRequest
 196| export function PermissionRequest({
 197|   toolUseConfirm,
 198|   toolUseContext,
 199|   onDone,
 200|   onReject,
 201|   verbose,
 202|   workerBadge,
 203|   setStickyFooter,
 204| }: PermissionRequestProps): React.ReactNode {
 205|   // Handle Ctrl+C (app:interrupt) to reject
 206|   useKeybinding(
 207|     'app:interrupt',
 208|     () => {
 209|       onDone()
 210|       onReject()
 211|       toolUseConfirm.onReject()
 212|     },
 213|     { context: 'Confirmation' },
 214|   )
 215| 
 216|   const notificationMessage = getNotificationMessage(toolUseConfirm)

getNotificationMessage 规则

通知字符串影响 macOS/Windows 横幅与终端 bell:

  • ExitPlanModeV2 → 「needs your approval for the plan」
  • EnterPlanMode → 「wants to enter plan mode」
  • ReviewArtifact feature → 专用文案
  • 空 toolName → 通用 「needs your attention」
  • 默认 → 「permission to use {toolName}」

与 BashPermissionRequest 内的 destructive warning、sandbox 选项正交——通知只负责把人拉回终端。

源码引用: src/components/permissions/PermissionRequest.tsx · 第 128–143 行(共 233 行)

 128|    * Used by ExitPlanModePermissionRequest to keep response options visible
 129|    * while the user scrolls through a long plan. The callback is stable —
 130|    * JSX passed should use refs for callbacks that close over component state
 131|    * to avoid stale closures (React reconciles the JSX, preserving Select's
 132|    * internal focus/input state).
 133|    */
 134|   setStickyFooter?: (jsx: React.ReactNode | null) => void
 135| }
 136| 
 137| export type ToolUseConfirm<Input extends AnyObject = AnyObject> = {
 138|   assistantMessage: AssistantMessage
 139|   tool: Tool<Input>
 140|   description: string
 141|   input: z.infer<Input>
 142|   toolUseContext: ToolUseContext
 143|   toolUseID: string

BashPermissionRequest 与 classifier 性能

Bash 权限是最复杂的子 UI 之一(约 480 行)。ClassifierCheckingSubtitle 单独抽离 shimmer 动画:

  • 原把 useShimmerAnimation 放在 535 行 Inner 内,20fps 时钟导致整窗重绘
  • 抽离后仅 subtitle 重绘,PermissionDialog + Select 保持稳定
  • CHECKING_TEXT = 「Attempting to auto-approve…」

Inner 还集成:destructiveCommandWarning、sed 编辑解析、sandbox Select、PermissionRuleExplanation、bashToolUseOptions 生成的 Allow 列表。

源码引用: src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx · 第 34–60 行(共 613 行)

  34| import { type UnaryEvent, usePermissionRequestLogging } from '../hooks.js'
  35| import { PermissionDecisionDebugInfo } from '../PermissionDecisionDebugInfo.js'
  36| import { PermissionDialog } from '../PermissionDialog.js'
  37| import {
  38|   PermissionExplainerContent,
  39|   usePermissionExplainerUI,
  40| } from '../PermissionExplanation.js'
  41| import type { PermissionRequestProps } from '../PermissionRequest.js'
  42| import { PermissionRuleExplanation } from '../PermissionRuleExplanation.js'
  43| import { SedEditPermissionRequest } from '../SedEditPermissionRequest/SedEditPermissionRequest.js'
  44| import { useShellPermissionFeedback } from '../useShellPermissionFeedback.js'
  45| import { logUnaryPermissionEvent } from '../utils.js'
  46| import { bashToolUseOptions } from './bashToolUseOptions.js'
  47| 
  48| const CHECKING_TEXT = 'Attempting to auto-approve\u2026'
  49| 
  50| // Isolates the 20fps shimmer clock from BashPermissionRequestInner. Before this
  51| // extraction, useShimmerAnimation lived inside the 535-line Inner body, so every
  52| // 50ms clock tick re-rendered the entire dialog (PermissionDialog + Select +
  53| // all children) for the ~1-3 seconds the classifier typically takes. Inner also
  54| // has a Compiler bailout (see below), so nothing was auto-memoized — the full
  55| // JSX tree was reconstructed 20-60 times per classifier check.
  56| function ClassifierCheckingSubtitle(): React.ReactNode {
  57|   const [ref, glimmerIndex] = useShimmerAnimation(
  58|     'requesting',
  59|     CHECKING_TEXT,
  60|     false,

源码引用: src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx · 第 1–33 行(共 613 行)

   1| import { feature } from 'bun:bundle'
   2| import figures from 'figures'
   3| import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
   4| import { Box, Text, useTheme } from '../../../ink.js'
   5| import { useKeybinding } from '../../../keybindings/useKeybinding.js'
   6| import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../../services/analytics/growthbook.js'
   7| import {
   8|   type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
   9|   logEvent,
  10| } from '../../../services/analytics/index.js'
  11| import { sanitizeToolNameForAnalytics } from '../../../services/analytics/metadata.js'
  12| import { useAppState } from '../../../state/AppState.js'
  13| import { BashTool } from '../../../tools/BashTool/BashTool.js'
  14| import {
  15|   getFirstWordPrefix,
  16|   getSimpleCommandPrefix,
  17| } from '../../../tools/BashTool/bashPermissions.js'
  18| import { getDestructiveCommandWarning } from '../../../tools/BashTool/destructiveCommandWarning.js'
  19| import { parseSedEditCommand } from '../../../tools/BashTool/sedEditParser.js'
  20| import { shouldUseSandbox } from '../../../tools/BashTool/shouldUseSandbox.js'
  21| import { getCompoundCommandPrefixesStatic } from '../../../utils/bash/prefix.js'
  22| import {
  23|   createPromptRuleContent,
  24|   generateGenericDescription,
  25|   getBashPromptAllowDescriptions,
  26|   isClassifierPermissionsEnabled,
  27| } from '../../../utils/permissions/bashClassifier.js'
  28| import { extractRules } from '../../../utils/permissions/PermissionUpdate.js'
  29| import type { PermissionUpdate } from '../../../utils/permissions/PermissionUpdateSchema.js'
  30| import { SandboxManager } from '../../../utils/sandbox/sandbox-adapter.js'
  31| import { Select } from '../../CustomSelect/select.js'
  32| import { ShimmerChar } from '../../Spinner/ShimmerChar.js'
  33| import { useShimmerAnimation } from '../../Spinner/useShimmerAnimation.js'

FileEdit 与 Plan 模式专用组件

FileEditPermissionRequest / FileWritePermissionRequest:展示 diff(FileWriteToolDiff、NotebookEditToolDiff),支持 acceptEdits 模式下的简化批准。

ExitPlanModePermissionRequest:使用 setStickyFooter 把响应选项固定在 FullscreenLayout bottom,用户滚动长 plan 时仍可按 1/2/3 选择。

FilesystemPermissionRequest:统一处理 Glob/Grep/Read 的 path 展示。

AskUserQuestionPermissionRequest:渲染 PreviewBox,与 AskUserQuestionTool 输入 schema 对齐。

阅读这些文件时共用 PermissionDialog + Select + usePermissionRequestLogging 模式,日志事件名在 hooks.ts 统一。

源码引用: src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx · 第 1–40 行(共 80 行)

   1| import { basename, relative } from 'path'
   2| import React from 'react'
   3| import { FileEditToolDiff } from 'src/components/FileEditToolDiff.js'
   4| import { getCwd } from 'src/utils/cwd.js'
   5| import type { z } from 'zod/v4'
   6| import { Text } from '../../../ink.js'
   7| import { FileEditTool } from '../../../tools/FileEditTool/FileEditTool.js'
   8| import { FilePermissionDialog } from '../FilePermissionDialog/FilePermissionDialog.js'
   9| import {
  10|   createSingleEditDiffConfig,
  11|   type FileEdit,
  12|   type IDEDiffSupport,
  13| } from '../FilePermissionDialog/ideDiffConfig.js'
  14| import type { PermissionRequestProps } from '../PermissionRequest.js'
  15| 
  16| type FileEditInput = z.infer<typeof FileEditTool.inputSchema>
  17| 
  18| const ideDiffSupport: IDEDiffSupport<FileEditInput> = {
  19|   getConfig: (input: FileEditInput) =>
  20|     createSingleEditDiffConfig(
  21|       input.file_path,
  22|       input.old_string,
  23|       input.new_string,
  24|       input.replace_all,
  25|     ),
  26|   applyChanges: (input: FileEditInput, modifiedEdits: FileEdit[]) => {
  27|     const firstEdit = modifiedEdits[0]
  28|     if (firstEdit) {
  29|       return {
  30|         ...input,
  31|         old_string: firstEdit.old_string,
  32|         new_string: firstEdit.new_string,
  33|         replace_all: firstEdit.replace_all,
  34|       }
  35|     }
  36|     return input
  37|   },
  38| }
  39| 
  40| export function FileEditPermissionRequest(

源码引用: src/components/permissions/PermissionDialog.tsx · 第 1–35 行(共 55 行)

   1| import * as React from 'react'
   2| import { Box } from '../../ink.js'
   3| import type { Theme } from '../../utils/theme.js'
   4| import { PermissionRequestTitle } from './PermissionRequestTitle.js'
   5| import type { WorkerBadgeProps } from './WorkerBadge.js'
   6| 
   7| type Props = {
   8|   title: string
   9|   subtitle?: React.ReactNode
  10|   color?: keyof Theme
  11|   titleColor?: keyof Theme
  12|   innerPaddingX?: number
  13|   workerBadge?: WorkerBadgeProps
  14|   titleRight?: React.ReactNode
  15|   children: React.ReactNode
  16| }
  17| 
  18| export function PermissionDialog({
  19|   title,
  20|   subtitle,
  21|   color = 'permission',
  22|   titleColor,
  23|   innerPaddingX = 1,
  24|   workerBadge,
  25|   titleRight,
  26|   children,
  27| }: Props): React.ReactNode {
  28|   return (
  29|     <Box
  30|       flexDirection="column"
  31|       borderStyle="round"
  32|       borderColor={color}
  33|       borderLeft={false}
  34|       borderRight={false}
  35|       borderBottom={false}

REPL 如何挂载 PermissionRequest

REPL 4519 行:

focusedInputDialog === 'tool-permission' 时才渲染 overlay。

  • key=toolUseID 保证队列切换时 remount 干净状态
  • onDone 出队:setToolUseConfirmQueue(([_, ...tail]) =&gt; tail)
  • getToolUseContext(...) 传入最新 messages 与 abortController
  • 全屏时 setStickyFooter 传入 ExitPlan 等组件

ScrollKeybindingHandler 在 tool-permission 时仍 active,允许滚动背后 transcript——设计意图是用户可对照历史命令再批准。

源码引用: src/screens/REPL.tsx · 第 4519–4519 行(共 7050 行)

4519|         }

源码引用: src/screens/REPL.tsx · 第 4561–4561 行(共 7050 行)

4561|       }

rules/ 与设置界面

permissions/rules/ 下的 AddPermissionRules、PermissionRuleList、WorkspaceTab 等服务于 /permissions 斜杠命令,与单次 ToolUseConfirm 不同生命周期。

调试「为何总是 ask」时:

  1. utils/permissions/permissions.ts 看规则求值
  2. useCanUseTool 看是否入队
  3. PermissionRequest 看是否映射到正确子组件
  4. rules/ 看用户是否配置了 deny/allow 模式

WorkerPendingPermission、WorkerBadge 处理 swarm worker 发起、leader 批准的路径,Mailbox 与 REPL 队列桥接。

源码引用: src/components/permissions/rules/PermissionRuleList.tsx · 第 1–30 行(共 800 行)

   1| import chalk from 'chalk'
   2| import figures from 'figures'
   3| import * as React from 'react'
   4| import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
   5| import { useAppState, useSetAppState } from 'src/state/AppState.js'
   6| import {
   7|   applyPermissionUpdate,
   8|   persistPermissionUpdate,
   9| } from 'src/utils/permissions/PermissionUpdate.js'
  10| import type { PermissionUpdateDestination } from 'src/utils/permissions/PermissionUpdateSchema.js'
  11| import type { CommandResultDisplay } from '../../../commands.js'
  12| import { Select } from '../../../components/CustomSelect/select.js'
  13| import { useExitOnCtrlCDWithKeybindings } from '../../../hooks/useExitOnCtrlCDWithKeybindings.js'
  14| import { useSearchInput } from '../../../hooks/useSearchInput.js'
  15| import type { KeyboardEvent } from '../../../ink/events/keyboard-event.js'
  16| import { Box, Text, useTerminalFocus } from '../../../ink.js'
  17| import { useKeybinding } from '../../../keybindings/useKeybinding.js'
  18| import {
  19|   type AutoModeDenial,
  20|   getAutoModeDenials,
  21| } from '../../../utils/autoModeDenials.js'
  22| import type {
  23|   PermissionBehavior,
  24|   PermissionRule,
  25|   PermissionRuleValue,
  26| } from '../../../utils/permissions/PermissionRule.js'
  27| import { permissionRuleValueToString } from '../../../utils/permissions/permissionRuleParser.js'
  28| import {
  29|   deletePermissionRule,
  30|   getAllowRules,

源码引用: src/components/permissions/WorkerBadge.tsx · 第 1–25 行(共 28 行)

   1| import * as React from 'react'
   2| import { BLACK_CIRCLE } from '../../constants/figures.js'
   3| import { Box, Text } from '../../ink.js'
   4| import { toInkColor } from '../../utils/ink.js'
   5| 
   6| export type WorkerBadgeProps = {
   7|   name: string
   8|   color: string
   9| }
  10| 
  11| /**
  12|  * Renders a colored badge showing the worker's name for permission prompts.
  13|  * Used to indicate which swarm worker is requesting the permission.
  14|  */
  15| export function WorkerBadge({
  16|   name,
  17|   color,
  18| }: WorkerBadgeProps): React.ReactNode {
  19|   const inkColor = toInkColor(color)
  20|   return (
  21|     <Box flexDirection="row" gap={1}>
  22|       <Text color={inkColor}>
  23|         {BLACK_CIRCLE} <Text bold>@{name}</Text>
  24|       </Text>
  25|     </Box>

源码目录

展开 BashPermissionRequest/、FileEditPermissionRequest/ 等子目录对照本页表格。

动手练习

  1. 触发 FileEdit 确认,观察 diff 组件与 Fallback 的差异
  2. 在 classifier 等待时注意 subtitle shimmer 是否独立闪烁
  3. 按 interrupt 绑定,确认队列出队且 transcript 出现 reject 结果
  4. 打开 /permissions,对照 rules/ 与 PermissionRuleExplanation 文案来源

本章小结与延伸

PermissionRequest = 权限 UI 路由器。策略求值在 utils/permissions;队列在 useCanUseTool;展示在各 *PermissionRequest 子目录。 继续学习:

  • components 总览
  • useCanUseTool
  • REPL 主屏
Prev
messages · 消息行渲染
Next
PromptInput · 底部输入