source dump of claude code
at main 389 lines 17 kB view raw
1// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered 2import { toolMatchesName, type Tool, type Tools } from './Tool.js' 3import { AgentTool } from './tools/AgentTool/AgentTool.js' 4import { SkillTool } from './tools/SkillTool/SkillTool.js' 5import { BashTool } from './tools/BashTool/BashTool.js' 6import { FileEditTool } from './tools/FileEditTool/FileEditTool.js' 7import { FileReadTool } from './tools/FileReadTool/FileReadTool.js' 8import { FileWriteTool } from './tools/FileWriteTool/FileWriteTool.js' 9import { GlobTool } from './tools/GlobTool/GlobTool.js' 10import { NotebookEditTool } from './tools/NotebookEditTool/NotebookEditTool.js' 11import { WebFetchTool } from './tools/WebFetchTool/WebFetchTool.js' 12import { TaskStopTool } from './tools/TaskStopTool/TaskStopTool.js' 13import { BriefTool } from './tools/BriefTool/BriefTool.js' 14// Dead code elimination: conditional import for ant-only tools 15/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ 16const REPLTool = 17 process.env.USER_TYPE === 'ant' 18 ? require('./tools/REPLTool/REPLTool.js').REPLTool 19 : null 20const SuggestBackgroundPRTool = 21 process.env.USER_TYPE === 'ant' 22 ? require('./tools/SuggestBackgroundPRTool/SuggestBackgroundPRTool.js') 23 .SuggestBackgroundPRTool 24 : null 25const SleepTool = 26 feature('PROACTIVE') || feature('KAIROS') 27 ? require('./tools/SleepTool/SleepTool.js').SleepTool 28 : null 29const cronTools = feature('AGENT_TRIGGERS') 30 ? [ 31 require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool, 32 require('./tools/ScheduleCronTool/CronDeleteTool.js').CronDeleteTool, 33 require('./tools/ScheduleCronTool/CronListTool.js').CronListTool, 34 ] 35 : [] 36const RemoteTriggerTool = feature('AGENT_TRIGGERS_REMOTE') 37 ? require('./tools/RemoteTriggerTool/RemoteTriggerTool.js').RemoteTriggerTool 38 : null 39const MonitorTool = feature('MONITOR_TOOL') 40 ? require('./tools/MonitorTool/MonitorTool.js').MonitorTool 41 : null 42const SendUserFileTool = feature('KAIROS') 43 ? require('./tools/SendUserFileTool/SendUserFileTool.js').SendUserFileTool 44 : null 45const PushNotificationTool = 46 feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION') 47 ? require('./tools/PushNotificationTool/PushNotificationTool.js') 48 .PushNotificationTool 49 : null 50const SubscribePRTool = feature('KAIROS_GITHUB_WEBHOOKS') 51 ? require('./tools/SubscribePRTool/SubscribePRTool.js').SubscribePRTool 52 : null 53/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ 54import { TaskOutputTool } from './tools/TaskOutputTool/TaskOutputTool.js' 55import { WebSearchTool } from './tools/WebSearchTool/WebSearchTool.js' 56import { TodoWriteTool } from './tools/TodoWriteTool/TodoWriteTool.js' 57import { ExitPlanModeV2Tool } from './tools/ExitPlanModeTool/ExitPlanModeV2Tool.js' 58import { TestingPermissionTool } from './tools/testing/TestingPermissionTool.js' 59import { GrepTool } from './tools/GrepTool/GrepTool.js' 60import { TungstenTool } from './tools/TungstenTool/TungstenTool.js' 61// Lazy require to break circular dependency: tools.ts -> TeamCreateTool/TeamDeleteTool -> ... -> tools.ts 62/* eslint-disable @typescript-eslint/no-require-imports */ 63const getTeamCreateTool = () => 64 require('./tools/TeamCreateTool/TeamCreateTool.js') 65 .TeamCreateTool as typeof import('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateTool 66const getTeamDeleteTool = () => 67 require('./tools/TeamDeleteTool/TeamDeleteTool.js') 68 .TeamDeleteTool as typeof import('./tools/TeamDeleteTool/TeamDeleteTool.js').TeamDeleteTool 69const getSendMessageTool = () => 70 require('./tools/SendMessageTool/SendMessageTool.js') 71 .SendMessageTool as typeof import('./tools/SendMessageTool/SendMessageTool.js').SendMessageTool 72/* eslint-enable @typescript-eslint/no-require-imports */ 73import { AskUserQuestionTool } from './tools/AskUserQuestionTool/AskUserQuestionTool.js' 74import { LSPTool } from './tools/LSPTool/LSPTool.js' 75import { ListMcpResourcesTool } from './tools/ListMcpResourcesTool/ListMcpResourcesTool.js' 76import { ReadMcpResourceTool } from './tools/ReadMcpResourceTool/ReadMcpResourceTool.js' 77import { ToolSearchTool } from './tools/ToolSearchTool/ToolSearchTool.js' 78import { EnterPlanModeTool } from './tools/EnterPlanModeTool/EnterPlanModeTool.js' 79import { EnterWorktreeTool } from './tools/EnterWorktreeTool/EnterWorktreeTool.js' 80import { ExitWorktreeTool } from './tools/ExitWorktreeTool/ExitWorktreeTool.js' 81import { ConfigTool } from './tools/ConfigTool/ConfigTool.js' 82import { TaskCreateTool } from './tools/TaskCreateTool/TaskCreateTool.js' 83import { TaskGetTool } from './tools/TaskGetTool/TaskGetTool.js' 84import { TaskUpdateTool } from './tools/TaskUpdateTool/TaskUpdateTool.js' 85import { TaskListTool } from './tools/TaskListTool/TaskListTool.js' 86import uniqBy from 'lodash-es/uniqBy.js' 87import { isToolSearchEnabledOptimistic } from './utils/toolSearch.js' 88import { isTodoV2Enabled } from './utils/tasks.js' 89// Dead code elimination: conditional import for CLAUDE_CODE_VERIFY_PLAN 90/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ 91const VerifyPlanExecutionTool = 92 process.env.CLAUDE_CODE_VERIFY_PLAN === 'true' 93 ? require('./tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.js') 94 .VerifyPlanExecutionTool 95 : null 96/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ 97import { SYNTHETIC_OUTPUT_TOOL_NAME } from './tools/SyntheticOutputTool/SyntheticOutputTool.js' 98export { 99 ALL_AGENT_DISALLOWED_TOOLS, 100 CUSTOM_AGENT_DISALLOWED_TOOLS, 101 ASYNC_AGENT_ALLOWED_TOOLS, 102 COORDINATOR_MODE_ALLOWED_TOOLS, 103} from './constants/tools.js' 104import { feature } from 'bun:bundle' 105// Dead code elimination: conditional import for OVERFLOW_TEST_TOOL 106/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ 107const OverflowTestTool = feature('OVERFLOW_TEST_TOOL') 108 ? require('./tools/OverflowTestTool/OverflowTestTool.js').OverflowTestTool 109 : null 110const CtxInspectTool = feature('CONTEXT_COLLAPSE') 111 ? require('./tools/CtxInspectTool/CtxInspectTool.js').CtxInspectTool 112 : null 113const TerminalCaptureTool = feature('TERMINAL_PANEL') 114 ? require('./tools/TerminalCaptureTool/TerminalCaptureTool.js') 115 .TerminalCaptureTool 116 : null 117const WebBrowserTool = feature('WEB_BROWSER_TOOL') 118 ? require('./tools/WebBrowserTool/WebBrowserTool.js').WebBrowserTool 119 : null 120const coordinatorModeModule = feature('COORDINATOR_MODE') 121 ? (require('./coordinator/coordinatorMode.js') as typeof import('./coordinator/coordinatorMode.js')) 122 : null 123const SnipTool = feature('HISTORY_SNIP') 124 ? require('./tools/SnipTool/SnipTool.js').SnipTool 125 : null 126const ListPeersTool = feature('UDS_INBOX') 127 ? require('./tools/ListPeersTool/ListPeersTool.js').ListPeersTool 128 : null 129const WorkflowTool = feature('WORKFLOW_SCRIPTS') 130 ? (() => { 131 require('./tools/WorkflowTool/bundled/index.js').initBundledWorkflows() 132 return require('./tools/WorkflowTool/WorkflowTool.js').WorkflowTool 133 })() 134 : null 135/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ 136import type { ToolPermissionContext } from './Tool.js' 137import { getDenyRuleForTool } from './utils/permissions/permissions.js' 138import { hasEmbeddedSearchTools } from './utils/embeddedTools.js' 139import { isEnvTruthy } from './utils/envUtils.js' 140import { isPowerShellToolEnabled } from './utils/shell/shellToolUtils.js' 141import { isAgentSwarmsEnabled } from './utils/agentSwarmsEnabled.js' 142import { isWorktreeModeEnabled } from './utils/worktreeModeEnabled.js' 143import { 144 REPL_TOOL_NAME, 145 REPL_ONLY_TOOLS, 146 isReplModeEnabled, 147} from './tools/REPLTool/constants.js' 148export { REPL_ONLY_TOOLS } 149/* eslint-disable @typescript-eslint/no-require-imports */ 150const getPowerShellTool = () => { 151 if (!isPowerShellToolEnabled()) return null 152 return ( 153 require('./tools/PowerShellTool/PowerShellTool.js') as typeof import('./tools/PowerShellTool/PowerShellTool.js') 154 ).PowerShellTool 155} 156/* eslint-enable @typescript-eslint/no-require-imports */ 157 158/** 159 * Predefined tool presets that can be used with --tools flag 160 */ 161export const TOOL_PRESETS = ['default'] as const 162 163export type ToolPreset = (typeof TOOL_PRESETS)[number] 164 165export function parseToolPreset(preset: string): ToolPreset | null { 166 const presetString = preset.toLowerCase() 167 if (!TOOL_PRESETS.includes(presetString as ToolPreset)) { 168 return null 169 } 170 return presetString as ToolPreset 171} 172 173/** 174 * Get the list of tool names for a given preset 175 * Filters out tools that are disabled via isEnabled() check 176 * @param preset The preset name 177 * @returns Array of tool names 178 */ 179export function getToolsForDefaultPreset(): string[] { 180 const tools = getAllBaseTools() 181 const isEnabled = tools.map(tool => tool.isEnabled()) 182 return tools.filter((_, i) => isEnabled[i]).map(tool => tool.name) 183} 184 185/** 186 * Get the complete exhaustive list of all tools that could be available 187 * in the current environment (respecting process.env flags). 188 * This is the source of truth for ALL tools. 189 */ 190/** 191 * NOTE: This MUST stay in sync with https://console.statsig.com/4aF3Ewatb6xPVpCwxb5nA3/dynamic_configs/claude_code_global_system_caching, in order to cache the system prompt across users. 192 */ 193export function getAllBaseTools(): Tools { 194 return [ 195 AgentTool, 196 TaskOutputTool, 197 BashTool, 198 // Ant-native builds have bfs/ugrep embedded in the bun binary (same ARGV0 199 // trick as ripgrep). When available, find/grep in Claude's shell are aliased 200 // to these fast tools, so the dedicated Glob/Grep tools are unnecessary. 201 ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]), 202 ExitPlanModeV2Tool, 203 FileReadTool, 204 FileEditTool, 205 FileWriteTool, 206 NotebookEditTool, 207 WebFetchTool, 208 TodoWriteTool, 209 WebSearchTool, 210 TaskStopTool, 211 AskUserQuestionTool, 212 SkillTool, 213 EnterPlanModeTool, 214 ...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []), 215 ...(process.env.USER_TYPE === 'ant' ? [TungstenTool] : []), 216 ...(SuggestBackgroundPRTool ? [SuggestBackgroundPRTool] : []), 217 ...(WebBrowserTool ? [WebBrowserTool] : []), 218 ...(isTodoV2Enabled() 219 ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool] 220 : []), 221 ...(OverflowTestTool ? [OverflowTestTool] : []), 222 ...(CtxInspectTool ? [CtxInspectTool] : []), 223 ...(TerminalCaptureTool ? [TerminalCaptureTool] : []), 224 ...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []), 225 ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []), 226 getSendMessageTool(), 227 ...(ListPeersTool ? [ListPeersTool] : []), 228 ...(isAgentSwarmsEnabled() 229 ? [getTeamCreateTool(), getTeamDeleteTool()] 230 : []), 231 ...(VerifyPlanExecutionTool ? [VerifyPlanExecutionTool] : []), 232 ...(process.env.USER_TYPE === 'ant' && REPLTool ? [REPLTool] : []), 233 ...(WorkflowTool ? [WorkflowTool] : []), 234 ...(SleepTool ? [SleepTool] : []), 235 ...cronTools, 236 ...(RemoteTriggerTool ? [RemoteTriggerTool] : []), 237 ...(MonitorTool ? [MonitorTool] : []), 238 BriefTool, 239 ...(SendUserFileTool ? [SendUserFileTool] : []), 240 ...(PushNotificationTool ? [PushNotificationTool] : []), 241 ...(SubscribePRTool ? [SubscribePRTool] : []), 242 ...(getPowerShellTool() ? [getPowerShellTool()] : []), 243 ...(SnipTool ? [SnipTool] : []), 244 ...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []), 245 ListMcpResourcesTool, 246 ReadMcpResourceTool, 247 // Include ToolSearchTool when tool search might be enabled (optimistic check) 248 // The actual decision to defer tools happens at request time in claude.ts 249 ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []), 250 ] 251} 252 253/** 254 * Filters out tools that are blanket-denied by the permission context. 255 * A tool is filtered out if there's a deny rule matching its name with no 256 * ruleContent (i.e., a blanket deny for that tool). 257 * 258 * Uses the same matcher as the runtime permission check (step 1a), so MCP 259 * server-prefix rules like `mcp__server` strip all tools from that server 260 * before the model sees them — not just at call time. 261 */ 262export function filterToolsByDenyRules< 263 T extends { 264 name: string 265 mcpInfo?: { serverName: string; toolName: string } 266 }, 267>(tools: readonly T[], permissionContext: ToolPermissionContext): T[] { 268 return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool)) 269} 270 271export const getTools = (permissionContext: ToolPermissionContext): Tools => { 272 // Simple mode: only Bash, Read, and Edit tools 273 if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { 274 // --bare + REPL mode: REPL wraps Bash/Read/Edit/etc inside the VM, so 275 // return REPL instead of the raw primitives. Matches the non-bare path 276 // below which also hides REPL_ONLY_TOOLS when REPL is enabled. 277 if (isReplModeEnabled() && REPLTool) { 278 const replSimple: Tool[] = [REPLTool] 279 if ( 280 feature('COORDINATOR_MODE') && 281 coordinatorModeModule?.isCoordinatorMode() 282 ) { 283 replSimple.push(TaskStopTool, getSendMessageTool()) 284 } 285 return filterToolsByDenyRules(replSimple, permissionContext) 286 } 287 const simpleTools: Tool[] = [BashTool, FileReadTool, FileEditTool] 288 // When coordinator mode is also active, include AgentTool and TaskStopTool 289 // so the coordinator gets Task+TaskStop (via useMergedTools filtering) and 290 // workers get Bash/Read/Edit (via filterToolsForAgent filtering). 291 if ( 292 feature('COORDINATOR_MODE') && 293 coordinatorModeModule?.isCoordinatorMode() 294 ) { 295 simpleTools.push(AgentTool, TaskStopTool, getSendMessageTool()) 296 } 297 return filterToolsByDenyRules(simpleTools, permissionContext) 298 } 299 300 // Get all base tools and filter out special tools that get added conditionally 301 const specialTools = new Set([ 302 ListMcpResourcesTool.name, 303 ReadMcpResourceTool.name, 304 SYNTHETIC_OUTPUT_TOOL_NAME, 305 ]) 306 307 const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name)) 308 309 // Filter out tools that are denied by the deny rules 310 let allowedTools = filterToolsByDenyRules(tools, permissionContext) 311 312 // When REPL mode is enabled, hide primitive tools from direct use. 313 // They're still accessible inside REPL via the VM context. 314 if (isReplModeEnabled()) { 315 const replEnabled = allowedTools.some(tool => 316 toolMatchesName(tool, REPL_TOOL_NAME), 317 ) 318 if (replEnabled) { 319 allowedTools = allowedTools.filter( 320 tool => !REPL_ONLY_TOOLS.has(tool.name), 321 ) 322 } 323 } 324 325 const isEnabled = allowedTools.map(_ => _.isEnabled()) 326 return allowedTools.filter((_, i) => isEnabled[i]) 327} 328 329/** 330 * Assemble the full tool pool for a given permission context and MCP tools. 331 * 332 * This is the single source of truth for combining built-in tools with MCP tools. 333 * Both REPL.tsx (via useMergedTools hook) and runAgent.ts (for coordinator workers) 334 * use this function to ensure consistent tool pool assembly. 335 * 336 * The function: 337 * 1. Gets built-in tools via getTools() (respects mode filtering) 338 * 2. Filters MCP tools by deny rules 339 * 3. Deduplicates by tool name (built-in tools take precedence) 340 * 341 * @param permissionContext - Permission context for filtering built-in tools 342 * @param mcpTools - MCP tools from appState.mcp.tools 343 * @returns Combined, deduplicated array of built-in and MCP tools 344 */ 345export function assembleToolPool( 346 permissionContext: ToolPermissionContext, 347 mcpTools: Tools, 348): Tools { 349 const builtInTools = getTools(permissionContext) 350 351 // Filter out MCP tools that are in the deny list 352 const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext) 353 354 // Sort each partition for prompt-cache stability, keeping built-ins as a 355 // contiguous prefix. The server's claude_code_system_cache_policy places a 356 // global cache breakpoint after the last prefix-matched built-in tool; a flat 357 // sort would interleave MCP tools into built-ins and invalidate all downstream 358 // cache keys whenever an MCP tool sorts between existing built-ins. uniqBy 359 // preserves insertion order, so built-ins win on name conflict. 360 // Avoid Array.toSorted (Node 20+) — we support Node 18. builtInTools is 361 // readonly so copy-then-sort; allowedMcpTools is a fresh .filter() result. 362 const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name) 363 return uniqBy( 364 [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)), 365 'name', 366 ) 367} 368 369/** 370 * Get all tools including both built-in tools and MCP tools. 371 * 372 * This is the preferred function when you need the complete tools list for: 373 * - Tool search threshold calculations (isToolSearchEnabled) 374 * - Token counting that includes MCP tools 375 * - Any context where MCP tools should be considered 376 * 377 * Use getTools() only when you specifically need just built-in tools. 378 * 379 * @param permissionContext - Permission context for filtering built-in tools 380 * @param mcpTools - MCP tools from appState.mcp.tools 381 * @returns Combined array of built-in and MCP tools 382 */ 383export function getMergedTools( 384 permissionContext: ToolPermissionContext, 385 mcpTools: Tools, 386): Tools { 387 const builtInTools = getTools(permissionContext) 388 return [...builtInTools, ...mcpTools] 389}