feat(core): Add configurable inactivity timeout for shell commands (#13531)

This commit is contained in:
Gal Zahavi
2025-11-26 13:43:33 -08:00
committed by GitHub
parent 87edeb4e32
commit 0d29385e1b
9 changed files with 124 additions and 7 deletions

View File

@@ -92,6 +92,7 @@ describe('ShellTool', () => {
getGeminiClient: vi.fn(),
getEnableInteractiveShell: vi.fn().mockReturnValue(false),
isInteractive: vi.fn().mockReturnValue(true),
getShellToolInactivityTimeout: vi.fn().mockReturnValue(300000),
} as unknown as Config;
shellTool = new ShellTool(mockConfig);
@@ -219,7 +220,7 @@ describe('ShellTool', () => {
wrappedCommand,
tempRootDir,
expect.any(Function),
mockAbortSignal,
expect.any(AbortSignal),
false,
{},
);
@@ -244,7 +245,7 @@ describe('ShellTool', () => {
wrappedCommand,
subdir,
expect.any(Function),
mockAbortSignal,
expect.any(AbortSignal),
false,
{},
);
@@ -265,7 +266,7 @@ describe('ShellTool', () => {
wrappedCommand,
path.join(tempRootDir, 'subdir'),
expect.any(Function),
mockAbortSignal,
expect.any(AbortSignal),
false,
{},
);
@@ -292,7 +293,7 @@ describe('ShellTool', () => {
'dir',
tempRootDir,
expect.any(Function),
mockAbortSignal,
expect.any(AbortSignal),
false,
{},
);
@@ -376,6 +377,30 @@ describe('ShellTool', () => {
expect(result.returnDisplay).toBe('long output');
});
it('should NOT start a timeout if timeoutMs is <= 0', async () => {
// Mock the timeout config to be 0
(mockConfig.getShellToolInactivityTimeout as Mock).mockReturnValue(0);
vi.useFakeTimers();
const invocation = shellTool.build({ command: 'sleep 10' });
const promise = invocation.execute(mockAbortSignal);
// Verify no timeout logic is triggered even after a long time
resolveShellExecution({
output: 'finished',
exitCode: 0,
});
await promise;
// If we got here without aborting/timing out logic interfering, we're good.
// We can also verify that setTimeout was NOT called for the inactivity timeout.
// However, since we don't have direct access to the internal `resetTimeout`,
// we can infer success by the fact it didn't abort.
vi.useRealTimers();
});
it('should clean up the temp file on synchronous execution error', async () => {
const error = new Error('sync spawn error');
mockShellExecutionService.mockImplementation(() => {