本章总览
先抓主轴:tool-permission-types 描述了“命令如何触发工具、工具如何受权限约束、执行过程如何反馈进度”的类型合同。围绕这条主轴,types/command.ts 管命令形态,types/permissions.ts 管决策与更新协议,types/tools.ts 管统一进度载荷。
学完本章你应该能
- 说明 permissions.ts 打破循环依赖的动机
- 列举 ExternalPermissionMode 与 InternalPermissionMode 差异
- 理解 PermissionRule、PermissionUpdate 判别联合
- 区分 PromptCommand 与 LocalJSXCommand 的 load/call 模式
- 知道 ToolProgressData 为何统一用 kind + index signature
- 能 trace LocalJSXCommandContext 与 ToolUseContext 的扩展关系
核心概念(先读懂这些)
permissions.ts 是类型层的安全阀
文件头注释明确:仅类型与无 runtime 依赖的常量。EXTERNAL_PERMISSION_MODES、INTERNAL_PERMISSION_MODES 用 feature gate 注入 auto 模式。utils/permissions/* 实现 import 此文件,hooks 与 state 引用 PermissionMode 而不拉入 PermissionRule 求值逻辑。
Command 三种 type 字面量
prompt:技能/插件提供的模型侧命令,getPromptForCommand 返回 ContentBlockParam[]。local:纯 TS 函数,返回 LocalCommandResult(text/compact/skip)。local-jsx:Ink Dialog,load() 懒加载,call(onDone, context, args) 返回 ReactNode。LocalJSXCommandOnDone 的 shouldQuery、metaMessages 控制命令后是否继续 query。
Tool progress 类型是标记联合的松散版
BashProgress、MCPProgress、AgentToolProgress 等均 extends ToolProgressData。kind 字段供 UI 选择渲染器;其余字段 unknown 扩展,避免每加一个 Tool 就改中央联合。streaming executor 在运行时 narrow kind。
建议学习步骤
- 阅读 permissions.ts PERMISSION_MODES 与 PermissionBehavior
- 浏览 PermissionUpdate 五种 operation 变体
- 阅读 command.ts PromptCommand 与 fork/inline context
- 对照 LocalJSXCommandContext 的 setMessages、resume 字段
- 查看 tools.ts 全部 Progress 别名
- 在 commands/registry 搜索 Command union 窄化
常见误区
注意
PermissionUpdateDestination 与 PermissionRuleSource 相似但不等价
注意
LocalCommandResult compact 变体携带 CompactionResult,勿与 /compact 命令混淆
注意
PromptCommand paths glob 仅影响可见性,不改变 type
注意
WorkingDirectorySource 注释说明未来可能与 RuleSource 分叉
permissions.ts 结构
文件分块:Modes → Behaviors → Rules → Updates → Decisions → Context。
PermissionMode = ExternalPermissionMode | 'auto' | 'bubble'。用户可设 defaultMode 的运行时集合是 INTERNAL_PERMISSION_MODES,TRANSCRIPT_CLASSIFIER feature 开启时含 auto。
PermissionRule 三元组:source(userSettings/projectSettings/.../session)、ruleBehavior(allow/deny/ask)、ruleValue(toolName + 可选 ruleContent)。
PermissionUpdate 判别联合支持 addRules、replaceRules、removeRules、setMode、addDirectories、removeDirectories——Permission UI 与 hooks 的 PermissionRequest 回调共用此形状。
源码引用: src/types/permissions.ts · 第 16–38 行(共 442 行)
16| export const EXTERNAL_PERMISSION_MODES = [
17| 'acceptEdits',
18| 'bypassPermissions',
19| 'default',
20| 'dontAsk',
21| 'plan',
22| ] as const
23|
24| export type ExternalPermissionMode = (typeof EXTERNAL_PERMISSION_MODES)[number]
25|
26| // Exhaustive mode union for typechecking. The user-addressable runtime set
27| // is INTERNAL_PERMISSION_MODES below.
28| export type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'
29| export type PermissionMode = InternalPermissionMode
30|
31| // Runtime validation set: modes that are user-addressable (settings.json
32| // defaultMode, --permission-mode CLI flag, conversation recovery).
33| export const INTERNAL_PERMISSION_MODES = [
34| ...EXTERNAL_PERMISSION_MODES,
35| ...(feature('TRANSCRIPT_CLASSIFIER') ? (['auto'] as const) : ([] as const)),
36| ] as const satisfies readonly PermissionMode[]
37|
38| export const PERMISSION_MODES = INTERNAL_PERMISSION_MODES
源码引用: src/types/permissions.ts · 第 54–79 行(共 442 行)
54| export type PermissionRuleSource =
55| | 'userSettings'
56| | 'projectSettings'
57| | 'localSettings'
58| | 'flagSettings'
59| | 'policySettings'
60| | 'cliArg'
61| | 'command'
62| | 'session'
63|
64| /**
65| * The value of a permission rule - specifies which tool and optional content
66| */
67| export type PermissionRuleValue = {
68| toolName: string
69| ruleContent?: string
70| }
71|
72| /**
73| * A permission rule with its source and behavior
74| */
75| export type PermissionRule = {
76| source: PermissionRuleSource
77| ruleBehavior: PermissionBehavior
78| ruleValue: PermissionRuleValue
79| }
源码引用: src/types/permissions.ts · 第 98–131 行(共 442 行)
98| export type PermissionUpdate =
99| | {
100| type: 'addRules'
101| destination: PermissionUpdateDestination
102| rules: PermissionRuleValue[]
103| behavior: PermissionBehavior
104| }
105| | {
106| type: 'replaceRules'
107| destination: PermissionUpdateDestination
108| rules: PermissionRuleValue[]
109| behavior: PermissionBehavior
110| }
111| | {
112| type: 'removeRules'
113| destination: PermissionUpdateDestination
114| rules: PermissionRuleValue[]
115| behavior: PermissionBehavior
116| }
117| | {
118| type: 'setMode'
119| destination: PermissionUpdateDestination
120| mode: ExternalPermissionMode
121| }
122| | {
123| type: 'addDirectories'
124| destination: PermissionUpdateDestination
125| directories: string[]
126| }
127| | {
128| type: 'removeDirectories'
129| destination: PermissionUpdateDestination
130| directories: string[]
131| }
PermissionDecision 与 ToolPermissionContext
PermissionDecision、PermissionResult 相关类型(文件后半)描述单次 tool 调用求值结果:behavior、updatedInput、updatedPermissions、interrupt 等。
ToolPermissionContext 类型在 permissions.ts 或相邻 export 定义 mode、rules、additionalWorkingDirectories、bypass 标志——AppState.toolPermissionContext 字段类型源。
Permission UI(components/permissions-ui)与 useCanUseTool 仅依赖 types/permissions + PermissionResult,不 import Tool 实现,这是 extract 的主要收益。
源码引用: src/types/permissions.ts · 第 148–200 行(共 442 行)
148| // ============================================================================
149| // Permission Decisions & Results
150| // ============================================================================
151|
152| /**
153| * Minimal command shape for permission metadata.
154| * This is intentionally a subset of the full Command type to avoid import cycles.
155| * Only includes properties needed by permission-related components.
156| */
157| export type PermissionCommandMetadata = {
158| name: string
159| description?: string
160| // Allow additional properties for forward compatibility
161| [key: string]: unknown
162| }
163|
164| /**
165| * Metadata attached to permission decisions
166| */
167| export type PermissionMetadata =
168| | { command: PermissionCommandMetadata }
169| | undefined
170|
171| /**
172| * Result when permission is granted
173| */
174| export type PermissionAllowDecision<
175| Input extends { [key: string]: unknown } = { [key: string]: unknown },
176| > = {
177| behavior: 'allow'
178| updatedInput?: Input
179| userModified?: boolean
180| decisionReason?: PermissionDecisionReason
181| toolUseID?: string
182| acceptFeedback?: string
183| contentBlocks?: ContentBlockParam[]
184| }
185|
186| /**
187| * Metadata for a pending classifier check that will run asynchronously.
188| * Used to enable non-blocking allow classifier evaluation.
189| */
190| export type PendingClassifierCheck = {
191| command: string
192| cwd: string
193| descriptions: string[]
194| }
195|
196| /**
197| * Result when user should be prompted
198| */
199| export type PermissionAskDecision<
200| Input extends { [key: string]: unknown } = { [key: string]: unknown },
源码引用: src/types/permissions.ts · 第 250–320 行(共 442 行)
250| */
251| export type PermissionResult<
252| Input extends { [key: string]: unknown } = { [key: string]: unknown },
253| > =
254| | PermissionDecision<Input>
255| | {
256| behavior: 'passthrough'
257| message: string
258| decisionReason?: PermissionDecision<Input>['decisionReason']
259| suggestions?: PermissionUpdate[]
260| blockedPath?: string
261| /**
262| * If set, an allow classifier check should be run asynchronously.
263| * The classifier may auto-approve the permission before the user responds.
264| */
265| pendingClassifierCheck?: PendingClassifierCheck
266| }
267|
268| /**
269| * Explanation of why a permission decision was made
270| */
271| export type PermissionDecisionReason =
272| | {
273| type: 'rule'
274| rule: PermissionRule
275| }
276| | {
277| type: 'mode'
278| mode: PermissionMode
279| }
280| | {
281| type: 'subcommandResults'
282| reasons: Map<string, PermissionResult>
283| }
284| | {
285| type: 'permissionPromptTool'
286| permissionPromptToolName: string
287| toolResult: unknown
288| }
289| | {
290| type: 'hook'
291| hookName: string
292| hookSource?: string
293| reason?: string
294| }
295| | {
296| type: 'asyncAgent'
297| reason: string
298| }
299| | {
300| type: 'sandboxOverride'
301| reason: 'excludedCommand' | 'dangerouslyDisableSandbox'
302| }
303| | {
304| type: 'classifier'
305| classifier: string
306| reason: string
307| }
308| | {
309| type: 'workingDir'
310| reason: string
311| }
312| | {
313| type: 'safetyCheck'
314| reason: string
315| // When true, auto mode lets the classifier evaluate this instead of
316| // forcing a prompt. True for sensitive-file paths (.claude/, .git/,
317| // shell configs) — the classifier can see context and decide. False
318| // for Windows path bypass attempts and cross-machine bridge messages.
319| classifierApprovable: boolean
320| }
tools.ts Progress 类型
ToolProgressData 基类:
{ kind?: string; [key: string]: unknown }
派生别名覆盖 Bash、PowerShell、MCP、Skill、TaskOutput、WebSearch、Agent、REPL、SdkWorkflow 等 Tool。Streaming executor 把 Tool 回调的 progress 对象塞进 ProgressMessage 或 inline UI。
行数少(约 16 行)但引用面广——新增 Tool 时复制别名模式即可,无需改 message.ts。
源码引用: src/types/tools.ts · 第 1–16 行(共 16 行)
1| export type ToolProgressData = {
2| kind?: string
3| [key: string]: unknown
4| }
5|
6| export type ShellProgress = ToolProgressData
7| export type BashProgress = ToolProgressData
8| export type PowerShellProgress = ToolProgressData
9| export type MCPProgress = ToolProgressData
10| export type SkillToolProgress = ToolProgressData
11| export type TaskOutputProgress = ToolProgressData
12| export type WebSearchProgress = ToolProgressData
13| export type AgentToolProgress = ToolProgressData
14| export type REPLToolProgress = ToolProgressData
15| export type SdkWorkflowProgress = ToolProgressData
16|
PromptCommand 与技能 fork
PromptCommand 字段 Highlights:
| 字段 | 作用 |
|---|---|
| source | builtin / mcp / plugin / bundled / SettingSource |
| context | inline(展开进对话)或 fork(子 agent) |
| agent | fork 时 agent 类型名 |
| allowedTools | 限制技能可用工具集 |
| hooks | 调用时注册 HooksSettings |
| paths | glob 触达后才在 autocomplete 显示 |
getPromptForCommand(args, ToolUseContext) 异步返回 ContentBlockParam[],与 API messages 对齐。
源码引用: src/types/command.ts · 第 25–57 行(共 217 行)
25| export type PromptCommand = {
26| type: 'prompt'
27| progressMessage: string
28| contentLength: number // Length of command content in characters (used for token estimation)
29| argNames?: string[]
30| allowedTools?: string[]
31| model?: string
32| source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'
33| pluginInfo?: {
34| pluginManifest: PluginManifest
35| repository: string
36| }
37| disableNonInteractive?: boolean
38| // Hooks to register when this skill is invoked
39| hooks?: HooksSettings
40| // Base directory for skill resources (used to set CLAUDE_PLUGIN_ROOT environment variable for skill hooks)
41| skillRoot?: string
42| // Execution context: 'inline' (default) or 'fork' (run as sub-agent)
43| // 'inline' = skill content expands into the current conversation
44| // 'fork' = skill runs in a sub-agent with separate context and token budget
45| context?: 'inline' | 'fork'
46| // Agent type to use when forked (e.g., 'Bash', 'general-purpose')
47| // Only applicable when context is 'fork'
48| agent?: string
49| effort?: EffortValue
50| // Glob patterns for file paths this skill applies to
51| // When set, the skill is only visible after the model touches matching files
52| paths?: string[]
53| getPromptForCommand(
54| args: string,
55| context: ToolUseContext,
56| ): Promise<ContentBlockParam[]>
57| }
Local 与 LocalJSX 命令
LocalCommand type: 'local',load() → { call: LocalCommandCall }。call 返回 LocalCommandResult:
- text:用户可见字符串
- compact:CompactionResult + 可选 displayText
- skip:不产生 transcript 行
LocalJSXCommand type: 'local-jsx',call 签名接收 onDone、扩展 context、args。LocalJSXCommandOnDone 支持 display: skip/system/user、shouldQuery、metaMessages、nextInput。
/memory 命令即 LocalJSXCommandCall:打开 MemoryFileSelector Dialog。
源码引用: src/types/command.ts · 第 16–24 行(共 217 行)
16| export type LocalCommandResult =
17| | { type: 'text'; value: string }
18| | {
19| type: 'compact'
20| compactionResult: CompactionResult
21| displayText?: string
22| }
23| | { type: 'skip' } // Skip messages
24|
源码引用: src/types/command.ts · 第 74–98 行(共 217 行)
74| type LocalCommand = {
75| type: 'local'
76| supportsNonInteractive: boolean
77| load: () => Promise<LocalCommandModule>
78| }
79|
80| export type LocalJSXCommandContext = ToolUseContext & {
81| canUseTool?: CanUseToolFn
82| setMessages: (updater: (prev: Message[]) => Message[]) => void
83| options: {
84| dynamicMcpConfig?: Record<string, ScopedMcpServerConfig>
85| ideInstallationStatus: IDEExtensionInstallationStatus | null
86| theme: ThemeName
87| }
88| onChangeAPIKey: () => void
89| onChangeDynamicMcpConfig?: (
90| config: Record<string, ScopedMcpServerConfig>,
91| ) => void
92| onInstallIDEExtension?: (ide: IdeType) => void
93| resume?: (
94| sessionId: UUID,
95| log: LogOption,
96| entrypoint: ResumeEntrypoint,
97| ) => Promise<void>
98| }
源码引用: src/types/command.ts · 第 117–155 行(共 217 行)
117| export type LocalJSXCommandOnDone = (
118| result?: string,
119| options?: {
120| display?: CommandResultDisplay
121| shouldQuery?: boolean
122| metaMessages?: string[]
123| nextInput?: string
124| submitNextInput?: boolean
125| },
126| ) => void
127|
128| /**
129| * The call signature for a local JSX command implementation.
130| */
131| export type LocalJSXCommandCall = (
132| onDone: LocalJSXCommandOnDone,
133| context: ToolUseContext & LocalJSXCommandContext,
134| args: string,
135| ) => Promise<React.ReactNode>
136|
137| /**
138| * Module shape returned by load() for lazy-loaded commands.
139| */
140| export type LocalJSXCommandModule = {
141| call: LocalJSXCommandCall
142| }
143|
144| type LocalJSXCommand = {
145| type: 'local-jsx'
146| /**
147| * Lazy-load the command implementation.
148| * Returns a module with a call() function.
149| * This defers loading heavy dependencies until the command is invoked.
150| */
151| load: () => Promise<LocalJSXCommandModule>
152| }
153|
154| /**
155| * Declares which auth/provider environments a command is available in.
ResumeEntrypoint 与 CommandResultDisplay
ResumeEntrypoint 枚举 cli_flag、slash_command_picker、slash_command_session_id、slash_command_title、fork——analytics 与 sessionStorage 用来标记 resume 来源。
CommandResultDisplay = skip | system | user 控制命令完成后的 transcript 展示层级。memory 命令 onDone 使用 display: system 展示编辑器提示。
LocalJSXCommandContext 扩展 ToolUseContext,注入 canUseTool、setMessages、theme、IDE 状态、onChangeAPIKey 等 REPL 专用回调。
源码引用: src/types/command.ts · 第 100–115 行(共 217 行)
100| export type ResumeEntrypoint =
101| | 'cli_flag'
102| | 'slash_command_picker'
103| | 'slash_command_session_id'
104| | 'slash_command_title'
105| | 'fork'
106|
107| export type CommandResultDisplay = 'skip' | 'system' | 'user'
108|
109| /**
110| * Callback when a command completes.
111| * @param result - Optional user-visible message to display
112| * @param options - Optional configuration for command completion
113| * @param options.display - How to display the result: 'skip' | 'system' | 'user' (default)
114| * @param options.shouldQuery - If true, send messages to the model after command completes
115| * @param options.metaMessages - Additional messages to insert as isMeta (model-visible but hidden)
源码引用: src/types/command.ts · 第 107–107 行(共 217 行)
107| export type CommandResultDisplay = 'skip' | 'system' | 'user'
三类型在运行时的汇合点
用户 /commit 或 keybinding command:commit
→ commands registry 查 Command union
→ local-jsx / local / prompt 分支
→ ToolUseContext + PermissionMode (types/permissions)
→ Tool 执行 → ToolProgressData (types/tools) → ProgressMessage (types/message)
类型层不包含 registry 逻辑,但 Command 与 PermissionUpdate 形状必须 stable,否则 SDK 与外部 hooks 配置会静默不兼容。
权限类型的分层边界
permissions.ts 的关键价值不是“把所有权限逻辑写在一个文件”,而是把纯类型与可运行求值器拆开。PermissionRule 描述规则来自哪里、匹配哪个 tool、行为是 allow/deny/ask;PermissionUpdate 描述一次配置变更要写到 userSettings、projectSettings、session 还是 cliArg;真正的规则合并、持久化、危险规则剥离和 hook 决策应用留在 utils/permissions。这样组件、SDK schema、hook 输出校验和 AppState 可以共享同一组名词,却不会因为 import 一个求值函数而拉进配置、文件系统或 GrowthBook 依赖。
ToolPermissionContext 又在 Tool.ts 中用 DeepImmutable 包住,说明运行时把它当“当前权限快照”传给工具,而不是让工具随手改全局状态。mode、alwaysAllowRules、alwaysDenyRules、alwaysAskRules、additionalWorkingDirectories 与 bypass/auto 可用性共同决定一次调用是否弹窗。PermissionUpdateSchema 在 SDK schema 中复刻同一判别联合,是外部 hook 与结构化 IO 能返回 updatedPermissions 的原因;因此改字段名或 destination 取值时必须同时考虑内部 TypeScript 类型、Zod schema、持久化实现和 CLI/SDK 兼容性。
源码引用: src/types/permissions.ts · 第 85–132 行(共 442 行)
85| /**
86| * Where a permission update should be persisted
87| */
88| export type PermissionUpdateDestination =
89| | 'userSettings'
90| | 'projectSettings'
91| | 'localSettings'
92| | 'session'
93| | 'cliArg'
94|
95| /**
96| * Update operations for permission configuration
97| */
98| export type PermissionUpdate =
99| | {
100| type: 'addRules'
101| destination: PermissionUpdateDestination
102| rules: PermissionRuleValue[]
103| behavior: PermissionBehavior
104| }
105| | {
106| type: 'replaceRules'
107| destination: PermissionUpdateDestination
108| rules: PermissionRuleValue[]
109| behavior: PermissionBehavior
110| }
111| | {
112| type: 'removeRules'
113| destination: PermissionUpdateDestination
114| rules: PermissionRuleValue[]
115| behavior: PermissionBehavior
116| }
117| | {
118| type: 'setMode'
119| destination: PermissionUpdateDestination
120| mode: ExternalPermissionMode
121| }
122| | {
123| type: 'addDirectories'
124| destination: PermissionUpdateDestination
125| directories: string[]
126| }
127| | {
128| type: 'removeDirectories'
129| destination: PermissionUpdateDestination
130| directories: string[]
131| }
132|
源码引用: src/Tool.ts · 第 122–138 行(共 793 行)
122| // Apply DeepImmutable to the imported type
123| export type ToolPermissionContext = DeepImmutable<{
124| mode: PermissionMode
125| additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
126| alwaysAllowRules: ToolPermissionRulesBySource
127| alwaysDenyRules: ToolPermissionRulesBySource
128| alwaysAskRules: ToolPermissionRulesBySource
129| isBypassPermissionsModeAvailable: boolean
130| isAutoModeAvailable?: boolean
131| strippedDangerousRules?: ToolPermissionRulesBySource
132| /** When true, permission prompts are auto-denied (e.g., background agents that can't show UI) */
133| shouldAvoidPermissionPrompts?: boolean
134| /** When true, automated checks (classifier, hooks) are awaited before showing the permission dialog (coordinator workers) */
135| awaitAutomatedChecksBeforeDialog?: boolean
136| /** Stores the permission mode before model-initiated plan mode entry, so it can be restored on exit */
137| prePlanMode?: PermissionMode
138| }>
源码引用: src/entrypoints/sdk/coreSchemas.ts · 第 263–298 行(共 1890 行)
263| export const PermissionUpdateSchema = lazySchema(() =>
264| z.discriminatedUnion('type', [
265| z.object({
266| type: z.literal('addRules'),
267| rules: z.array(PermissionRuleValueSchema()),
268| behavior: PermissionBehaviorSchema(),
269| destination: PermissionUpdateDestinationSchema(),
270| }),
271| z.object({
272| type: z.literal('replaceRules'),
273| rules: z.array(PermissionRuleValueSchema()),
274| behavior: PermissionBehaviorSchema(),
275| destination: PermissionUpdateDestinationSchema(),
276| }),
277| z.object({
278| type: z.literal('removeRules'),
279| rules: z.array(PermissionRuleValueSchema()),
280| behavior: PermissionBehaviorSchema(),
281| destination: PermissionUpdateDestinationSchema(),
282| }),
283| z.object({
284| type: z.literal('setMode'),
285| mode: z.lazy(() => PermissionModeSchema()),
286| destination: PermissionUpdateDestinationSchema(),
287| }),
288| z.object({
289| type: z.literal('addDirectories'),
290| directories: z.array(z.string()),
291| destination: PermissionUpdateDestinationSchema(),
292| }),
293| z.object({
294| type: z.literal('removeDirectories'),
295| directories: z.array(z.string()),
296| destination: PermissionUpdateDestinationSchema(),
297| }),
298| ]),
本章小结与延伸
tool-permission-types 连接 Tool 流式 UI、权限规则引擎与命令注册表。下一章 api-sdk-types 读 hooks 与 SDK 协议类型。 继续学习: