4.1-4.2 工具注册 — Fail-Closed 类型安全设计与四层工具架构

4.1-4.2 工具注册 --- Fail-Closed 类型安全设计与四层工具架构

对应原书 :第4章 4.1节"问题与挑战"、4.2节"内置工具全景"、4.3节"工具注册表 --- Fail-Closed 的类型安全设计"、4.5节"九步执行管线"、4.6节"并发编排"

辅助源码Tool.ts(793行)、tools.ts(390行)、constants/tools.ts(113行)、services/tools/toolExecution.tsservices/tools/StreamingToolExecutor.tstools/GlobTool/GlobTool.ts(示例工具)

重点关注:Tool 泛型接口、buildTool 工厂、Fail-Closed 默认值、编译期类型体操、工具注册表组装、四维模式过滤、Prompt Cache 稳定性排序、九步执行管线、并发编排与三层 AbortController


1. 导语:工具系统为何如此复杂

原书第4章开篇提出了一个尖锐的问题:一个最简单的工具调用无非是"接收 LLM 返回的 JSON 参数 → 调用对应函数 → 返回结果",一个 switch-case 加几个函数就能搞定。但在生产环境中,工具系统面临五大挑战:

复制代码
| 挑战 | 本质问题 | Claude Code 的应对 |
|------|---------|-------------------|
| **安全性** | LLM 可能被 Prompt 注入,生成 `rm -rf /` | 七层防御链 + 权限决策树 |
| **并发性** | LLM 一次返回多个工具调用,哪些并行? | `isConcurrencySafe` 声明 + 分区批处理 |
| **可中断性** | 用户随时 Ctrl+C,正在写入的文件怎么办? | 三层 AbortController + interruptBehavior |
| **可扩展性** | 内置 40+ 工具 + MCP 动态注入 | 注册表 + feature flag 条件加载 |
| **可观测性** | 每步记录遥测,不泄露代码/路径 | `TelemetrySafeError_I_VERIFIED_...` |

源码中,这五大挑战的应对散布在 Tool.ts(接口定义)、tools.ts(注册表)、toolExecution.ts(执行管线)、StreamingToolExecutor.ts(并发编排)四个文件中,共同构成一个从类型定义到运行时执行的完整体系。


2. 四层架构总览:从注册表到执行引擎

原书将工具系统描述为四层架构。源码验证如下:

复制代码
┌──────────────────────────────────────────────────────────────┐
│ Layer 3: 查询引擎 (QueryEngine.ts / query.ts)                │
│   从 LLM 响应中提取 tool_use 块,驱动执行                      │
├──────────────────────────────────────────────────────────────┤
│ Layer 2: 执行编排 (StreamingToolExecutor.ts)                  │
│   并发控制、分区策略、错误级联、流式响应                        │
├──────────────────────────────────────────────────────────────┤
│ Layer 1: 单工具执行 (toolExecution.ts)                        │
│   输入验证 → Hook → 权限决策 → call() → 后置 Hook              │
├──────────────────────────────────────────────────────────────┤
│ Layer 0: 工具注册表 (tools.ts + Tool.ts)                      │
│   工具发现、过滤、合并、排序                                    │
└──────────────────────────────────────────────────────────────┘

本笔记聚焦 Layer 0(注册表与基类),同时覆盖 Layer 1(执行管线入口)和 Layer 2(并发编排)的关键设计,为后续 4.3-4.10 各篇笔记建立基础。


3. Tool 泛型接口:30+ 方法的完整契约

Tool.ts 的核心是 Tool<Input, Output, P> 泛型类型------一个包含 30+ 方法/属性的完整接口,定义了工具从注册到渲染的全生命周期契约。

3.1 三层泛型参数

typescript 复制代码
export type Tool<
  Input extends AnyObject = AnyObject,   // Zod schema 定义的输入类型
  Output = unknown,                        // 输出类型
  P extends ToolProgressData = ToolProgressData,  // 进度事件类型
> = { ... }
  • Input :必须是 z.ZodType<{ [key: string]: unknown }> 的子类型,即 Zod schema 定义的对象类型。这保证了每个工具的输入都有运行时验证。
  • Output :工具执行的输出类型,默认 unknown,由各工具自行约束。
  • P :进度事件类型,继承自 ToolProgressData,支持流式进度推送。

3.2 方法分类

Tool 接口的 30+ 方法可按职责分为六大类:

复制代码
| 类别 | 方法 | 说明 |
|------|------|------|
| **元数据** | `name`, `aliases`, `searchHint`, `isMcp`, `isLsp`, `mcpInfo`, `shouldDefer`, `alwaysLoad`, `strict` | 工具标识与发现 |
| **安全声明** | `isEnabled`, `isReadOnly`, `isConcurrencySafe`, `isDestructive`, `isOpenWorld` | Fail-Closed 默认值覆盖 |
| **输入/输出** | `inputSchema`, `inputJSONSchema`, `outputSchema`, `validateInput`, `checkPermissions`, `getPath`, `preparePermissionMatcher` | 验证与权限 |
| **执行** | `call()`, `mapToolResultToToolResultBlockParam`, `backfillObservableInput`, `maxResultSizeChars` | 核心执行逻辑 |
| **渲染** | `description()`, `prompt()`, `renderToolUseMessage()`, `renderToolResultMessage?()`, `renderToolUseProgressMessage?()`, `renderToolUseRejectedMessage?()`, `renderToolUseErrorMessage?()`, `renderGroupedToolUse?()`, `extractSearchText?()`, `isResultTruncated?()`, `renderToolUseTag?()`, `renderToolUseQueuedMessage?()` | UI 渲染 |
| **交互** | `userFacingName()`, `userFacingNameBackgroundColor?()`, `getToolUseSummary?()`, `getActivityDescription?()`, `toAutoClassifierInput()`, `isSearchOrReadCommand?()`, `requiresUserInteraction?()`, `interruptBehavior?()`, `isTransparentWrapper?()` | 用户体验与中断 |

3.3 关键方法详解

call() --- 核心执行
typescript 复制代码
call(
  args: z.infer<Input>,
  context: ToolUseContext,
  canUseTool: CanUseToolFn,
  parentMessage: AssistantMessage,
  onProgress?: ToolCallProgress<P>,
): Promise<ToolResult<Output>>

