mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-23 17:56:55 -07:00
feat: Implement background shell commands (#14849)
This commit is contained in:
@@ -43,6 +43,7 @@ import type {
|
||||
ServerGeminiStreamEvent as GeminiEvent,
|
||||
ThoughtSummary,
|
||||
ToolCallRequestInfo,
|
||||
ToolCallResponseInfo,
|
||||
GeminiErrorEventValue,
|
||||
RetryAttemptPayload,
|
||||
ToolCallConfirmationDetails,
|
||||
@@ -72,6 +73,7 @@ import {
|
||||
type TrackedCompletedToolCall,
|
||||
type TrackedCancelledToolCall,
|
||||
type TrackedWaitingToolCall,
|
||||
type TrackedExecutingToolCall,
|
||||
} from './useToolScheduler.js';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
@@ -79,12 +81,34 @@ import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
|
||||
type ToolResponseWithParts = ToolCallResponseInfo & {
|
||||
llmContent?: PartListUnion;
|
||||
};
|
||||
|
||||
interface ShellToolData {
|
||||
pid?: number;
|
||||
command?: string;
|
||||
initialOutput?: string;
|
||||
}
|
||||
|
||||
enum StreamProcessingStatus {
|
||||
Completed,
|
||||
UserCancelled,
|
||||
Error,
|
||||
}
|
||||
|
||||
function isShellToolData(data: unknown): data is ShellToolData {
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
return false;
|
||||
}
|
||||
const d = data as Partial<ShellToolData>;
|
||||
return (
|
||||
(d.pid === undefined || typeof d.pid === 'number') &&
|
||||
(d.command === undefined || typeof d.command === 'string') &&
|
||||
(d.initialOutput === undefined || typeof d.initialOutput === 'string')
|
||||
);
|
||||
}
|
||||
|
||||
function showCitations(settings: LoadedSettings): boolean {
|
||||
const enabled = settings.merged.ui.showCitations;
|
||||
if (enabled !== undefined) {
|
||||
@@ -401,14 +425,11 @@ export const useGeminiStream = (
|
||||
}, [toolCalls, pushedToolCallIds, config]);
|
||||
|
||||
const activeToolPtyId = useMemo(() => {
|
||||
const executingShellTool = toolCalls?.find(
|
||||
const executingShellTool = toolCalls.find(
|
||||
(tc) =>
|
||||
tc.status === 'executing' && tc.request.name === 'run_shell_command',
|
||||
);
|
||||
if (executingShellTool) {
|
||||
return (executingShellTool as { pid?: number }).pid;
|
||||
}
|
||||
return undefined;
|
||||
return (executingShellTool as TrackedExecutingToolCall | undefined)?.pid;
|
||||
}, [toolCalls]);
|
||||
|
||||
const lastQueryRef = useRef<PartListUnion | null>(null);
|
||||
@@ -426,18 +447,30 @@ export const useGeminiStream = (
|
||||
await done;
|
||||
setIsResponding(false);
|
||||
}, []);
|
||||
const { handleShellCommand, activeShellPtyId, lastShellOutputTime } =
|
||||
useShellCommandProcessor(
|
||||
addItem,
|
||||
setPendingHistoryItem,
|
||||
onExec,
|
||||
onDebugMessage,
|
||||
config,
|
||||
geminiClient,
|
||||
setShellInputFocused,
|
||||
terminalWidth,
|
||||
terminalHeight,
|
||||
);
|
||||
|
||||
const {
|
||||
handleShellCommand,
|
||||
activeShellPtyId,
|
||||
lastShellOutputTime,
|
||||
backgroundShellCount,
|
||||
isBackgroundShellVisible,
|
||||
toggleBackgroundShell,
|
||||
backgroundCurrentShell,
|
||||
registerBackgroundShell,
|
||||
dismissBackgroundShell,
|
||||
backgroundShells,
|
||||
} = useShellCommandProcessor(
|
||||
addItem,
|
||||
setPendingHistoryItem,
|
||||
onExec,
|
||||
onDebugMessage,
|
||||
config,
|
||||
geminiClient,
|
||||
setShellInputFocused,
|
||||
terminalWidth,
|
||||
terminalHeight,
|
||||
activeToolPtyId,
|
||||
);
|
||||
|
||||
const activePtyId = activeShellPtyId || activeToolPtyId;
|
||||
|
||||
@@ -1404,6 +1437,25 @@ export const useGeminiStream = (
|
||||
!processedMemoryToolsRef.current.has(t.request.callId),
|
||||
);
|
||||
|
||||
// Handle backgrounded shell tools
|
||||
completedAndReadyToSubmitTools.forEach((t) => {
|
||||
const isShell = t.request.name === 'run_shell_command';
|
||||
// Access result from the tracked tool call response
|
||||
const response = t.response as ToolResponseWithParts;
|
||||
const rawData = response?.data;
|
||||
const data = isShellToolData(rawData) ? rawData : undefined;
|
||||
|
||||
// Use data.pid for shell commands moved to the background.
|
||||
const pid = data?.pid;
|
||||
|
||||
if (isShell && pid) {
|
||||
const command = (data?.['command'] as string) ?? 'shell';
|
||||
const initialOutput = (data?.['initialOutput'] as string) ?? '';
|
||||
|
||||
registerBackgroundShell(pid, command, initialOutput);
|
||||
}
|
||||
});
|
||||
|
||||
if (newSuccessfulMemorySaves.length > 0) {
|
||||
// Perform the refresh only if there are new ones.
|
||||
void performMemoryRefresh();
|
||||
@@ -1510,6 +1562,7 @@ export const useGeminiStream = (
|
||||
performMemoryRefresh,
|
||||
modelSwitchedFromQuotaError,
|
||||
addItem,
|
||||
registerBackgroundShell,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1599,6 +1652,12 @@ export const useGeminiStream = (
|
||||
activePtyId,
|
||||
loopDetectionConfirmationRequest,
|
||||
lastOutputTime,
|
||||
backgroundShellCount,
|
||||
isBackgroundShellVisible,
|
||||
toggleBackgroundShell,
|
||||
backgroundCurrentShell,
|
||||
backgroundShells,
|
||||
dismissBackgroundShell,
|
||||
retryStatus,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user