From 6621bd88c8fd48665b9d6ba07f46ef6f6bc18cba Mon Sep 17 00:00:00 2001 From: Christian Gunderman Date: Mon, 13 Apr 2026 19:44:05 -0700 Subject: [PATCH] Format. --- .gemini/settings.json | 9 ++- integration-tests/shell-parity.test.ts | 51 ++++++++----- .../__snapshots__/SectionHeader.test.tsx.snap | 6 ++ packages/cli/src/ui/hooks/toolMapping.ts | 5 +- packages/core/src/agents/local-executor.ts | 5 +- packages/core/src/core/geminiChat.ts | 5 +- packages/core/src/tools/shell.ts | 24 ++++-- packages/core/src/utils/shell-utils.test.ts | 74 ++++++++++--------- packages/core/src/utils/shell-utils.ts | 9 ++- 9 files changed, 119 insertions(+), 69 deletions(-) diff --git a/.gemini/settings.json b/.gemini/settings.json index 9051dc78de..6e51b96ed1 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -1,11 +1,16 @@ { "experimental": { - "plan": true, "extensionReloading": true, "modelSteering": true, "memoryManager": true }, "general": { - "devtools": true + "devtools": true, + "plan": { + "enabled": true + } + }, + "agents": { + "overrides": {} } } diff --git a/integration-tests/shell-parity.test.ts b/integration-tests/shell-parity.test.ts index 7bc16d68df..12352300a3 100644 --- a/integration-tests/shell-parity.test.ts +++ b/integration-tests/shell-parity.test.ts @@ -5,9 +5,7 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { - TestRig, -} from './test-helper.js'; +import { TestRig } from './test-helper.js'; describe('shell-parity', () => { let rig: TestRig; @@ -19,20 +17,29 @@ describe('shell-parity', () => { afterEach(async () => await rig.cleanup()); it('should use run_shell_command for replace when sandboxing is enabled', async () => { - await rig.setup('should use run_shell_command for replace when sandboxing is enabled', { - settings: { - security: { toolSandboxing: true }, + await rig.setup( + 'should use run_shell_command for replace when sandboxing is enabled', + { + settings: { + security: { toolSandboxing: true }, + }, }, - }); + ); rig.createFile('test.ts', 'const foo = "bar";'); // We expect the model to use run_shell_command because edit/replace/write_file are filtered out. - const result = await rig.run({ + await rig.run({ args: `Replace "bar" with "baz" in test.ts`, }); // Verify forbidden tools were NOT used - const forbiddenTools = ['grep_search', 'replace', 'write_file', 'edit', 'read_file']; + const forbiddenTools = [ + 'grep_search', + 'replace', + 'write_file', + 'edit', + 'read_file', + ]; const toolLogs = rig.readToolLogs(); const usedForbidden = toolLogs.filter((t) => forbiddenTools.includes(t.toolRequest.name), @@ -55,11 +62,14 @@ describe('shell-parity', () => { }); it('should use run_shell_command for search when sandboxing is enabled', async () => { - await rig.setup('should use run_shell_command for search when sandboxing is enabled', { - settings: { - security: { toolSandboxing: true }, + await rig.setup( + 'should use run_shell_command for search when sandboxing is enabled', + { + settings: { + security: { toolSandboxing: true }, + }, }, - }); + ); rig.createFile('search-me.txt', 'target-string'); await rig.run({ @@ -68,7 +78,9 @@ describe('shell-parity', () => { // Verify grep_search was NOT used const toolLogs = rig.readToolLogs(); - const usedGrep = toolLogs.filter((t) => t.toolRequest.name === 'grep_search'); + const usedGrep = toolLogs.filter( + (t) => t.toolRequest.name === 'grep_search', + ); expect(usedGrep).toHaveLength(0); // Verify run_shell_command was used @@ -80,11 +92,14 @@ describe('shell-parity', () => { }); it('should use run_shell_command for read when sandboxing is enabled', async () => { - await rig.setup('should use run_shell_command for read when sandboxing is enabled', { - settings: { - security: { toolSandboxing: true }, + await rig.setup( + 'should use run_shell_command for read when sandboxing is enabled', + { + settings: { + security: { toolSandboxing: true }, + }, }, - }); + ); rig.createFile('read-me.txt', 'hello world'); const result = await rig.run({ diff --git a/packages/cli/src/ui/components/shared/__snapshots__/SectionHeader.test.tsx.snap b/packages/cli/src/ui/components/shared/__snapshots__/SectionHeader.test.tsx.snap index fb18e546a8..bdf5564124 100644 --- a/packages/cli/src/ui/components/shared/__snapshots__/SectionHeader.test.tsx.snap +++ b/packages/cli/src/ui/components/shared/__snapshots__/SectionHeader.test.tsx.snap @@ -6,6 +6,12 @@ Narrow Container " `; +exports[` > 'renders correctly in a narrow contain…' 2`] = ` +"───────────────────────── +Narrow Container +" +`; + exports[` > 'renders correctly when title is trunc…' 1`] = ` "──────────────────── Very Long Header Ti… diff --git a/packages/cli/src/ui/hooks/toolMapping.ts b/packages/cli/src/ui/hooks/toolMapping.ts index 901b89e44a..afb8b34755 100644 --- a/packages/cli/src/ui/hooks/toolMapping.ts +++ b/packages/cli/src/ui/hooks/toolMapping.ts @@ -42,7 +42,10 @@ export function mapToDisplay( if (call.status === CoreToolCallStatus.Error) { description = JSON.stringify(call.request.args); } else { - description = typeof call.invocation.getDisplayTitle === 'function' ? call.invocation.getDisplayTitle() : call.invocation.getDescription(); + description = + typeof call.invocation.getDisplayTitle === 'function' + ? call.invocation.getDisplayTitle() + : call.invocation.getDescription(); renderOutputAsMarkdown = call.tool.isOutputMarkdown; } diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts index bfd8973cdf..b62df0d11b 100644 --- a/packages/core/src/agents/local-executor.ts +++ b/packages/core/src/agents/local-executor.ts @@ -1026,7 +1026,10 @@ export class LocalAgentExecutor { const invocation = tool.build(args); // Prefer getDisplayTitle if it differs, otherwise fallback to getDescription. // This ensures the timeline ("Shell (Replace)") matches the confirmation dialog. - description = typeof invocation.getDisplayTitle === 'function' ? invocation.getDisplayTitle() : invocation.getDescription(); + description = + typeof invocation.getDisplayTitle === 'function' + ? invocation.getDisplayTitle() + : invocation.getDescription(); } } catch { // Ignore errors during formatting for activity emission diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index eb95b61ea5..3fb966ecf0 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -1032,7 +1032,10 @@ export class GeminiChat { let description: string | undefined = undefined; if ('invocation' in call && call.invocation) { - description = typeof call.invocation.getDisplayTitle === 'function' ? call.invocation.getDisplayTitle() : call.invocation.getDescription(); + description = + typeof call.invocation.getDisplayTitle === 'function' + ? call.invocation.getDisplayTitle() + : call.invocation.getDescription(); } return { diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index 96d6114066..29ec9fb9ba 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -151,6 +151,8 @@ export class ShellToolInvocation extends BaseToolInvocation< return `Shell (Write File)`; case FileOperationType.READ: return `Shell (Read File)`; + default: + break; } } return this.params.command; @@ -218,7 +220,7 @@ export class ShellToolInvocation extends BaseToolInvocation< } protected override async getConfirmationDetails( - abortSignal: AbortSignal, + _abortSignal: AbortSignal, ): Promise { const command = stripShellWrapper(this.params.command); const inferred = inferFileOperation(this.params.command); @@ -244,25 +246,29 @@ export class ShellToolInvocation extends BaseToolInvocation< inferred.type === FileOperationType.EDIT && originalContent !== null ) { - if (inferred.metadata?.['sedExpression']) { + if (typeof inferred.metadata?.['sedExpression'] === 'string') { newContent = this.simulateSed( originalContent, - inferred.metadata['sedExpression'] as string, + inferred.metadata['sedExpression'], ); } else if ( - inferred.metadata?.['oldString'] && - inferred.metadata?.['newString'] + typeof inferred.metadata?.['oldString'] === 'string' && + typeof inferred.metadata?.['newString'] === 'string' ) { newContent = this.simulatePsReplace( originalContent, - inferred.metadata['oldString'] as string, - inferred.metadata['newString'] as string, + inferred.metadata['oldString'], + inferred.metadata['newString'], ); } } if (newContent !== undefined && originalContent !== null) { - const fileDiff = Diff.createPatch(filePath, originalContent, newContent); + const fileDiff = Diff.createPatch( + filePath, + originalContent, + newContent, + ); const editDetails: ToolEditConfirmationDetails = { type: 'edit', title: this.getDisplayTitle(), @@ -511,6 +517,8 @@ export class ShellToolInvocation extends BaseToolInvocation< operation = FileOperation.UPDATE; canonicalToolName = WRITE_FILE_TOOL_NAME; break; + default: + break; } if (operation) { diff --git a/packages/core/src/utils/shell-utils.test.ts b/packages/core/src/utils/shell-utils.test.ts index ff79f60c3a..6c4c6c381d 100644 --- a/packages/core/src/utils/shell-utils.test.ts +++ b/packages/core/src/utils/shell-utils.test.ts @@ -606,40 +606,42 @@ describe('resolveExecutable', () => { }); }); - describe('inferFileOperation', () => { - it('should infer sed -i as an EDIT operation', () => { - mockPlatform.mockReturnValue('linux'); - const result = inferFileOperation("sed -i 's/foo/bar/g' test.ts"); - expect(result).toBeDefined(); - expect(result?.type).toBe(FileOperationType.EDIT); - expect(result?.filePath).toBe('test.ts'); - expect(result?.metadata?.['sedExpression']).toBe('s/foo/bar/g'); - }); - - it('should infer echo redirection as a WRITE operation', () => { - mockPlatform.mockReturnValue('linux'); - const result = inferFileOperation("echo 'hello' > hello.txt"); - expect(result).toBeDefined(); - expect(result?.type).toBe(FileOperationType.WRITE); - expect(result?.filePath).toBe('hello.txt'); - }); - - it('should infer grep as a SEARCH operation', () => { - mockPlatform.mockReturnValue('linux'); - const result = inferFileOperation("grep 'pattern' file.txt"); - expect(result).toBeDefined(); - expect(result?.type).toBe(FileOperationType.SEARCH); - expect(result?.filePath).toBe('file.txt'); - expect(result?.metadata?.['pattern']).toBe('pattern'); - }); - - it('should infer PowerShell -replace as an EDIT operation', () => { - mockPlatform.mockReturnValue('win32'); - const result = inferFileOperation("(Get-Content file.txt) -replace 'a', 'b' | Set-Content file.txt"); - expect(result).toBeDefined(); - expect(result?.type).toBe(FileOperationType.EDIT); - expect(result?.filePath).toBe('file.txt'); - expect(result?.metadata?.['oldString']).toBe('a'); - expect(result?.metadata?.['newString']).toBe('b'); - }); +describe('inferFileOperation', () => { + it('should infer sed -i as an EDIT operation', () => { + mockPlatform.mockReturnValue('linux'); + const result = inferFileOperation("sed -i 's/foo/bar/g' test.ts"); + expect(result).toBeDefined(); + expect(result?.type).toBe(FileOperationType.EDIT); + expect(result?.filePath).toBe('test.ts'); + expect(result?.metadata?.['sedExpression']).toBe('s/foo/bar/g'); }); + + it('should infer echo redirection as a WRITE operation', () => { + mockPlatform.mockReturnValue('linux'); + const result = inferFileOperation("echo 'hello' > hello.txt"); + expect(result).toBeDefined(); + expect(result?.type).toBe(FileOperationType.WRITE); + expect(result?.filePath).toBe('hello.txt'); + }); + + it('should infer grep as a SEARCH operation', () => { + mockPlatform.mockReturnValue('linux'); + const result = inferFileOperation("grep 'pattern' file.txt"); + expect(result).toBeDefined(); + expect(result?.type).toBe(FileOperationType.SEARCH); + expect(result?.filePath).toBe('file.txt'); + expect(result?.metadata?.['pattern']).toBe('pattern'); + }); + + it('should infer PowerShell -replace as an EDIT operation', () => { + mockPlatform.mockReturnValue('win32'); + const result = inferFileOperation( + "(Get-Content file.txt) -replace 'a', 'b' | Set-Content file.txt", + ); + expect(result).toBeDefined(); + expect(result?.type).toBe(FileOperationType.EDIT); + expect(result?.filePath).toBe('file.txt'); + expect(result?.metadata?.['oldString']).toBe('a'); + expect(result?.metadata?.['newString']).toBe('b'); + }); +}); diff --git a/packages/core/src/utils/shell-utils.ts b/packages/core/src/utils/shell-utils.ts index 56ef2bd9c2..466a98b7e0 100644 --- a/packages/core/src/utils/shell-utils.ts +++ b/packages/core/src/utils/shell-utils.ts @@ -835,7 +835,9 @@ export function inferFileOperation( } // ... | tee file - const teeMatch = stripped.match(/\|\s*tee\s+(?:-a\s+)?['"]?([^'"]+)['"]?\s*$/); + const teeMatch = stripped.match( + /\|\s*tee\s+(?:-a\s+)?['"]?([^'"]+)['"]?\s*$/, + ); if (teeMatch) { return { type: FileOperationType.WRITE, @@ -874,7 +876,10 @@ export function inferFileOperation( return { type: FileOperationType.EDIT, filePath: psReplaceMatch[1].trim(), - metadata: { oldString: psReplaceMatch[2], newString: psReplaceMatch[3] }, + metadata: { + oldString: psReplaceMatch[2], + newString: psReplaceMatch[3], + }, }; }