这是工具的核心执行方法。注意它接收四个参数:

  • args:经 Zod 验证后的输入
  • context:完整的执行上下文(ToolUseContext)
  • canUseTool:权限检查回调函数
  • parentMessage:触发此工具调用的 Assistant 消息
  • onProgress:可选的进度回调
checkPermissions() --- 工具特定权限
typescript 复制代码
checkPermissions(
  input: z.infer<Input>,
  context: ToolUseContext,
): Promise<PermissionResult>

原书提到"权限决策解析"是九步管线中的第 8 步。checkPermissions 是工具特定的权限逻辑,在通用权限系统之外提供工具级别的补充检查。默认实现返回 { behavior: 'allow', updatedInput },即默认放行------通用权限系统才是主要守门人。

interruptBehavior() --- 中断行为声明
typescript 复制代码
interruptBehavior?(): 'cancel' | 'block'

当用户在工具执行期间提交新消息时:

  • 'cancel' --- 停止工具并丢弃结果
  • 'block' --- 继续运行,新消息等待

默认为 'block'。这个设计让长时间运行的工具(如编译)不会被意外中断。

maxResultSizeChars --- 结果持久化阈值
typescript 复制代码
maxResultSizeChars: number

当工具结果超过此字符数时,结果会被持久化到磁盘文件,Claude 只收到预览和文件路径。Read 工具设为 Infinity(永不持久化,因为会创建 Read→file→Read 循环)。

shouldDefer / alwaysLoad --- 延迟加载
typescript 复制代码
readonly shouldDefer?: boolean    // 需要 ToolSearch 先加载才能调用
readonly alwaysLoad?: boolean     // 永不延迟,始终在初始 prompt 中

这两个标记实现了工具的按需加载策略,减少初始 prompt 的 token 消耗。shouldDefer 的工具在模型首次使用前需要通过 ToolSearch 发现,alwaysLoad 则确保某些关键工具始终可见。


4. buildTool:Fail-Closed 默认值引擎

buildTool() 是所有工具的工厂函数。它的核心设计是 Fail-Closed 默认值------当开发者忘记声明安全属性时,系统自动选择最严格的路径。

4.1 TOOL_DEFAULTS:七个默认值

typescript 复制代码
const TOOL_DEFAULTS = {
  isEnabled: () => true,                    // ✅ 默认启用
  isConcurrencySafe: (_input?: unknown) => false,  // 🔒 保守:假设不安全
  isReadOnly: (_input?: unknown) => false,         // 🔒 保守:假设会写入
  isDestructive: (_input?: unknown) => false,      // 默认非破坏性
  checkPermissions: (input, _ctx?) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),  // 默认放行
  toAutoClassifierInput: (_input?: unknown) => '',  // 默认跳过分类器
  userFacingName: (_input?: unknown) => '',         // 默认空(被 name 覆盖)
}

原书精辟地总结了这个设计哲学:

不对称的代价------一次误判"安全"可能导致数据丢失,而一次误判"危险"最多让用户多点一次确认按钮。

关键的三组默认值:

  • isConcurrencySafe → false:新工具默认串行执行,避免并发竞态
  • isReadOnly → false:新工具默认需要权限检查,避免意外写入
  • checkPermissions → allow:默认放行给通用权限系统,工具特定权限只是补充

4.2 buildTool 的运行时逻辑

typescript 复制代码
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,          // 1. 先铺默认值
    userFacingName: () => def.name,  // 2. 用工具名作为默认 userFacingName
    ...def,                    // 3. 用开发者定义覆盖
  } as BuiltTool<D>
}

三步展开:默认值 → name 默认 → 开发者覆盖。展开顺序确保开发者定义总是优先于默认值。

4.3 GlobTool 示例:buildTool 的实际使用

GlobTool 为例,展示一个完整工具定义:

typescript 复制代码
export const GlobTool = buildTool({
  name: GLOB_TOOL_NAME,
  searchHint: 'find files by name pattern or wildcard',
  maxResultSizeChars: 100_000,
  async description() { return DESCRIPTION },
  userFacingName,
  getToolUseSummary,
  getActivityDescription(input) {
    const summary = getToolUseSummary(input)
    return summary ? `Finding ${summary}` : 'Finding files'
  },
  get inputSchema(): InputSchema { return inputSchema() },
  get outputSchema(): OutputSchema { return outputSchema() },
  isConcurrencySafe() { return true },      // ✅ 只读,并发安全
  isReadOnly() { return true },              // ✅ 只读,自动放行
  toAutoClassifierInput(input) { return input.pattern },
  isSearchOrReadCommand() { return { isSearch: true, isRead: false } },
  getPath({ path }): string { return path ? expandPath(path) : getCwd() },
  async preparePermissionMatcher({ pattern }) {
    return rulePattern => matchWildcardPattern(rulePattern, pattern)
  },
  async validateInput({ path }): Promise<ValidationResult> { ... },
  async checkPermissions(input, context): Promise<PermissionDecision> { ... },
  async prompt() { return DESCRIPTION },
  renderToolUseMessage,
  renderToolUseErrorMessage,
  renderToolResultMessage,
  extractSearchText({ filenames }) { return filenames.join('\n') },
  async call(input, { abortController, getAppState, globLimits }) { ... },
  mapToolResultToToolResultBlockParam(output, toolUseID) { ... },
} satisfies ToolDef<InputSchema, Output>)

注意几个关键点:

  1. GlobTool 显式声明 isConcurrencySafe: trueisReadOnly: true,覆盖了 Fail-Closed 默认值
  2. 使用 satisfies ToolDef<InputSchema, Output> 确保类型安全
  3. inputSchemaoutputSchema 使用 getter + lazySchema() 实现延迟初始化
  4. call() 方法是纯执行逻辑,副作用通过 ToolResult 返回

5. 编译期类型体操:ToolDef 与 BuiltTool

buildTool 最精妙的设计不在运行时,而在编译期------通过 TypeScript 条件类型和映射类型,将"运行时的防御转移到编译期的约束"。

5.1 DefaultableToolKeys:可默认的七个键

typescript 复制代码
type DefaultableToolKeys =
  | 'isEnabled'
  | 'isConcurrencySafe'
  | 'isReadOnly'
  | 'isDestructive'
  | 'checkPermissions'
  | 'toAutoClassifierInput'
  | 'userFacingName'

