mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-01 19:03:42 +00:00
Implement background process monitoring and inspection tools (#23799)
This commit is contained in:
105
integration-tests/shell-background.test.ts
Normal file
105
integration-tests/shell-background.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
describe('shell-background-tools', () => {
|
||||
let rig: TestRig;
|
||||
|
||||
beforeEach(() => {
|
||||
rig = new TestRig();
|
||||
});
|
||||
|
||||
afterEach(async () => await rig.cleanup());
|
||||
|
||||
it('should run a command in the background, list it, and read its output', async () => {
|
||||
// We use a fake responses file to make the test deterministic and run in CI.
|
||||
rig.setup('shell-background-workflow', {
|
||||
fakeResponsesPath: join(__dirname, 'shell-background.responses'),
|
||||
settings: {
|
||||
tools: {
|
||||
core: [
|
||||
'run_shell_command',
|
||||
'list_background_processes',
|
||||
'read_background_output',
|
||||
],
|
||||
},
|
||||
hooksConfig: {
|
||||
enabled: true,
|
||||
},
|
||||
hooks: {
|
||||
BeforeTool: [
|
||||
{
|
||||
matcher: 'run_shell_command',
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
// This hook intercepts run_shell_command.
|
||||
// If is_background is true, it returns a mock result with PID 12345.
|
||||
// It also creates the mock log file that read_background_output expects.
|
||||
command: `node -e "
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
|
||||
const args = JSON.parse(input.tool_call.args);
|
||||
|
||||
if (args.is_background) {
|
||||
const logDir = path.join(process.env.GEMINI_CLI_HOME, 'background-processes');
|
||||
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(logDir, 'background-12345.log'), 'hello-from-background\\n');
|
||||
|
||||
console.log(JSON.stringify({
|
||||
decision: 'replace',
|
||||
hookSpecificOutput: {
|
||||
result: {
|
||||
llmContent: 'Command moved to background (PID: 12345). Output hidden. Press Ctrl+B to view.',
|
||||
data: { pid: 12345, command: args.command }
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
console.log(JSON.stringify({ decision: 'allow' }));
|
||||
}
|
||||
"`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const run = await rig.runInteractive({ approvalMode: 'yolo' });
|
||||
|
||||
// 1. Start a background process
|
||||
// We use a command that stays alive for a bit to ensure it shows up in lists
|
||||
await run.type(
|
||||
"Run 'sleep 10 && echo hello-from-background' in the background.",
|
||||
);
|
||||
await run.type('\r');
|
||||
|
||||
// Wait for the model's canned response acknowledging the start
|
||||
await run.expectText('background', 30000);
|
||||
|
||||
// 2. List background processes
|
||||
await run.type('List my background processes.');
|
||||
await run.type('\r');
|
||||
// Wait for the model's canned response showing the list
|
||||
await run.expectText('hello-from-background', 30000);
|
||||
|
||||
// 3. Read the output
|
||||
await run.type('Read the output of that process.');
|
||||
await run.type('\r');
|
||||
// Wait for the model's canned response showing the output
|
||||
await run.expectText('hello-from-background', 30000);
|
||||
}, 60000);
|
||||
});
|
||||
Reference in New Issue
Block a user