Sync upstream Gemini-CLI v0.8.2 (#838)

This commit is contained in:
tanzhenxin
2025-10-23 09:27:04 +08:00
committed by GitHub
parent 096fabb5d6
commit eb95c131be
644 changed files with 70389 additions and 23709 deletions

View File

@@ -56,7 +56,9 @@ describe('ShellTool', () => {
getDebugMode: vi.fn().mockReturnValue(false),
getTargetDir: vi.fn().mockReturnValue('/test/dir'),
getSummarizeToolOutputConfig: vi.fn().mockReturnValue(undefined),
getWorkspaceContext: () => createMockWorkspaceContext('.'),
getWorkspaceContext: vi
.fn()
.mockReturnValue(createMockWorkspaceContext('/test/dir')),
getGeminiClient: vi.fn(),
getGitCoAuthor: vi.fn().mockReturnValue({
enabled: true,
@@ -115,17 +117,41 @@ describe('ShellTool', () => {
).toThrow('Command cannot be empty.');
});
it('should throw an error for a non-existent directory', () => {
vi.mocked(fs.existsSync).mockReturnValue(false);
it('should throw an error for a relative directory path', () => {
expect(() =>
shellTool.build({
command: 'ls',
directory: 'rel/path',
is_background: false,
}),
).toThrow(
"Directory 'rel/path' is not a registered workspace directory.",
).toThrow('Directory must be an absolute path.');
});
it('should throw an error for a directory outside the workspace', () => {
(mockConfig.getWorkspaceContext as Mock).mockReturnValue(
createMockWorkspaceContext('/test/dir', ['/another/workspace']),
);
expect(() =>
shellTool.build({
command: 'ls',
directory: '/not/in/workspace',
is_background: false,
}),
).toThrow(
"Directory '/not/in/workspace' is not within any of the registered workspace directories.",
);
});
it('should return an invocation for a valid absolute directory path', () => {
(mockConfig.getWorkspaceContext as Mock).mockReturnValue(
createMockWorkspaceContext('/test/dir', ['/another/workspace']),
);
const invocation = shellTool.build({
command: 'ls',
directory: '/test/dir/subdir',
is_background: false,
});
expect(invocation).toBeDefined();
});
it('should include background indicator in description when is_background is true', () => {
@@ -182,12 +208,11 @@ describe('ShellTool', () => {
const wrappedCommand = `{ my-command & }; __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
expect(mockShellExecutionService).toHaveBeenCalledWith(
wrappedCommand,
expect.any(String),
'/test/dir',
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
expect(result.llmContent).toContain('Background PIDs: 54322');
expect(vi.mocked(fs.unlinkSync)).toHaveBeenCalledWith(tmpFile);
@@ -214,8 +239,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -240,8 +264,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -266,8 +289,32 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
it('should use the provided directory as cwd', async () => {
(mockConfig.getWorkspaceContext as Mock).mockReturnValue(
createMockWorkspaceContext('/test/dir'),
);
const invocation = shellTool.build({
command: 'ls',
directory: '/test/dir/subdir',
is_background: false,
});
const promise = invocation.execute(mockAbortSignal);
resolveShellExecution();
await promise;
const tmpFile = path.join(os.tmpdir(), 'shell_pgrep_abcdef.tmp');
const wrappedCommand = `{ ls; }; __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
expect(mockShellExecutionService).toHaveBeenCalledWith(
wrappedCommand,
'/test/dir/subdir',
expect.any(Function),
mockAbortSignal,
false,
{},
);
});
@@ -291,12 +338,11 @@ describe('ShellTool', () => {
await promise;
expect(mockShellExecutionService).toHaveBeenCalledWith(
'dir',
expect.any(String),
'/test/dir',
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -349,16 +395,13 @@ describe('ShellTool', () => {
});
it('should throw an error for invalid directory', () => {
vi.mocked(fs.existsSync).mockReturnValue(false);
expect(() =>
shellTool.build({
command: 'ls',
directory: 'nonexistent',
is_background: false,
}),
).toThrow(
`Directory 'nonexistent' is not a registered workspace directory.`,
);
).toThrow('Directory must be an absolute path.');
});
it('should summarize output when configured', async () => {
@@ -424,46 +467,6 @@ describe('ShellTool', () => {
vi.useRealTimers();
});
it('should throttle text output updates', async () => {
const invocation = shellTool.build({
command: 'stream',
is_background: false,
});
const promise = invocation.execute(mockAbortSignal, updateOutputMock);
// First chunk, should be throttled.
mockShellOutputCallback({
type: 'data',
chunk: 'hello ',
});
expect(updateOutputMock).not.toHaveBeenCalled();
// Advance time past the throttle interval.
await vi.advanceTimersByTimeAsync(OUTPUT_UPDATE_INTERVAL_MS + 1);
// Send a second chunk. THIS event triggers the update with the CUMULATIVE content.
mockShellOutputCallback({
type: 'data',
chunk: 'hello world',
});
// It should have been called once now with the combined output.
expect(updateOutputMock).toHaveBeenCalledOnce();
expect(updateOutputMock).toHaveBeenCalledWith('hello world');
resolveExecutionPromise({
rawOutput: Buffer.from(''),
output: '',
exitCode: 0,
signal: null,
error: null,
aborted: false,
pid: 12345,
executionMethod: 'child_process',
});
await promise;
});
it('should immediately show binary detection message and throttle progress', async () => {
const invocation = shellTool.build({
command: 'cat img',
@@ -541,8 +544,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -572,8 +574,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -603,8 +604,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -633,8 +633,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -663,8 +662,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -694,8 +692,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -731,8 +728,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
@@ -769,8 +765,7 @@ describe('ShellTool', () => {
expect.any(Function),
mockAbortSignal,
false,
undefined,
undefined,
{},
);
});
});
@@ -837,76 +832,3 @@ describe('ShellTool', () => {
});
});
});
describe('validateToolParams', () => {
it('should return null for valid directory', () => {
const config = {
getCoreTools: () => undefined,
getExcludeTools: () => undefined,
getTargetDir: () => '/root',
getWorkspaceContext: () =>
createMockWorkspaceContext('/root', ['/users/test']),
} as unknown as Config;
const shellTool = new ShellTool(config);
const result = shellTool.validateToolParams({
command: 'ls',
directory: 'test',
is_background: false,
});
expect(result).toBeNull();
});
it('should return error for directory outside workspace', () => {
const config = {
getCoreTools: () => undefined,
getExcludeTools: () => undefined,
getTargetDir: () => '/root',
getWorkspaceContext: () =>
createMockWorkspaceContext('/root', ['/users/test']),
} as unknown as Config;
const shellTool = new ShellTool(config);
const result = shellTool.validateToolParams({
command: 'ls',
directory: 'test2',
is_background: false,
});
expect(result).toContain('is not a registered workspace directory');
});
});
describe('build', () => {
it('should return an invocation for valid directory', () => {
const config = {
getCoreTools: () => undefined,
getExcludeTools: () => undefined,
getTargetDir: () => '/root',
getWorkspaceContext: () =>
createMockWorkspaceContext('/root', ['/users/test']),
} as unknown as Config;
const shellTool = new ShellTool(config);
const invocation = shellTool.build({
command: 'ls',
directory: 'test',
is_background: false,
});
expect(invocation).toBeDefined();
});
it('should throw an error for directory outside workspace', () => {
const config = {
getCoreTools: () => undefined,
getExcludeTools: () => undefined,
getTargetDir: () => '/root',
getWorkspaceContext: () =>
createMockWorkspaceContext('/root', ['/users/test']),
} as unknown as Config;
const shellTool = new ShellTool(config);
expect(() =>
shellTool.build({
command: 'ls',
directory: 'test2',
is_background: false,
}),
).toThrow('is not a registered workspace directory');
});
});