这七个方法在 ToolDef 中是可选的(Partial<>),但在 BuiltTool 中是必需的(-?)。

5.2 ToolDef:开发者侧的宽松类型

typescript 复制代码
export type ToolDef<Input, Output, P> =
  Omit<Tool<Input, Output, P>, DefaultableToolKeys> &  // 非默认方法必须提供
  Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>>  // 默认方法可选

开发者定义工具时可以省略有默认值的方法,但必须提供 namecallinputSchema 等非默认方法。

5.3 BuiltTool:调用方侧的严格类型

typescript 复制代码
type BuiltTool<D> = Omit<D, DefaultableToolKeys> & {
  [K in DefaultableToolKeys]-?:  // -? 移除可选性
    K extends keyof D
      ? undefined extends D[K]
        ? ToolDefaults[K]    // 开发者提供了但可能 undefined → 用默认
        : D[K]               // 开发者明确提供了 → 用开发者的
      : ToolDefaults[K]      // 开发者没提供 → 用默认
}

这段条件类型的含义:

  1. -?:所有默认键变为必需,调用方不需要任何运行时 null 检查
  2. 如果开发者提供了某个方法且非 undefined → 用开发者的类型
  3. 如果开发者提供了但可能 undefined → 用默认类型
  4. 如果开发者没提供 → 用默认类型

原书评价:

这是 TypeScript 类型系统的一个优雅应用:把运行时的防御转移到编译期的约束。


6. ToolUseContext:执行上下文的全景

ToolUseContext 是工具执行时接收的完整上下文,包含 40+ 个字段。这是工具系统中最复杂的类型之一。

6.1 核心结构

typescript 复制代码
export type ToolUseContext = {
  options: {
    commands: Command[]                    // 可用命令
    debug: boolean                         // 调试模式
    mainLoopModel: string                  // 主循环模型名
    tools: Tools                           // 可用工具列表
    verbose: boolean                       // 详细输出
    thinkingConfig: ThinkingConfig         // thinking 模式配置
    mcpClients: MCPServerConnection[]      // MCP 客户端
    mcpResources: Record<string, ServerResource[]>  // MCP 资源
    isNonInteractiveSession: boolean       // 非交互会话
    agentDefinitions: AgentDefinitionsResult  // Agent 定义
    maxBudgetUsd?: number                  // 最大预算
    customSystemPrompt?: string            // 自定义系统提示
    appendSystemPrompt?: string            // 追加系统提示
    querySource?: QuerySource              // 查询来源
    refreshTools?: () => Tools             // 刷新工具列表回调
  }
  abortController: AbortController         // 中断控制器
  readFileState: FileStateCache            // 文件状态缓存
  getAppState(): AppState                  // 获取应用状态
  setAppState(f: (prev: AppState) => AppState): void  // 更新应用状态
  setAppStateForTasks?: ...               // 会话级状态更新
  handleElicitation?: ...                  // URL 引导处理
  setToolJSX?: SetToolJSXFn               // 工具 JSX 渲染
  addNotification?: (notif: Notification) => void  // 通知
  appendSystemMessage?: ...               // 系统消息追加
  sendOSNotification?: ...                // OS 级通知
  // ... 还有 20+ 个字段
  messages: Message[]                      // 当前消息列表
  toolUseId?: string                       // 当前工具调用 ID
  agentId?: AgentId                        // 子 Agent ID
  agentType?: string                       // Agent 类型名
  contentReplacementState?: ContentReplacementState  // 内容替换状态
  renderedSystemPrompt?: SystemPrompt      // 父级渲染的系统提示
}

6.2 关键设计

setAppState vs setAppStateForTasks :两个字段看似相同,但有重要区别。setAppState 对异步 Agent 是空操作(no-op),而 setAppStateForTasks 始终到达根 Store。这确保了任何嵌套深度的 Agent 都能注册/清理基础设施。

renderedSystemPrompt :冻结在 turn 开始时的父级系统提示字节。用于 fork 子 Agent 时共享父级的 prompt cache------如果在 fork 时重新调用 getSystemPrompt(),GrowthBook 的 cold→warm 切换可能导致缓存失效。

contentReplacementState:工具结果预算的每会话状态。主线程 REPL 只初始化一次(永不重置),子 Agent 默认克隆父状态。


7. ToolResult:函数式副作用注入

typescript 复制代码
export type ToolResult<T> = {
  data: T                                  // 核心输出数据
  newMessages?: (                          // 附加消息
    | UserMessage | AssistantMessage | AttachmentMessage | SystemMessage
  )[]
  contextModifier?: (context: ToolUseContext) => ToolUseContext  // 上下文修改器
  mcpMeta?: {                              // MCP 协议元数据
    _meta?: Record<string, unknown>
    structuredContent?: Record<string, unknown>
  }
}

原书重点分析了 contextModifier 字段:

工具执行后可以通过 contextModifier 函数修改执行上下文。核心 call 方法保持纯输出,副作用(如修改权限状态、更新文件缓存)通过组合子注入。这种设计便于测试:测试时只需检查返回的 contextModifier 是否正确,不需要 mock 全局状态。

这是函数式副作用注入 模式------将副作用从工具的核心执行逻辑中分离出来,以可组合、可测试的方式表达。注意注释说明 contextModifier 只对非并发安全的工具有效("only honored for tools that aren't concurrency safe"),因为并发工具的上下文修改需要延迟收集以消除竞态。


8. tools.ts:工具注册表的组装逻辑

tools.ts 是工具注册表的核心,负责工具的发现、过滤、合并和排序。

8.1 getAllBaseTools():全量工具列表

