mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-24 20:14:44 -07:00
Implement background process monitoring and inspection tools (#23799)
This commit is contained in:
@@ -103,6 +103,8 @@ export interface ShellExecutionConfig {
|
||||
maxSerializedLines?: number;
|
||||
sandboxConfig?: SandboxConfig;
|
||||
backgroundCompletionBehavior?: 'inject' | 'notify' | 'silent';
|
||||
originalCommand?: string;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,6 +116,8 @@ interface ActivePty {
|
||||
ptyProcess: IPty;
|
||||
headlessTerminal: pkg.Terminal;
|
||||
maxSerializedLines?: number;
|
||||
command: string;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
interface ActiveChildProcess {
|
||||
@@ -124,6 +128,8 @@ interface ActiveChildProcess {
|
||||
sniffChunks: Buffer[];
|
||||
binaryBytesReceived: number;
|
||||
};
|
||||
command: string;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
const findLastContentLine = (
|
||||
@@ -230,11 +236,28 @@ const writeBufferToLogStream = (
|
||||
*
|
||||
*/
|
||||
|
||||
export type BackgroundProcess = {
|
||||
pid: number;
|
||||
command: string;
|
||||
status: 'running' | 'exited';
|
||||
exitCode?: number | null;
|
||||
signal?: number | null;
|
||||
};
|
||||
|
||||
export type BackgroundProcessRecord = Omit<BackgroundProcess, 'pid'> & {
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
};
|
||||
|
||||
export class ShellExecutionService {
|
||||
private static activePtys = new Map<number, ActivePty>();
|
||||
private static activeChildProcesses = new Map<number, ActiveChildProcess>();
|
||||
private static backgroundLogPids = new Set<number>();
|
||||
private static backgroundLogStreams = new Map<number, fs.WriteStream>();
|
||||
private static backgroundProcessHistory = new Map<
|
||||
string, // sessionId
|
||||
Map<number, BackgroundProcessRecord>
|
||||
>();
|
||||
|
||||
static getLogDir(): string {
|
||||
return path.join(Storage.getGlobalTempDir(), 'background-processes');
|
||||
@@ -519,10 +542,12 @@ export class ShellExecutionService {
|
||||
binaryBytesReceived: 0,
|
||||
};
|
||||
|
||||
if (child.pid) {
|
||||
if (child.pid !== undefined) {
|
||||
this.activeChildProcesses.set(child.pid, {
|
||||
process: child,
|
||||
state,
|
||||
command: shellExecutionConfig.originalCommand ?? commandToExecute,
|
||||
sessionId: shellExecutionConfig.sessionId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -696,6 +721,17 @@ export class ShellExecutionService {
|
||||
exitCode,
|
||||
signal: exitSignal,
|
||||
};
|
||||
|
||||
const sessionId = shellExecutionConfig.sessionId ?? 'default';
|
||||
const history =
|
||||
ShellExecutionService.backgroundProcessHistory.get(sessionId);
|
||||
const historyItem = history?.get(pid);
|
||||
if (historyItem) {
|
||||
historyItem.status = 'exited';
|
||||
historyItem.exitCode = exitCode ?? undefined;
|
||||
historyItem.signal = exitSignal ?? undefined;
|
||||
historyItem.endTime = Date.now();
|
||||
}
|
||||
onOutputEvent(event);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
@@ -849,6 +885,8 @@ export class ShellExecutionService {
|
||||
ptyProcess,
|
||||
headlessTerminal,
|
||||
maxSerializedLines: shellExecutionConfig.maxSerializedLines,
|
||||
command: shellExecutionConfig.originalCommand ?? commandToExecute,
|
||||
sessionId: shellExecutionConfig.sessionId,
|
||||
});
|
||||
|
||||
const result = ExecutionLifecycleService.attachExecution(ptyPid, {
|
||||
@@ -1116,6 +1154,17 @@ export class ShellExecutionService {
|
||||
exitCode,
|
||||
signal: signal ?? null,
|
||||
};
|
||||
|
||||
const sessionId = shellExecutionConfig.sessionId ?? 'default';
|
||||
const history =
|
||||
ShellExecutionService.backgroundProcessHistory.get(sessionId);
|
||||
const historyItem = history?.get(ptyPid);
|
||||
if (historyItem) {
|
||||
historyItem.status = 'exited';
|
||||
historyItem.exitCode = exitCode;
|
||||
historyItem.signal = signal ?? null;
|
||||
historyItem.endTime = Date.now();
|
||||
}
|
||||
onOutputEvent(event);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
@@ -1269,16 +1318,57 @@ export class ShellExecutionService {
|
||||
*
|
||||
* @param pid The process ID of the target PTY.
|
||||
*/
|
||||
static background(pid: number): void {
|
||||
static background(pid: number, sessionId?: string, command?: string): void {
|
||||
const activePty = this.activePtys.get(pid);
|
||||
const activeChild = this.activeChildProcesses.get(pid);
|
||||
|
||||
const resolvedSessionId =
|
||||
sessionId ?? activePty?.sessionId ?? activeChild?.sessionId;
|
||||
const resolvedCommand =
|
||||
command ??
|
||||
activePty?.command ??
|
||||
activeChild?.command ??
|
||||
'unknown command';
|
||||
|
||||
if (!resolvedSessionId) {
|
||||
throw new Error('Session ID is required for background operations');
|
||||
}
|
||||
|
||||
const MAX_BACKGROUND_PROCESS_HISTORY_SIZE = 100;
|
||||
const history =
|
||||
this.backgroundProcessHistory.get(resolvedSessionId) ??
|
||||
new Map<
|
||||
number,
|
||||
{
|
||||
command: string;
|
||||
status: 'running' | 'exited';
|
||||
exitCode?: number | null;
|
||||
signal?: number | null;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
}
|
||||
>();
|
||||
|
||||
if (history.size >= MAX_BACKGROUND_PROCESS_HISTORY_SIZE) {
|
||||
const oldestPid = history.keys().next().value;
|
||||
if (oldestPid !== undefined) {
|
||||
history.delete(oldestPid);
|
||||
}
|
||||
}
|
||||
|
||||
history.set(pid, {
|
||||
command: resolvedCommand,
|
||||
status: 'running',
|
||||
startTime: Date.now(),
|
||||
});
|
||||
this.backgroundProcessHistory.set(resolvedSessionId, history);
|
||||
|
||||
// Set up background logging
|
||||
const logPath = this.getLogFilePath(pid);
|
||||
const logDir = this.getLogDir();
|
||||
try {
|
||||
mkdirSync(logDir, { recursive: true });
|
||||
const stream = fs.createWriteStream(logPath, { flags: 'w' });
|
||||
mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
||||
const stream = fs.createWriteStream(logPath, { flags: 'wx' });
|
||||
stream.on('error', (err) => {
|
||||
debugLogger.warn('Background log stream error:', err);
|
||||
});
|
||||
@@ -1391,4 +1481,20 @@ export class ShellExecutionService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static listBackgroundProcesses(sessionId: string): BackgroundProcess[] {
|
||||
if (!sessionId) {
|
||||
throw new Error('Session ID is required');
|
||||
}
|
||||
const history = this.backgroundProcessHistory.get(sessionId);
|
||||
if (!history) return [];
|
||||
|
||||
return Array.from(history.entries()).map(([pid, info]) => ({
|
||||
pid,
|
||||
command: info.command,
|
||||
status: info.status,
|
||||
exitCode: info.exitCode,
|
||||
signal: info.signal,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user