mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-24 12:04:56 -07:00
Implement background process monitoring and inspection tools (#23799)
This commit is contained in:
@@ -128,6 +128,7 @@ const mockProcessKill = vi
|
||||
.mockImplementation(() => true);
|
||||
|
||||
const shellExecutionConfig: ShellExecutionConfig = {
|
||||
sessionId: 'default',
|
||||
terminalWidth: 80,
|
||||
terminalHeight: 24,
|
||||
pager: 'cat',
|
||||
@@ -483,6 +484,7 @@ describe('ShellExecutionService', () => {
|
||||
ptyProcess: mockPtyProcess as any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
headlessTerminal: mockHeadlessTerminal as any,
|
||||
command: 'some-command',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -753,6 +755,8 @@ describe('ShellExecutionService', () => {
|
||||
(ShellExecutionService as any).activePtys.clear();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ShellExecutionService as any).activeChildProcesses.clear();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ShellExecutionService as any).backgroundProcessHistory.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -783,7 +787,11 @@ describe('ShellExecutionService', () => {
|
||||
]);
|
||||
|
||||
// Background the process
|
||||
ShellExecutionService.background(handle.pid!);
|
||||
ShellExecutionService.background(
|
||||
handle.pid!,
|
||||
'default',
|
||||
'long-running-pty',
|
||||
);
|
||||
|
||||
const result = await handle.result;
|
||||
expect(result.backgrounded).toBe(true);
|
||||
@@ -791,7 +799,7 @@ describe('ShellExecutionService', () => {
|
||||
|
||||
expect(mockMkdirSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('background-processes'),
|
||||
{ recursive: true },
|
||||
{ recursive: true, mode: 0o700 },
|
||||
);
|
||||
|
||||
// Verify initial output was written
|
||||
@@ -822,7 +830,11 @@ describe('ShellExecutionService', () => {
|
||||
mockBgChildProcess.stdout?.emit('data', Buffer.from('initial cp output'));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
ShellExecutionService.background(handle.pid!);
|
||||
ShellExecutionService.background(
|
||||
handle.pid!,
|
||||
'default',
|
||||
'long-running-child',
|
||||
);
|
||||
|
||||
const result = await handle.result;
|
||||
expect(result.backgrounded).toBe(true);
|
||||
@@ -861,7 +873,11 @@ describe('ShellExecutionService', () => {
|
||||
});
|
||||
|
||||
// Background the process
|
||||
ShellExecutionService.background(handle.pid!);
|
||||
ShellExecutionService.background(
|
||||
handle.pid!,
|
||||
'default',
|
||||
'failing-log-setup',
|
||||
);
|
||||
|
||||
const result = await handle.result;
|
||||
expect(result.backgrounded).toBe(true);
|
||||
@@ -872,6 +888,89 @@ describe('ShellExecutionService', () => {
|
||||
|
||||
await ShellExecutionService.kill(handle.pid!);
|
||||
});
|
||||
|
||||
it('should track background process history', async () => {
|
||||
await simulateExecution(
|
||||
'history-test-cmd',
|
||||
async (pty) => {
|
||||
ShellExecutionService.background(
|
||||
pty.pid,
|
||||
'default',
|
||||
'history-test-cmd',
|
||||
);
|
||||
|
||||
const history =
|
||||
ShellExecutionService.listBackgroundProcesses('default');
|
||||
expect(history).toHaveLength(1);
|
||||
expect(history[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
pid: pty.pid,
|
||||
command: 'history-test-cmd',
|
||||
status: 'running',
|
||||
}),
|
||||
);
|
||||
|
||||
// Simulate exit
|
||||
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
|
||||
},
|
||||
{ ...shellExecutionConfig, originalCommand: 'history-test-cmd' },
|
||||
);
|
||||
|
||||
const history = ShellExecutionService.listBackgroundProcesses('default');
|
||||
expect(history[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
pid: mockPtyProcess.pid,
|
||||
command: 'history-test-cmd',
|
||||
status: 'exited',
|
||||
exitCode: 0,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should evict oldest process history when exceeding max size', () => {
|
||||
const MAX = 100;
|
||||
const history = new Map();
|
||||
for (let i = 1; i <= MAX; i++) {
|
||||
history.set(i, {
|
||||
command: `cmd-${i}`,
|
||||
status: 'running',
|
||||
startTime: Date.now(),
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ShellExecutionService as any).backgroundProcessHistory.set(
|
||||
'default',
|
||||
history,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ShellExecutionService as any).activeChildProcesses.set(101, {
|
||||
process: {},
|
||||
state: { output: '' },
|
||||
command: 'cmd-101',
|
||||
sessionId: 'default',
|
||||
});
|
||||
|
||||
ShellExecutionService.background(101, 'default', 'cmd-101');
|
||||
|
||||
const processes =
|
||||
ShellExecutionService.listBackgroundProcesses('default');
|
||||
expect(processes).toHaveLength(MAX);
|
||||
expect(processes.some((p) => p.pid === 1)).toBe(false);
|
||||
});
|
||||
|
||||
it('should throw error if sessionId is missing for background operations', () => {
|
||||
expect(() => ShellExecutionService.background(102)).toThrow(
|
||||
'Session ID is required for background operations',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error if sessionId is missing for listBackgroundProcesses', () => {
|
||||
expect(() =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
ShellExecutionService.listBackgroundProcesses(undefined as any),
|
||||
).toThrow('Session ID is required');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Binary Output', () => {
|
||||
|
||||
Reference in New Issue
Block a user