typescript 复制代码
export function getAllBaseTools(): Tools {
  return [
    AgentTool,
    TaskOutputTool,
    BashTool,
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),  // 内嵌搜索工具时省略
    ExitPlanModeV2Tool,
    FileReadTool,
    FileEditTool,
    FileWriteTool,
    NotebookEditTool,
    WebFetchTool,
    TodoWriteTool,
    WebSearchTool,
    TaskStopTool,
    AskUserQuestionTool,
    SkillTool,
    EnterPlanModeTool,
    ...(process.env.USER_TYPE === 'ant' ? [ConfigTool, TungstenTool] : []),
    ...(SuggestBackgroundPRTool ? [SuggestBackgroundPRTool] : []),
    ...(WebBrowserTool ? [WebBrowserTool] : []),
    ...(isTodoV2Enabled() ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool] : []),
    ...(OverflowTestTool ? [OverflowTestTool] : []),
    ...(CtxInspectTool ? [CtxInspectTool] : []),
    ...(TerminalCaptureTool ? [TerminalCaptureTool] : []),
    ...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []),
    ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
    getSendMessageTool(),
    ...(ListPeersTool ? [ListPeersTool] : []),
    ...(isAgentSwarmsEnabled() ? [getTeamCreateTool(), getTeamDeleteTool()] : []),
    ...(VerifyPlanExecutionTool ? [VerifyPlanExecutionTool] : []),
    ...(process.env.USER_TYPE === 'ant' && REPLTool ? [REPLTool] : []),
    ...(WorkflowTool ? [WorkflowTool] : []),
    ...(SleepTool ? [SleepTool] : []),
    ...cronTools,
    ...(RemoteTriggerTool ? [RemoteTriggerTool] : []),
    ...(MonitorTool ? [MonitorTool] : []),
    BriefTool,
    ...(SendUserFileTool ? [SendUserFileTool] : []),
    ...(PushNotificationTool ? [PushNotificationTool] : []),
    ...(SubscribePRTool ? [SubscribePRTool] : []),
    ...(getPowerShellTool() ? [getPowerShellTool()] : []),
    ...(SnipTool ? [SnipTool] : []),
    ...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []),
    ListMcpResourcesTool,
    ReadMcpResourceTool,
    ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
  ]
}

8.2 条件加载的三种模式

源码中工具的条件加载有三种模式:

复制代码
| 模式 | 机制 | 示例 |
|------|------|------|
| **环境变量** | `process.env.USER_TYPE === 'ant'` | ConfigTool, TungstenTool, REPLTool |
| **Feature Flag** | `feature('PROACTIVE')` / `feature('AGENT_TRIGGERS')` | SleepTool, CronTools, RemoteTriggerTool |
| **运行时检测** | `isWorktreeModeEnabled()` / `isAgentSwarmsEnabled()` | WorktreeTools, TeamTools |

Dead Code Elimination :条件加载通过 require() 动态导入实现,而非静态 import。这使得 Bun 打包器可以在构建时根据 feature flag 移除未启用的工具代码,减少包体积。源码注释明确标注了 // Dead code elimination: conditional import

8.3 getTools():权限过滤后的工具列表

typescript 复制代码
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
  // 1. Simple 模式:仅保留 Bash/Read/Edit
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
    if (isReplModeEnabled() && REPLTool) {
      const replSimple: Tool[] = [REPLTool]
      // Coordinator 模式下额外暴露 Agent/TaskStop/SendMessage
      if (feature('COORDINATOR_MODE') && coordinatorModeModule?.isCoordinatorMode()) {
        replSimple.push(TaskStopTool, getSendMessageTool())
      }
      return filterToolsByDenyRules(replSimple, permissionContext)
    }
    const simpleTools: Tool[] = [BashTool, FileReadTool, FileEditTool]
    if (feature('COORDINATOR_MODE') && coordinatorModeModule?.isCoordinatorMode()) {
      simpleTools.push(AgentTool, TaskStopTool, getSendMessageTool())
    }
    return filterToolsByDenyRules(simpleTools, permissionContext)
  }

  // 2. 获取全量工具,排除特殊工具
  const specialTools = new Set([
    ListMcpResourcesTool.name, ReadMcpResourceTool.name, SYNTHETIC_OUTPUT_TOOL_NAME
  ])
  const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))

  // 3. Deny 规则过滤
  let allowedTools = filterToolsByDenyRules(tools, permissionContext)

  // 4. REPL 模式:隐藏被 REPL 包装的原始工具
  if (isReplModeEnabled()) {
    const replEnabled = allowedTools.some(tool => toolMatchesName(tool, REPL_TOOL_NAME))
    if (replEnabled) {
      allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
    }
  }

  // 5. isEnabled() 过滤
  const isEnabled = allowedTools.map(_ => _.isEnabled())
  return allowedTools.filter((_, i) => isEnabled[i])
}

9. 四维模式过滤:Simple / REPL / Coordinator / Deny

原书将 getTools() 的过滤逻辑总结为"四维模式过滤"。源码验证如下:

复制代码
| 维度 | 机制 | 效果 |
|------|------|------|
| **Simple 模式** | `CLAUDE_CODE_SIMPLE` 环境变量 | 仅保留 Bash/Read/Edit,最小工具集 |
| **REPL 模式** | `isReplModeEnabled()` + `REPL_ONLY_TOOLS` | 隐藏原始工具,强制走 REPL 统一入口 |
| **Coordinator** | `feature('COORDINATOR_MODE')` + `isCoordinatorMode()` | 额外暴露 Agent/TaskStop/SendMessage 调度能力 |
| **Deny 规则** | `filterToolsByDenyRules()` | 模型看不到已禁用工具 |

9.1 容错设计:REPL 被禁时的兜底

typescript 复制代码
if (isReplModeEnabled()) {
  const replEnabled = allowedTools.some(tool => toolMatchesName(tool, REPL_TOOL_NAME))
  if (replEnabled) {
    allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
  }
}

原书指出了一个重要的容错设计:

如果 REPLTool 被 deny 规则移除,原始工具自动重新可见,避免模型无工具可用。这体现了一个原则:过滤是减法,但减到零时要兜底

代码中 if (replEnabled) 条件确保:只有 REPL 工具确实可用时才隐藏原始工具。如果 deny 规则移除了 REPL,replEnabled 为 false,原始工具保持可见。

9.2 filterToolsByDenyRules:通用 deny 过滤

typescript 复制代码
export function filterToolsByDenyRules<T extends {
  name: string
  mcpInfo?: { serverName: string; toolName: string }
}>(tools: readonly T[], permissionContext: ToolPermissionContext): T[] {
  return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))
}

这个函数在工具池组装时(而非调用时)就过滤掉被 deny 的工具,确保模型根本看不到这些工具。源码注释指出:

Uses the same matcher as the runtime permission check (step 1a), so MCP server-prefix rules like mcp__server strip all tools from that server before the model sees them --- not just at call time.


