mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-20 16:53:12 -07:00
Format.
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"experimental": {
|
||||
"plan": true,
|
||||
"extensionReloading": true,
|
||||
"modelSteering": true,
|
||||
"memoryManager": true
|
||||
},
|
||||
"general": {
|
||||
"devtools": true
|
||||
"devtools": true,
|
||||
"plan": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"overrides": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -6,6 +6,12 @@ Narrow Container
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<SectionHeader /> > 'renders correctly in a narrow contain…' 2`] = `
|
||||
"─────────────────────────
|
||||
Narrow Container
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<SectionHeader /> > 'renders correctly when title is trunc…' 1`] = `
|
||||
"────────────────────
|
||||
Very Long Header Ti…
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1026,7 +1026,10 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<ToolCallConfirmationDetails | false> {
|
||||
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) {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user