Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
7.3 kB · 198 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199import { feature } from 'bun:bundle'import { AGENT_TOOL_NAME } from '../../tools/AgentTool/constants.js'import { TASK_OUTPUT_TOOL_NAME } from '../../tools/TaskOutputTool/constants.js'import { TASK_STOP_TOOL_NAME } from '../../tools/TaskStopTool/prompt.js'import type { PermissionRuleValue } from './PermissionRule.js'
// Dead code elimination: ant-only tool names are conditionally required so// their strings don't leak into external builds. Static imports always bundle./* eslint-disable @typescript-eslint/no-require-imports */const BRIEF_TOOL_NAME: string | null = feature('KAIROS') || feature('KAIROS_BRIEF') ? ( require('../../tools/BriefTool/prompt.js') as typeof import('../../tools/BriefTool/prompt.js') ).BRIEF_TOOL_NAME : null/* eslint-enable @typescript-eslint/no-require-imports */
// Maps legacy tool names to their current canonical names.// When a tool is renamed, add old → new here so permission rules,// hooks, and persisted wire names resolve to the canonical name.const LEGACY_TOOL_NAME_ALIASES: Record<string, string> = { Task: AGENT_TOOL_NAME, KillShell: TASK_STOP_TOOL_NAME, AgentOutputTool: TASK_OUTPUT_TOOL_NAME, BashOutputTool: TASK_OUTPUT_TOOL_NAME, ...((feature('KAIROS') || feature('KAIROS_BRIEF')) && BRIEF_TOOL_NAME ? { Brief: BRIEF_TOOL_NAME } : {}),}
export function normalizeLegacyToolName(name: string): string { return LEGACY_TOOL_NAME_ALIASES[name] ?? name}
export function getLegacyToolNames(canonicalName: string): string[] { const result: string[] = [] for (const [legacy, canonical] of Object.entries(LEGACY_TOOL_NAME_ALIASES)) { if (canonical === canonicalName) result.push(legacy) } return result}
/** * Escapes special characters in rule content for safe storage in permission rules. * Permission rules use the format "Tool(content)", so parentheses in content must be escaped. * * Escaping order matters: * 1. Escape existing backslashes first (\ -> \\) * 2. Then escape parentheses (( -> \(, ) -> \)) * * @example * escapeRuleContent('psycopg2.connect()') // => 'psycopg2.connect\\(\\)' * escapeRuleContent('echo "test\\nvalue"') // => 'echo "test\\\\nvalue"' */export function escapeRuleContent(content: string): string { return content .replace(/\\/g, '\\\\') // Escape backslashes first .replace(/\(/g, '\\(') // Escape opening parentheses .replace(/\)/g, '\\)') // Escape closing parentheses}
/** * Unescapes special characters in rule content after parsing from permission rules. * This reverses the escaping done by escapeRuleContent. * * Unescaping order matters (reverse of escaping): * 1. Unescape parentheses first (\( -> (, \) -> )) * 2. Then unescape backslashes (\\ -> \) * * @example * unescapeRuleContent('psycopg2.connect\\(\\)') // => 'psycopg2.connect()' * unescapeRuleContent('echo "test\\\\nvalue"') // => 'echo "test\\nvalue"' */export function unescapeRuleContent(content: string): string { return content .replace(/\\\(/g, '(') // Unescape opening parentheses .replace(/\\\)/g, ')') // Unescape closing parentheses .replace(/\\\\/g, '\\') // Unescape backslashes last}
/** * Parses a permission rule string into its components. * Handles escaped parentheses in the content portion. * * Format: "ToolName" or "ToolName(content)" * Content may contain escaped parentheses: \( and \) * * @example * permissionRuleValueFromString('Bash') // => { toolName: 'Bash' } * permissionRuleValueFromString('Bash(npm install)') // => { toolName: 'Bash', ruleContent: 'npm install' } * permissionRuleValueFromString('Bash(python -c "print\\(1\\)")') // => { toolName: 'Bash', ruleContent: 'python -c "print(1)"' } */export function permissionRuleValueFromString( ruleString: string,): PermissionRuleValue { // Find the first unescaped opening parenthesis const openParenIndex = findFirstUnescapedChar(ruleString, '(') if (openParenIndex === -1) { // No parenthesis found - this is just a tool name return { toolName: normalizeLegacyToolName(ruleString) } }
// Find the last unescaped closing parenthesis const closeParenIndex = findLastUnescapedChar(ruleString, ')') if (closeParenIndex === -1 || closeParenIndex <= openParenIndex) { // No matching closing paren or malformed - treat as tool name return { toolName: normalizeLegacyToolName(ruleString) } }
// Ensure the closing paren is at the end if (closeParenIndex !== ruleString.length - 1) { // Content after closing paren - treat as tool name return { toolName: normalizeLegacyToolName(ruleString) } }
const toolName = ruleString.substring(0, openParenIndex) const rawContent = ruleString.substring(openParenIndex + 1, closeParenIndex)
// Missing toolName (e.g., "(foo)") is malformed - treat whole string as tool name if (!toolName) { return { toolName: normalizeLegacyToolName(ruleString) } }
// Empty content (e.g., "Bash()") or standalone wildcard (e.g., "Bash(*)") // should be treated as just the tool name (tool-wide rule) if (rawContent === '' || rawContent === '*') { return { toolName: normalizeLegacyToolName(toolName) } }
// Unescape the content const ruleContent = unescapeRuleContent(rawContent) return { toolName: normalizeLegacyToolName(toolName), ruleContent }}
/** * Converts a permission rule value to its string representation. * Escapes parentheses in the content to prevent parsing issues. * * @example * permissionRuleValueToString({ toolName: 'Bash' }) // => 'Bash' * permissionRuleValueToString({ toolName: 'Bash', ruleContent: 'npm install' }) // => 'Bash(npm install)' * permissionRuleValueToString({ toolName: 'Bash', ruleContent: 'python -c "print(1)"' }) // => 'Bash(python -c "print\\(1\\)")' */export function permissionRuleValueToString( ruleValue: PermissionRuleValue,): string { if (!ruleValue.ruleContent) { return ruleValue.toolName } const escapedContent = escapeRuleContent(ruleValue.ruleContent) return `${ruleValue.toolName}(${escapedContent})`}
/** * Find the index of the first unescaped occurrence of a character. * A character is escaped if preceded by an odd number of backslashes. */function findFirstUnescapedChar(str: string, char: string): number { for (let i = 0; i < str.length; i++) { if (str[i] === char) { // Count preceding backslashes let backslashCount = 0 let j = i - 1 while (j >= 0 && str[j] === '\\') { backslashCount++ j-- } // If even number of backslashes, the char is unescaped if (backslashCount % 2 === 0) { return i } } } return -1}
/** * Find the index of the last unescaped occurrence of a character. * A character is escaped if preceded by an odd number of backslashes. */function findLastUnescapedChar(str: string, char: string): number { for (let i = str.length - 1; i >= 0; i--) { if (str[i] === char) { // Count preceding backslashes let backslashCount = 0 let j = i - 1 while (j >= 0 && str[j] === '\\') { backslashCount++ j-- } // If even number of backslashes, the char is unescaped if (backslashCount % 2 === 0) { return i } } } return -1}