10. Prompt Cache 稳定性排序:内置在前 MCP 在后

assembleToolPool() 函数有一个看似简单却意义重大的排序设计:

typescript 复制代码
export function assembleToolPool(
  permissionContext: ToolPermissionContext,
  mcpTools: Tools,
): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)

  const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}

10.1 为什么要分区排序?

源码注释详细解释了原因:

The server's claude_code_system_cache_policy places a global cache breakpoint after the last prefix-matched built-in tool; a flat sort would interleave MCP tools into built-ins and invalidate all downstream cache keys whenever an MCP tool sorts between existing built-ins.

如果混排,MCP 工具的动态插入或删除会导致所有下游缓存键失效,浪费大量 token 费用。分区排序确保:

  1. 内置工具分区在前,按名称排序------这部分极少变化,缓存命中率高
  2. MCP 工具分区在后,按名称排序------这部分可能动态变化,但不影响内置工具的缓存

10.2 uniqBy 的去重语义

uniqBy([...builtIn, ...mcp], 'name') 保留插入顺序,内置工具优先。当 MCP 工具与内置工具同名时,内置工具胜出。这避免了恶意 MCP 工具覆盖内置工具的行为。

10.3 getMergedTools vs assembleToolPool

typescript 复制代码
// 不排序,仅拼接
export function getMergedTools(permissionContext, mcpTools): Tools {
  const builtInTools = getTools(permissionContext)
  return [...builtInTools, ...mcpTools]
}

// 排序 + 去重,用于发送给 API
export function assembleToolPool(permissionContext, mcpTools): Tools { ... }

getMergedTools 用于内部计算(如 token 计数、工具搜索阈值),不需要排序稳定性;assembleToolPool 用于实际发送给 API,需要缓存稳定性。


11. 循环依赖破解:延迟 require 模式

tools.ts 中有三个工具使用延迟 require 来破解循环依赖:

typescript 复制代码
// TeamCreateTool → tools.ts → TeamCreateTool (循环)
const getTeamCreateTool = () =>
  require('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateTool
    as typeof import('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateTool

const getTeamDeleteTool = () =>
  require('./tools/TeamDeleteTool/TeamDeleteTool.js').TeamDeleteTool
    as typeof import('./tools/TeamDeleteTool/TeamDeleteTool.js').TeamDeleteTool

const getSendMessageTool = () =>
  require('./tools/SendMessageTool/SendMessageTool.js').SendMessageTool
    as typeof import('./tools/SendMessageTool/SendMessageTool.js').SendMessageTool

原书解释:

getter 模式保证首次调用时模块图已稳定。这是 Node.js 生态中处理循环依赖的经典手法。

关键设计:

  1. 延迟执行require() 在 getter 函数内,而非模块顶层,确保只在首次调用时执行
  2. 类型安全 :使用 as typeof import(...) 保持类型签名与静态导入完全一致
  3. 为什么这三个工具?:TeamCreateTool 和 TeamDeleteTool 依赖 tools.ts 中的工具列表(用于 Agent 工具限制),SendMessageTool 也存在类似的反向依赖。延迟 require 打破了这个循环。

12. constants/tools.ts:Agent 工具限制矩阵

constants/tools.ts 定义了不同 Agent 类型可用/禁用的工具集合,是多 Agent 协作(阶段五)的基础。

12.1 四个工具集合

typescript 复制代码
// 所有 Agent 都禁用的工具
export const ALL_AGENT_DISALLOWED_TOOLS = new Set([
  TASK_OUTPUT_TOOL_NAME,       // 防止递归
  EXIT_PLAN_MODE_V2_TOOL_NAME, // Plan mode 是主线程抽象
  ENTER_PLAN_MODE_TOOL_NAME,
  ...(process.env.USER_TYPE === 'ant' ? [] : [AGENT_TOOL_NAME]),  // ant 用户允许嵌套 Agent
  ASK_USER_QUESTION_TOOL_NAME,
  TASK_STOP_TOOL_NAME,         // 需要主线程任务状态
  ...(feature('WORKFLOW_SCRIPTS') ? [WORKFLOW_TOOL_NAME] : []),   // 防止递归工作流
])

// 自定义 Agent 额外禁用(目前与 ALL_AGENT_DISALLOWED_TOOLS 相同)
export const CUSTOM_AGENT_DISALLOWED_TOOLS = new Set([...ALL_AGENT_DISALLOWED_TOOLS])

// 异步 Agent 允许的工具(白名单模式)
export const ASYNC_AGENT_ALLOWED_TOOLS = new Set([
  FILE_READ_TOOL_NAME, WEB_SEARCH_TOOL_NAME, TODO_WRITE_TOOL_NAME,
  GREP_TOOL_NAME, WEB_FETCH_TOOL_NAME, GLOB_TOOL_NAME,
  ...SHELL_TOOL_NAMES, FILE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME,
  NOTEBOOK_EDIT_TOOL_NAME, SKILL_TOOL_NAME, SYNTHETIC_OUTPUT_TOOL_NAME,
  TOOL_SEARCH_TOOL_NAME, ENTER_WORKTREE_TOOL_NAME, EXIT_WORKTREE_TOOL_NAME,
])

// 进程内 Teammate 允许的额外工具
export const IN_PROCESS_TEAMMATE_ALLOWED_TOOLS = new Set([
  TASK_CREATE_TOOL_NAME, TASK_GET_TOOL_NAME, TASK_LIST_TOOL_NAME,
  TASK_UPDATE_TOOL_NAME, SEND_MESSAGE_TOOL_NAME,
  ...(feature('AGENT_TRIGGERS') ? [CRON_CREATE_TOOL_NAME, ...] : []),
])

// Coordinator 模式允许的工具(仅调度,不直接干活)
export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
  AGENT_TOOL_NAME, TASK_STOP_TOOL_NAME, SEND_MESSAGE_TOOL_NAME, SYNTHETIC_OUTPUT_TOOL_NAME,
])

12.2 设计分析

黑名单 vs 白名单并存

  • ALL_AGENT_DISALLOWED_TOOLS 是黑名单------所有 Agent 都不能用的工具
  • ASYNC_AGENT_ALLOWED_TOOLS 是白名单------异步 Agent 只能用这些工具
  • 两层过滤:先黑名单移除危险工具,再白名单限制可用范围

