diff --git a/src/cli/components/ApprovalDialogRich.tsx b/src/cli/components/ApprovalDialogRich.tsx index c8687f8..76ad728 100644 --- a/src/cli/components/ApprovalDialogRich.tsx +++ b/src/cli/components/ApprovalDialogRich.tsx @@ -252,14 +252,16 @@ const DynamicPreview: React.FC = ({ ); } - // File edit previews: write/edit/multi_edit/replace/write_file + // File edit previews: write/edit/multi_edit/replace/write_file/write_file_gemini if ( (t === "write" || t === "edit" || t === "multiedit" || t === "replace" || t === "write_file" || - t === "writefile") && + t === "writefile" || + t === "write_file_gemini" || + t === "writefilegemini") && parsedArgs ) { try { @@ -269,7 +271,11 @@ const DynamicPreview: React.FC = ({ if (precomputedDiff) { return ( - {t === "write" || t === "write_file" || t === "writefile" ? ( + {t === "write" || + t === "write_file" || + t === "writefile" || + t === "write_file_gemini" || + t === "writefilegemini" ? ( = ({ } // Fallback to non-precomputed rendering - if (t === "write" || t === "write_file" || t === "writefile") { + if ( + t === "write" || + t === "write_file" || + t === "writefile" || + t === "write_file_gemini" || + t === "writefilegemini" + ) { return ( = ({ t === "edit" || t === "multiedit" || t === "replace" || - t === "write_file" + t === "write_file" || + t === "write_file_gemini" || + t === "writefilegemini" ) { return ( @@ -531,7 +545,13 @@ export const ApprovalDialog = memo(function ApprovalDialog({ if (!parsedArgs || !approvalRequest) return null; const toolName = approvalRequest.toolName.toLowerCase(); - if (toolName === "write") { + if ( + toolName === "write" || + toolName === "write_file" || + toolName === "writefile" || + toolName === "write_file_gemini" || + toolName === "writefilegemini" + ) { const result = computeAdvancedDiff({ kind: "write", filePath: parsedArgs.file_path as string, @@ -673,14 +693,20 @@ function getHeaderLabel(toolName: string): string { if (t === "updateplan") return "Plan update"; // Gemini toolset (snake_case) if (t === "run_shell_command") return "Shell command"; + if (t === "read_file_gemini") return "Read File"; if (t === "list_directory") return "List Directory"; + if (t === "glob_gemini") return "Find Files"; if (t === "search_file_content") return "Search in Files"; + if (t === "write_file_gemini") return "Write File"; if (t === "write_todos") return "Update Todos"; if (t === "read_many_files") return "Read Multiple Files"; // Gemini toolset (PascalCase → lowercased) if (t === "runshellcommand") return "Shell command"; + if (t === "readfilegemini") return "Read File"; if (t === "listdirectory") return "List Directory"; + if (t === "globgemini") return "Find Files"; if (t === "searchfilecontent") return "Search in Files"; + if (t === "writefilegemini") return "Write File"; if (t === "writetodos") return "Update Todos"; if (t === "readmanyfiles") return "Read Multiple Files"; // Shared/additional tools diff --git a/src/cli/components/ToolCallMessageRich.tsx b/src/cli/components/ToolCallMessageRich.tsx index 0a8fc86..3e0d1bd 100644 --- a/src/cli/components/ToolCallMessageRich.tsx +++ b/src/cli/components/ToolCallMessageRich.tsx @@ -135,6 +135,7 @@ export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => { const parsedArgs = JSON.parse(line.argsText); if (parsedArgs.todos && Array.isArray(parsedArgs.todos)) { // Convert todos to safe format for TodoRenderer + // Note: Anthropic/Codex use "content", Gemini uses "description" const safeTodos = parsedArgs.todos.map((t: unknown, i: number) => { const rec = isRecord(t) ? t : {}; const status: "pending" | "in_progress" | "completed" = @@ -144,8 +145,13 @@ export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => { ? "in_progress" : "pending"; const id = typeof rec.id === "string" ? rec.id : String(i); + // Handle both "content" (Anthropic/Codex) and "description" (Gemini) fields const content = - typeof rec.content === "string" ? rec.content : JSON.stringify(t); + typeof rec.content === "string" + ? rec.content + : typeof rec.description === "string" + ? rec.description + : JSON.stringify(t); const priority: "high" | "medium" | "low" | undefined = rec.priority === "high" ? "high" diff --git a/src/cli/helpers/toolNameMapping.ts b/src/cli/helpers/toolNameMapping.ts index abf2599..c62597b 100644 --- a/src/cli/helpers/toolNameMapping.ts +++ b/src/cli/helpers/toolNameMapping.ts @@ -42,15 +42,21 @@ export function getDisplayToolName(rawName: string): string { // Gemini toolset (snake_case) if (rawName === "run_shell_command") return "Shell"; + if (rawName === "read_file_gemini") return "Read"; if (rawName === "list_directory") return "LS"; + if (rawName === "glob_gemini") return "Glob"; if (rawName === "search_file_content") return "Grep"; + if (rawName === "write_file_gemini") return "Write"; if (rawName === "write_todos") return "TODO"; if (rawName === "read_many_files") return "Read Multiple"; // Gemini toolset (PascalCase) if (rawName === "RunShellCommand") return "Shell"; + if (rawName === "ReadFileGemini") return "Read"; if (rawName === "ListDirectory") return "LS"; + if (rawName === "GlobGemini") return "Glob"; if (rawName === "SearchFileContent") return "Grep"; + if (rawName === "WriteFileGemini") return "Write"; if (rawName === "WriteTodos") return "TODO"; if (rawName === "ReadManyFiles") return "Read Multiple"; diff --git a/src/permissions/checker.ts b/src/permissions/checker.ts index d277a5e..3d9f65f 100644 --- a/src/permissions/checker.ts +++ b/src/permissions/checker.ts @@ -20,7 +20,30 @@ import type { /** * Tools that don't require approval within working directory */ -const WORKING_DIRECTORY_TOOLS = ["Read", "Glob", "Grep"]; +const WORKING_DIRECTORY_TOOLS = [ + // Default/Anthropic toolset + "Read", + "Glob", + "Grep", + // Codex toolset + "read_file", + "ReadFile", + "list_dir", + "ListDir", + "grep_files", + "GrepFiles", + // Gemini toolset + "read_file_gemini", + "ReadFileGemini", + "glob_gemini", + "GlobGemini", + "list_directory", + "ListDirectory", + "search_file_content", + "SearchFileContent", + "read_many_files", + "ReadManyFiles", +]; const READ_ONLY_SHELL_TOOLS = new Set([ "Bash", "shell", @@ -244,13 +267,32 @@ function isWithinAllowedDirectories( */ function buildPermissionQuery(toolName: string, toolArgs: ToolArgs): string { switch (toolName) { + // File tools: "ToolName(path/to/file)" case "Read": - case "read_file": case "Write": case "Edit": case "Glob": - case "Grep": { - // File tools: "ToolName(path/to/file)" + case "Grep": + // Codex file tools + case "read_file": + case "ReadFile": + case "list_dir": + case "ListDir": + case "grep_files": + case "GrepFiles": + // Gemini file tools + case "read_file_gemini": + case "ReadFileGemini": + case "write_file_gemini": + case "WriteFileGemini": + case "glob_gemini": + case "GlobGemini": + case "list_directory": + case "ListDirectory": + case "search_file_content": + case "SearchFileContent": + case "read_many_files": + case "ReadManyFiles": { const filePath = extractFilePath(toolArgs); return filePath ? `${toolName}(${filePath})` : toolName; } @@ -286,6 +328,38 @@ function extractShellCommand(toolArgs: ToolArgs): string | string[] | null { return null; } +/** + * File tools that use glob matching for permissions + */ +const FILE_TOOLS = [ + // Default/Anthropic toolset + "Read", + "Write", + "Edit", + "Glob", + "Grep", + // Codex toolset + "read_file", + "ReadFile", + "list_dir", + "ListDir", + "grep_files", + "GrepFiles", + // Gemini toolset + "read_file_gemini", + "ReadFileGemini", + "write_file_gemini", + "WriteFileGemini", + "glob_gemini", + "GlobGemini", + "list_directory", + "ListDirectory", + "search_file_content", + "SearchFileContent", + "read_many_files", + "ReadManyFiles", +]; + /** * Check if query matches a permission pattern */ @@ -296,17 +370,7 @@ function matchesPattern( workingDirectory: string, ): boolean { // File tools use glob matching - if ( - [ - "Read", - "read_file", - "Write", - "Edit", - "Glob", - "Grep", - "grep_files", - ].includes(toolName) - ) { + if (FILE_TOOLS.includes(toolName)) { return matchesFilePattern(query, pattern, workingDirectory); } @@ -350,12 +414,16 @@ function getDefaultDecision(toolName: string): PermissionDecision { "GrepFiles", "UpdatePlan", // Gemini toolset (snake_case) - tools that don't require approval + "read_file_gemini", "list_directory", + "glob_gemini", "search_file_content", "write_todos", "read_many_files", // Gemini toolset (PascalCase) - tools that don't require approval + "ReadFileGemini", "ListDirectory", + "GlobGemini", "SearchFileContent", "WriteTodos", "ReadManyFiles",