chore: initialize recovered claude workspace
This commit is contained in:
389
src/tools.ts
Normal file
389
src/tools.ts
Normal file
@@ -0,0 +1,389 @@
|
||||
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
||||
import { toolMatchesName, type Tool, type Tools } from './Tool.js'
|
||||
import { AgentTool } from './tools/AgentTool/AgentTool.js'
|
||||
import { SkillTool } from './tools/SkillTool/SkillTool.js'
|
||||
import { BashTool } from './tools/BashTool/BashTool.js'
|
||||
import { FileEditTool } from './tools/FileEditTool/FileEditTool.js'
|
||||
import { FileReadTool } from './tools/FileReadTool/FileReadTool.js'
|
||||
import { FileWriteTool } from './tools/FileWriteTool/FileWriteTool.js'
|
||||
import { GlobTool } from './tools/GlobTool/GlobTool.js'
|
||||
import { NotebookEditTool } from './tools/NotebookEditTool/NotebookEditTool.js'
|
||||
import { WebFetchTool } from './tools/WebFetchTool/WebFetchTool.js'
|
||||
import { TaskStopTool } from './tools/TaskStopTool/TaskStopTool.js'
|
||||
import { BriefTool } from './tools/BriefTool/BriefTool.js'
|
||||
// Dead code elimination: conditional import for ant-only tools
|
||||
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
||||
const REPLTool =
|
||||
process.env.USER_TYPE === 'ant'
|
||||
? require('./tools/REPLTool/REPLTool.js').REPLTool
|
||||
: null
|
||||
const SuggestBackgroundPRTool =
|
||||
process.env.USER_TYPE === 'ant'
|
||||
? require('./tools/SuggestBackgroundPRTool/SuggestBackgroundPRTool.js')
|
||||
.SuggestBackgroundPRTool
|
||||
: null
|
||||
const SleepTool =
|
||||
feature('PROACTIVE') || feature('KAIROS')
|
||||
? require('./tools/SleepTool/SleepTool.js').SleepTool
|
||||
: null
|
||||
const cronTools = feature('AGENT_TRIGGERS')
|
||||
? [
|
||||
require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
|
||||
require('./tools/ScheduleCronTool/CronDeleteTool.js').CronDeleteTool,
|
||||
require('./tools/ScheduleCronTool/CronListTool.js').CronListTool,
|
||||
]
|
||||
: []
|
||||
const RemoteTriggerTool = feature('AGENT_TRIGGERS_REMOTE')
|
||||
? require('./tools/RemoteTriggerTool/RemoteTriggerTool.js').RemoteTriggerTool
|
||||
: null
|
||||
const MonitorTool = feature('MONITOR_TOOL')
|
||||
? require('./tools/MonitorTool/MonitorTool.js').MonitorTool
|
||||
: null
|
||||
const SendUserFileTool = feature('KAIROS')
|
||||
? require('./tools/SendUserFileTool/SendUserFileTool.js').SendUserFileTool
|
||||
: null
|
||||
const PushNotificationTool =
|
||||
feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION')
|
||||
? require('./tools/PushNotificationTool/PushNotificationTool.js')
|
||||
.PushNotificationTool
|
||||
: null
|
||||
const SubscribePRTool = feature('KAIROS_GITHUB_WEBHOOKS')
|
||||
? require('./tools/SubscribePRTool/SubscribePRTool.js').SubscribePRTool
|
||||
: null
|
||||
/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
||||
import { TaskOutputTool } from './tools/TaskOutputTool/TaskOutputTool.js'
|
||||
import { WebSearchTool } from './tools/WebSearchTool/WebSearchTool.js'
|
||||
import { TodoWriteTool } from './tools/TodoWriteTool/TodoWriteTool.js'
|
||||
import { ExitPlanModeV2Tool } from './tools/ExitPlanModeTool/ExitPlanModeV2Tool.js'
|
||||
import { TestingPermissionTool } from './tools/testing/TestingPermissionTool.js'
|
||||
import { GrepTool } from './tools/GrepTool/GrepTool.js'
|
||||
import { TungstenTool } from './tools/TungstenTool/TungstenTool.js'
|
||||
// Lazy require to break circular dependency: tools.ts -> TeamCreateTool/TeamDeleteTool -> ... -> tools.ts
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
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
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
import { AskUserQuestionTool } from './tools/AskUserQuestionTool/AskUserQuestionTool.js'
|
||||
import { LSPTool } from './tools/LSPTool/LSPTool.js'
|
||||
import { ListMcpResourcesTool } from './tools/ListMcpResourcesTool/ListMcpResourcesTool.js'
|
||||
import { ReadMcpResourceTool } from './tools/ReadMcpResourceTool/ReadMcpResourceTool.js'
|
||||
import { ToolSearchTool } from './tools/ToolSearchTool/ToolSearchTool.js'
|
||||
import { EnterPlanModeTool } from './tools/EnterPlanModeTool/EnterPlanModeTool.js'
|
||||
import { EnterWorktreeTool } from './tools/EnterWorktreeTool/EnterWorktreeTool.js'
|
||||
import { ExitWorktreeTool } from './tools/ExitWorktreeTool/ExitWorktreeTool.js'
|
||||
import { ConfigTool } from './tools/ConfigTool/ConfigTool.js'
|
||||
import { TaskCreateTool } from './tools/TaskCreateTool/TaskCreateTool.js'
|
||||
import { TaskGetTool } from './tools/TaskGetTool/TaskGetTool.js'
|
||||
import { TaskUpdateTool } from './tools/TaskUpdateTool/TaskUpdateTool.js'
|
||||
import { TaskListTool } from './tools/TaskListTool/TaskListTool.js'
|
||||
import uniqBy from 'lodash-es/uniqBy.js'
|
||||
import { isToolSearchEnabledOptimistic } from './utils/toolSearch.js'
|
||||
import { isTodoV2Enabled } from './utils/tasks.js'
|
||||
// Dead code elimination: conditional import for CLAUDE_CODE_VERIFY_PLAN
|
||||
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
||||
const VerifyPlanExecutionTool =
|
||||
process.env.CLAUDE_CODE_VERIFY_PLAN === 'true'
|
||||
? require('./tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.js')
|
||||
.VerifyPlanExecutionTool
|
||||
: null
|
||||
/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
||||
import { SYNTHETIC_OUTPUT_TOOL_NAME } from './tools/SyntheticOutputTool/SyntheticOutputTool.js'
|
||||
export {
|
||||
ALL_AGENT_DISALLOWED_TOOLS,
|
||||
CUSTOM_AGENT_DISALLOWED_TOOLS,
|
||||
ASYNC_AGENT_ALLOWED_TOOLS,
|
||||
COORDINATOR_MODE_ALLOWED_TOOLS,
|
||||
} from './constants/tools.js'
|
||||
import { feature } from 'bun:bundle'
|
||||
// Dead code elimination: conditional import for OVERFLOW_TEST_TOOL
|
||||
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
||||
const OverflowTestTool = feature('OVERFLOW_TEST_TOOL')
|
||||
? require('./tools/OverflowTestTool/OverflowTestTool.js').OverflowTestTool
|
||||
: null
|
||||
const CtxInspectTool = feature('CONTEXT_COLLAPSE')
|
||||
? require('./tools/CtxInspectTool/CtxInspectTool.js').CtxInspectTool
|
||||
: null
|
||||
const TerminalCaptureTool = feature('TERMINAL_PANEL')
|
||||
? require('./tools/TerminalCaptureTool/TerminalCaptureTool.js')
|
||||
.TerminalCaptureTool
|
||||
: null
|
||||
const WebBrowserTool = feature('WEB_BROWSER_TOOL')
|
||||
? require('./tools/WebBrowserTool/WebBrowserTool.js').WebBrowserTool
|
||||
: null
|
||||
const coordinatorModeModule = feature('COORDINATOR_MODE')
|
||||
? (require('./coordinator/coordinatorMode.js') as typeof import('./coordinator/coordinatorMode.js'))
|
||||
: null
|
||||
const SnipTool = feature('HISTORY_SNIP')
|
||||
? require('./tools/SnipTool/SnipTool.js').SnipTool
|
||||
: null
|
||||
const ListPeersTool = feature('UDS_INBOX')
|
||||
? require('./tools/ListPeersTool/ListPeersTool.js').ListPeersTool
|
||||
: null
|
||||
const WorkflowTool = feature('WORKFLOW_SCRIPTS')
|
||||
? (() => {
|
||||
require('./tools/WorkflowTool/bundled/index.js').initBundledWorkflows()
|
||||
return require('./tools/WorkflowTool/WorkflowTool.js').WorkflowTool
|
||||
})()
|
||||
: null
|
||||
/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
||||
import type { ToolPermissionContext } from './Tool.js'
|
||||
import { getDenyRuleForTool } from './utils/permissions/permissions.js'
|
||||
import { hasEmbeddedSearchTools } from './utils/embeddedTools.js'
|
||||
import { isEnvTruthy } from './utils/envUtils.js'
|
||||
import { isPowerShellToolEnabled } from './utils/shell/shellToolUtils.js'
|
||||
import { isAgentSwarmsEnabled } from './utils/agentSwarmsEnabled.js'
|
||||
import { isWorktreeModeEnabled } from './utils/worktreeModeEnabled.js'
|
||||
import {
|
||||
REPL_TOOL_NAME,
|
||||
REPL_ONLY_TOOLS,
|
||||
isReplModeEnabled,
|
||||
} from './tools/REPLTool/constants.js'
|
||||
export { REPL_ONLY_TOOLS }
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const getPowerShellTool = () => {
|
||||
if (!isPowerShellToolEnabled()) return null
|
||||
return (
|
||||
require('./tools/PowerShellTool/PowerShellTool.js') as typeof import('./tools/PowerShellTool/PowerShellTool.js')
|
||||
).PowerShellTool
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
|
||||
/**
|
||||
* Predefined tool presets that can be used with --tools flag
|
||||
*/
|
||||
export const TOOL_PRESETS = ['default'] as const
|
||||
|
||||
export type ToolPreset = (typeof TOOL_PRESETS)[number]
|
||||
|
||||
export function parseToolPreset(preset: string): ToolPreset | null {
|
||||
const presetString = preset.toLowerCase()
|
||||
if (!TOOL_PRESETS.includes(presetString as ToolPreset)) {
|
||||
return null
|
||||
}
|
||||
return presetString as ToolPreset
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of tool names for a given preset
|
||||
* Filters out tools that are disabled via isEnabled() check
|
||||
* @param preset The preset name
|
||||
* @returns Array of tool names
|
||||
*/
|
||||
export function getToolsForDefaultPreset(): string[] {
|
||||
const tools = getAllBaseTools()
|
||||
const isEnabled = tools.map(tool => tool.isEnabled())
|
||||
return tools.filter((_, i) => isEnabled[i]).map(tool => tool.name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete exhaustive list of all tools that could be available
|
||||
* in the current environment (respecting process.env flags).
|
||||
* This is the source of truth for ALL tools.
|
||||
*/
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export function getAllBaseTools(): Tools {
|
||||
return [
|
||||
AgentTool,
|
||||
TaskOutputTool,
|
||||
BashTool,
|
||||
// Ant-native builds have bfs/ugrep embedded in the bun binary (same ARGV0
|
||||
// trick as ripgrep). When available, find/grep in Claude's shell are aliased
|
||||
// to these fast tools, so the dedicated Glob/Grep tools are unnecessary.
|
||||
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
|
||||
ExitPlanModeV2Tool,
|
||||
FileReadTool,
|
||||
FileEditTool,
|
||||
FileWriteTool,
|
||||
NotebookEditTool,
|
||||
WebFetchTool,
|
||||
TodoWriteTool,
|
||||
WebSearchTool,
|
||||
TaskStopTool,
|
||||
AskUserQuestionTool,
|
||||
SkillTool,
|
||||
EnterPlanModeTool,
|
||||
...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []),
|
||||
...(process.env.USER_TYPE === 'ant' ? [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,
|
||||
// Include ToolSearchTool when tool search might be enabled (optimistic check)
|
||||
// The actual decision to defer tools happens at request time in claude.ts
|
||||
...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out tools that are blanket-denied by the permission context.
|
||||
* A tool is filtered out if there's a deny rule matching its name with no
|
||||
* ruleContent (i.e., a blanket deny for that tool).
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
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))
|
||||
}
|
||||
|
||||
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
|
||||
// Simple mode: only Bash, Read, and Edit tools
|
||||
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
|
||||
// --bare + REPL mode: REPL wraps Bash/Read/Edit/etc inside the VM, so
|
||||
// return REPL instead of the raw primitives. Matches the non-bare path
|
||||
// below which also hides REPL_ONLY_TOOLS when REPL is enabled.
|
||||
if (isReplModeEnabled() && REPLTool) {
|
||||
const replSimple: Tool[] = [REPLTool]
|
||||
if (
|
||||
feature('COORDINATOR_MODE') &&
|
||||
coordinatorModeModule?.isCoordinatorMode()
|
||||
) {
|
||||
replSimple.push(TaskStopTool, getSendMessageTool())
|
||||
}
|
||||
return filterToolsByDenyRules(replSimple, permissionContext)
|
||||
}
|
||||
const simpleTools: Tool[] = [BashTool, FileReadTool, FileEditTool]
|
||||
// When coordinator mode is also active, include AgentTool and TaskStopTool
|
||||
// so the coordinator gets Task+TaskStop (via useMergedTools filtering) and
|
||||
// workers get Bash/Read/Edit (via filterToolsForAgent filtering).
|
||||
if (
|
||||
feature('COORDINATOR_MODE') &&
|
||||
coordinatorModeModule?.isCoordinatorMode()
|
||||
) {
|
||||
simpleTools.push(AgentTool, TaskStopTool, getSendMessageTool())
|
||||
}
|
||||
return filterToolsByDenyRules(simpleTools, permissionContext)
|
||||
}
|
||||
|
||||
// Get all base tools and filter out special tools that get added conditionally
|
||||
const specialTools = new Set([
|
||||
ListMcpResourcesTool.name,
|
||||
ReadMcpResourceTool.name,
|
||||
SYNTHETIC_OUTPUT_TOOL_NAME,
|
||||
])
|
||||
|
||||
const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
|
||||
|
||||
// Filter out tools that are denied by the deny rules
|
||||
let allowedTools = filterToolsByDenyRules(tools, permissionContext)
|
||||
|
||||
// When REPL mode is enabled, hide primitive tools from direct use.
|
||||
// They're still accessible inside REPL via the VM context.
|
||||
if (isReplModeEnabled()) {
|
||||
const replEnabled = allowedTools.some(tool =>
|
||||
toolMatchesName(tool, REPL_TOOL_NAME),
|
||||
)
|
||||
if (replEnabled) {
|
||||
allowedTools = allowedTools.filter(
|
||||
tool => !REPL_ONLY_TOOLS.has(tool.name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const isEnabled = allowedTools.map(_ => _.isEnabled())
|
||||
return allowedTools.filter((_, i) => isEnabled[i])
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble the full tool pool for a given permission context and MCP tools.
|
||||
*
|
||||
* This is the single source of truth for combining built-in tools with MCP tools.
|
||||
* Both REPL.tsx (via useMergedTools hook) and runAgent.ts (for coordinator workers)
|
||||
* use this function to ensure consistent tool pool assembly.
|
||||
*
|
||||
* The function:
|
||||
* 1. Gets built-in tools via getTools() (respects mode filtering)
|
||||
* 2. Filters MCP tools by deny rules
|
||||
* 3. Deduplicates by tool name (built-in tools take precedence)
|
||||
*
|
||||
* @param permissionContext - Permission context for filtering built-in tools
|
||||
* @param mcpTools - MCP tools from appState.mcp.tools
|
||||
* @returns Combined, deduplicated array of built-in and MCP tools
|
||||
*/
|
||||
export function assembleToolPool(
|
||||
permissionContext: ToolPermissionContext,
|
||||
mcpTools: Tools,
|
||||
): Tools {
|
||||
const builtInTools = getTools(permissionContext)
|
||||
|
||||
// Filter out MCP tools that are in the deny list
|
||||
const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
|
||||
|
||||
// Sort each partition for prompt-cache stability, keeping built-ins as a
|
||||
// contiguous prefix. 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. uniqBy
|
||||
// preserves insertion order, so built-ins win on name conflict.
|
||||
// Avoid Array.toSorted (Node 20+) — we support Node 18. builtInTools is
|
||||
// readonly so copy-then-sort; allowedMcpTools is a fresh .filter() result.
|
||||
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
|
||||
return uniqBy(
|
||||
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
|
||||
'name',
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tools including both built-in tools and MCP tools.
|
||||
*
|
||||
* This is the preferred function when you need the complete tools list for:
|
||||
* - Tool search threshold calculations (isToolSearchEnabled)
|
||||
* - Token counting that includes MCP tools
|
||||
* - Any context where MCP tools should be considered
|
||||
*
|
||||
* Use getTools() only when you specifically need just built-in tools.
|
||||
*
|
||||
* @param permissionContext - Permission context for filtering built-in tools
|
||||
* @param mcpTools - MCP tools from appState.mcp.tools
|
||||
* @returns Combined array of built-in and MCP tools
|
||||
*/
|
||||
export function getMergedTools(
|
||||
permissionContext: ToolPermissionContext,
|
||||
mcpTools: Tools,
|
||||
): Tools {
|
||||
const builtInTools = getTools(permissionContext)
|
||||
return [...builtInTools, ...mcpTools]
|
||||
}
|
||||
Reference in New Issue
Block a user