Coordinator 的极简设计:Coordinator 模式只有 4 个工具(Agent/TaskStop/SendMessage/SyntheticOutput),体现了"协调者不直接干活"的架构原则。

阻止递归AGENT_TOOL_NAME 在非 ant 用户中被禁用,防止子 Agent 嵌套子 Agent 导致递归。WORKFLOW_TOOL_NAME 也被禁用,防止工作流递归。


13. 工具描述即 Prompt:软约束的行为引导

原书 4.2.2 节提出了一个重要设计模式:工具描述即 Prompt

13.1 description 字段的双重身份

typescript 复制代码
// Tool 接口中的 description 方法
description(
  input: z.infer<Input>,
  options: {
    isNonInteractiveSession: boolean
    toolPermissionContext: ToolPermissionContext
    tools: Tools
  },
): Promise<string>

每个工具的 description() 返回的文本会被注入到系统 Prompt 中,LLM 通过阅读这些描述来决定"用哪个工具"和"怎么用"。

13.2 行为引导示例

原书以 BashTool 为例:

BashTool 的描述中明确说"不要用 Bash 来 grep/find/cat",引导 LLM 优先使用专用工具------这不是技术约束,而是 Prompt 引导。如果删掉这句话,LLM 会大量使用 Bash 来做文件搜索,绕过了专用工具的优化路径。

13.3 缓存经济性

原书指出三个层面的影响:

  1. 措辞直接影响工具选择行为 :描述中的 IMPORTANT 标注引导 LLM 注意关键约束
  2. 长度影响 Prompt Cache 经济性:所有工具描述拼接后占据系统 Prompt 的相当比例,过长浪费 token,过短导致误用
  3. 稳定性影响缓存命中率 :每次修改工具描述都会使 Prompt Cache 失效------这就是 assembleToolPool() 排序策略如此重要的原因

源码注释也印证了这一点:

NOTE: This MUST stay in sync with https://console.statsig.com/.../claude_code_global_system_caching, in order to cache the system prompt across users.


14. 九步执行管线:runToolUse 完整剖析

原书 4.5 节描述了工具执行的九步管线。源码中,这个管线分布在 runToolUse()checkPermissionsAndCallTool() 两个函数中。

14.1 runToolUse():管线入口

typescript 复制代码
export async function* runToolUse(
  toolUse: ToolUseBlock,
  assistantMessage: AssistantMessage,
  canUseTool: CanUseToolFn,
  toolUseContext: ToolUseContext,
): AsyncGenerator<MessageUpdateLazy, void> {
  // Step 1: findToolByName --- 含别名回退
  let tool = findToolByName(toolUseContext.options.tools, toolUse.name)
  if (!tool) {
    const fallbackTool = findToolByName(getAllBaseTools(), toolUse.name)
    if (fallbackTool && fallbackTool.aliases?.includes(toolUse.name)) {
      tool = fallbackTool  // 别名回退(如 "KillShell" → "TaskStop")
    }
  }

  // Step 2: Abort 检查 --- 用户中断?
  if (toolUseContext.abortController.signal.aborted) {
    yield { message: /* CANCEL_MESSAGE */ }
    return
  }

  // Step 3-9: 委托给 streamedCheckPermissionsAndCallTool
  for await (const update of streamedCheckPermissionsAndCallTool(...)) {
    yield update
  }
}

14.2 九步管线详解

步骤 函数 源码位置 说明
1. findToolByName runToolUse L345-356 含别名回退,保证旧名称向后兼容
2. Abort 检查 runToolUse L415-453 检查用户中断和流式 fallback
3. Zod Schema 验证 checkPermissionsAndCallTool L615-680 safeParse,失败返回 InputValidationError
4. 工具自定义验证 checkPermissionsAndCallTool L683-733 tool.validateInput(),如 FileEditTool 的 read-before-write
5. Bash 分类器投机预检 checkPermissionsAndCallTool L740-752 并行启动,不阻塞主流程
6. 输入回填 checkPermissionsAndCallTool L776-789 backfillObservableInput(),展开 ~ 为绝对路径
7. PreToolUse Hooks checkPermissionsAndCallTool 后续 可修改输入、返回权限决策
8. 权限决策解析 checkPermissionsAndCallTool 后续 Hook → 规则 → canUseTool → 最终决策
9. tool.call() + PostToolUse Hooks checkPermissionsAndCallTool 后续 执行 + 结果映射

14.3 关键设计决策

步骤 1 的别名机制

typescript 复制代码
if (!tool) {
  const fallbackTool = findToolByName(getAllBaseTools(), toolUse.name)
  if (fallbackTool && fallbackTool.aliases?.includes(toolUse.name)) {
    tool = fallbackTool
  }
}

当工具被重命名或废弃时(如 KillShellTaskStop),旧名称通过 aliases 数组继续工作。注意回退只在别名匹配时生效------如果工具名直接匹配但不在当前可用工具列表中,不会回退。这保证了模型在对话历史中引用旧工具名时不会报错。

步骤 3 的延迟工具 Schema 提示

typescript 复制代码
const schemaHint = buildSchemaNotSentHint(tool, toolUseContext.messages, toolUseContext.options.tools)
if (schemaHint) {
  errorContent += schemaHint  // 附加 "call ToolSearch with query select:toolName first"
}

当 Zod 验证失败时,系统检查是否是因为延迟工具的 schema 未发送给 API。如果是,附加一条结构化提示引导 LLM 先调用 ToolSearch 加载工具。这是自修复引导机制。

步骤 5 的投机预检

typescript 复制代码
if (tool.name === BASH_TOOL_NAME && parsedInput.data && 'command' in parsedInput.data) {
  startSpeculativeClassifierCheck(
    (parsedInput.data as BashToolInput).command,
    appState.toolPermissionContext,
    toolUseContext.abortController.signal,
    toolUseContext.options.isNonInteractiveSession,
  )
}

在用户审批权限弹窗期间,系统并行启动 Bash 分类器。如果用户批准,分类器结果已就绪;如果拒绝,结果被丢弃。这是乐观并行策略------用少量计算资源换取可感知的延迟降低。

步骤 6 的路径回填

typescript 复制代码
let callInput = processedInput
const backfilledClone =
  tool.backfillObservableInput && typeof processedInput === 'object' && processedInput !== null
    ? ({ ...processedInput } as typeof processedInput)
    : null

~ 展开为绝对路径看似简单,但防御意义重大------如果 Hook 配置了基于路径的白名单规则,~/././../../etc/passwd 在回填前可以绕过白名单。注意回填作用于浅拷贝,不影响 API-bound 的原始输入(保护 prompt cache)。

14.4 流式桥接模式

typescript 复制代码
function streamedCheckPermissionsAndCallTool(...): AsyncIterable<MessageUpdateLazy> {
  const stream = new Stream<MessageUpdateLazy>()
  checkPermissionsAndCallTool(...,
    progress => {
      stream.enqueue({ message: createProgressMessage(...) })  // 进度事件
    }
  )
    .then(results => { for (const r of results) stream.enqueue(r) })  // 最终结果
    .catch(error => { stream.error(error) })
    .finally(() => { stream.done() })
  return stream
}

原书分析:

工具的 onProgress 回调和最终结果通过同一个 stream 通道输出,上层消费者用 for await 统一处理。这是 callback 到 pull-based stream 的经典桥接。


15. StreamingToolExecutor:并发编排与三层 AbortController

StreamingToolExecutor 是 Layer 2 的核心,管理工具的并发执行。

15.1 四状态生命周期

typescript 复制代码
type ToolStatus = 'queued' | 'executing' | 'completed' | 'yielded'

每个工具经历四个状态:

  • queued:已加入队列,等待执行条件满足
  • executing:正在执行
  • completed:执行完成,结果已收集
  • yielded:结果已输出给消费者

15.2 并发规则

typescript 复制代码
private canExecuteTool(isConcurrencySafe: boolean): boolean {
  const executingTools = this.tools.filter(t => t.status === 'executing')
  return (
    executingTools.length === 0 ||
    (isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))
  )
}

规则简洁而安全:

  • 没有执行中的工具 → 任何工具可以开始
  • 所有执行中的工具都是 concurrencySafe → 新的并发安全工具可以并行加入
  • 存在非并发安全工具在执行 → 所有后续工具排队等待

结合 TOOL_DEFAULTS 中的 isConcurrencySafe: () => false,系统默认走最安全的串行路径。

15.3 三层 AbortController 架构

复制代码
rootAbortController (根级:用户中断 Ctrl+C)
  └── siblingAbortController (中级:Bash 错误级联)
        └── toolAbortController (叶级:单工具控制)

关键语义(原书 4.6.2 节):

typescript 复制代码
// StreamingToolExecutor 构造函数
this.siblingAbortController = createChildAbortController(toolUseContext.abortController)

// executeTool 中创建叶级 controller
const toolAbortController = createChildAbortController(this.siblingAbortController)

// 关键:abort reason 决定是否冒泡
toolAbortController.signal.addEventListener('abort', () => {
  if (
    toolAbortController.signal.reason !== 'sibling_error' &&
    !this.toolUseContext.abortController.signal.aborted &&
    !this.discarded
  ) {
    // 非 sibling_error 的 abort 冒泡到根级
    this.toolUseContext.abortController.abort(toolAbortController.signal.reason)
  }
}, { once: true })

原书解释:

当一个 Bash 工具执行出错时,abort siblingAbortController,级联取消所有并行的兄弟工具------但不会冒泡到根级。查询循环不会因为一个 Bash 错误就终止整个 turn。然而,非 sibling_error 类型的 abort(如权限拒绝)则会冒泡到根级,终止查询。

为什么要区分?因为:

  • Bash 命令失败是"正常的"(编译错误、测试失败),Agent 应该看到错误并调整策略
  • 权限被拒是"致命的",继续执行没有意义

15.4 中断行为精细化

typescript 复制代码
private getAbortReason(tool: TrackedTool):
  'sibling_error' | 'user_interrupted' | 'streaming_fallback' | null {
  if (this.discarded) return 'streaming_fallback'
  if (this.hasErrored) return 'sibling_error'
  if (this.toolUseContext.abortController.signal.aborted) {
    if (this.toolUseContext.abortController.signal.reason === 'interrupt') {
      // 用户输入了新消息------只有 interruptBehavior='cancel' 的工具才中断
      return this.getToolInterruptBehavior(tool) === 'cancel'
        ? 'user_interrupted'
        : null
    }
    return 'user_interrupted'
  }
  return null
}

'interrupt' reason 表示用户在工具运行期间输入了新消息。此时只有 interruptBehavior() === 'cancel' 的工具才会被中断,'block' 类型的工具继续运行。这实现了原书提到的"可中断性"设计。


16. 延迟工具与自修复引导

原书 4.9 节描述了延迟工具的自修复引导机制。源码在 toolExecution.tsbuildSchemaNotSentHint() 函数中实现:

typescript 复制代码
export function buildSchemaNotSentHint(
  tool: Tool,
  messages: Message[],
  tools: readonly { name: string }[],
): string | null {
  if (!isToolSearchEnabledOptimistic()) return null
  if (!isToolSearchToolAvailable(tools)) return null
  if (!isDeferredTool(tool)) return null
  const discovered = extractDiscoveredToolNames(messages)
  if (discovered.has(tool.name)) return null  // 已发现,不需要提示
  return (
    `\n\nThis tool's schema was not sent to the API --- ` +
    `it was not in the discovered-tool set derived from message history. ` +
    `Without the schema in your prompt, typed parameters get emitted as strings ` +
    `and the client-side parser rejects them. ` +
    `Load the tool first: call ${TOOL_SEARCH_TOOL_NAME} with query "select:${tool.name}", then retry this call.`
  )
}

当 LLM 尝试调用一个延迟工具但 schema 未发送时,系统不简单报错,而是返回一条结构化提示,引导 LLM 先调用 ToolSearch 加载工具。原书评价:

这是一种自修复引导机制------LLM 犯错后,从错误消息中学习正确流程,下次就不会再犯同样的错。与其设计一个防止所有错误的完美系统,不如设计一个能从错误中优雅恢复的弹性系统。


17. 设计模式提炼

模式 核心思想 源码位置 适用场景
Fail-Closed 默认值 安全相关属性的默认值选最严格选项 TOOL_DEFAULTS 任何需要可选安全声明的注册表系统
编译期类型体操 用条件类型和映射类型将运行时防御转移到编译期 BuiltTool<D> 需要默认值填充的类型安全 API
分区批处理编排 按并发安全性切割为交替分区,分区间顺序执行 StreamingToolExecutor.processQueue() 混合读写操作的批处理
三层取消语义 嵌套 AbortController + reason 分类决定冒泡 siblingAbortController + toolAbortController 多层嵌套的异步任务取消
函数式副作用注入 contextModifier 组合子分离核心逻辑与副作用 ToolResult.contextModifier 需要可测试副作用的执行框架
工具描述即 Prompt 工具描述作为 LLM 的行为引导 description() 方法 所有 LLM 工具系统
Prompt Cache 稳定性排序 内置在前 MCP 在后,分区按名排序 assembleToolPool() 有缓存需求的动态工具集
循环依赖破解 延迟 require + getter 模式 getTeamCreateTool() Node.js 循环依赖
自修复引导 错误消息引导 LLM 学习正确流程 buildSchemaNotSentHint() 延迟加载系统
投机并行预检 在等待期间并行启动分类器 startSpeculativeClassifierCheck() 有等待环节的决策系统

18. 原书对照验证

原书描述 源码验证 状态
四层架构(Layer 0-3) tools.ts(L0) → toolExecution.ts(L1) → StreamingToolExecutor.ts(L2) → query.ts(L3) ✅ 准确
30+ 方法 Tool 接口 Tool.ts L362-695,统计有 35+ 方法/属性 ✅ 准确(实际更多)
buildTool 的 Fail-Closed 默认值 TOOL_DEFAULTSisConcurrencySafe: false, isReadOnly: false ✅ 准确
编译期类型体操 ToolDef + BuiltTool<D> 条件类型和映射类型 ✅ 准确
Prompt Cache 稳定性排序 assembleToolPool()[...builtIn].sort(byName).concat(mcp.sort(byName)) ✅ 准确
四维模式过滤 Simple/REPL/Coordinator/Deny 四维 ✅ 准确
REPL 被 deny 时兜底 if (replEnabled) 条件检查 ✅ 准确
循环依赖延迟 require getTeamCreateTool()/getTeamDeleteTool()/getSendMessageTool() ✅ 准确
工具描述即 Prompt description() 方法返回注入系统 Prompt 的文本 ✅ 准确
九步执行管线 runToolUsecheckPermissionsAndCallTool,9 步逐一对应 ✅ 准确
别名回退机制 findToolByName + aliases?.includes(toolName) ✅ 准确
投机预检 startSpeculativeClassifierCheck() 在权限检查前并行启动 ✅ 准确
路径回填 backfillObservableInput() 作用于浅拷贝 ✅ 准确
流式桥接模式 Stream<MessageUpdateLazy> 统一进度和结果 ✅ 准确
函数式副作用注入 ToolResult.contextModifier 字段 ✅ 准确
四状态生命周期 queued → executing → completed → yielded ✅ 准确
三层 AbortController root → sibling → tool,reason 决定冒泡 ✅ 准确
sibling_error 不冒泡 reason !== 'sibling_error' 条件检查 ✅ 准确
interruptBehavior `'cancel' 'block',默认 'block'`
自修复引导 buildSchemaNotSentHint() 返回 ToolSearch 引导 ✅ 准确
TelemetrySafeError 防呆设计 TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS 类名 ✅ 准确
30+ 内置工具 getAllBaseTools() 返回约 35-45 个工具(含条件加载) ✅ 准确

原书描述偏差

原书描述 源码实际情况 偏差说明
"30+ 个内置工具" getAllBaseTools() 返回约 35-45 个(含 feature flag 条件加载) 偏保守,实际更多
"六大类分类" 源码中工具的安全标记(isReadOnly/isConcurrencySafe/isDestructive)是三布尔标记,不是严格的六大类 分类是概念性的,源码用标记组合表达
4.3 节标题为"工具注册表" 阅读计划标记为"4.1-4.2 工具注册" 章节编号差异,不影响内容
"Layer 2: toolorchestration.ts" 实际文件名为 StreamingToolExecutor.ts,无 toolorchestration.ts 文件名差异

附录:关键文件索引

文件 行数 核心职责
Tool.ts 793 Tool 泛型接口、buildTool 工厂、TOOL_DEFAULTS、ToolUseContext
tools.ts 390 getAllBaseTools、getTools、assembleToolPool、filterToolsByDenyRules
constants/tools.ts 113 Agent 工具限制矩阵(ALL_AGENT_DISALLOWED/ASYNC_AGENT_ALLOWED/...)
services/tools/toolExecution.ts ~1000+ runToolUse、checkPermissionsAndCallTool、buildSchemaNotSentHint
services/tools/StreamingToolExecutor.ts ~400+ 并发编排、三层 AbortController、分区批处理
tools/GlobTool/GlobTool.ts 199 buildTool 使用示例(只读工具)

下一篇:4.3 BashTool 七层安全防御 --- 从 AST 解析到语义分类器的纵深防御链

相关推荐
SimonKing1 小时前
Base62编码生成6位不重复短码的背后设计
java·后端·程序员
折哥的程序人生 · 物流技术专研1 小时前
Java 23 种设计模式:从踩坑到精通 | 番外:状态 vs 策略 —— if-else 消除的两条路,你走对了吗?
java·spring·状态模式·策略模式·java面试·java设计模式·从踩坑到精通
等什么秋1 小时前
idea2024下载
java·ide·intellij-idea
学计算机的计算基1 小时前
LeetCode 图论四题精讲:BFS、拓扑排序、Trie 树的模板与优化
java·笔记·算法
浩瀚地学1 小时前
【面试算法笔记】0202-链表-基本功能实现
java·经验分享·笔记·算法·面试
3DVisionary2 小时前
如何用单目高速DIC把“看不见“的振动变成“数得清“的数据
java·spring boot·python·数码相机·微小振动·精密件·单目高速dic
tkevinjd2 小时前
416分割等和子集
java·python·算法·leetcode·职场和发展
索西引擎2 小时前
【React】React Fiber 架构:设计动机、核心机制与调度策略分析
前端·react.js·架构
qq_452396232 小时前
第三篇:《Jenkins Pipeline as Code:Declarative Pipeline 深度实战》
java·servlet·jenkins