feat(shell): enable interactive commands with virtual terminal (#6694)

This commit is contained in:
Gal Zahavi
2025-09-11 13:27:27 -07:00
committed by GitHub
parent 8969a232ec
commit 181898cb5d
43 changed files with 2345 additions and 324 deletions
+31 -3
View File
@@ -102,6 +102,10 @@ export const useGeminiStream = (
setModelSwitchedFromQuotaError: React.Dispatch<React.SetStateAction<boolean>>,
onEditorClose: () => void,
onCancelSubmit: () => void,
setShellInputFocused: (value: boolean) => void,
terminalWidth: number,
terminalHeight: number,
isShellFocused?: boolean,
) => {
const [initError, setInitError] = useState<string | null>(null);
const abortControllerRef = useRef<AbortController | null>(null);
@@ -141,7 +145,6 @@ export const useGeminiStream = (
}
},
config,
setPendingHistoryItem,
getPreferredEditor,
onEditorClose,
);
@@ -152,6 +155,17 @@ export const useGeminiStream = (
[toolCalls],
);
const activeToolPtyId = useMemo(() => {
const executingShellTool = toolCalls?.find(
(tc) =>
tc.status === 'executing' && tc.request.name === 'run_shell_command',
);
if (executingShellTool) {
return (executingShellTool as { pid?: number }).pid;
}
return undefined;
}, [toolCalls]);
const loopDetectedRef = useRef(false);
const [
loopDetectionConfirmationRequest,
@@ -165,15 +179,26 @@ export const useGeminiStream = (
await done;
setIsResponding(false);
}, []);
const { handleShellCommand } = useShellCommandProcessor(
const { handleShellCommand, activeShellPtyId } = useShellCommandProcessor(
addItem,
setPendingHistoryItem,
onExec,
onDebugMessage,
config,
geminiClient,
setShellInputFocused,
terminalWidth,
terminalHeight,
);
const activePtyId = activeShellPtyId || activeToolPtyId;
useEffect(() => {
if (!activePtyId) {
setShellInputFocused(false);
}
}, [activePtyId, setShellInputFocused]);
const streamingState = useMemo(() => {
if (toolCalls.some((tc) => tc.status === 'awaiting_approval')) {
return StreamingState.WaitingForConfirmation;
@@ -240,17 +265,19 @@ export const useGeminiStream = (
setPendingHistoryItem(null);
onCancelSubmit();
setIsResponding(false);
setShellInputFocused(false);
}, [
streamingState,
addItem,
setPendingHistoryItem,
onCancelSubmit,
pendingHistoryItemRef,
setShellInputFocused,
]);
useKeypress(
(key) => {
if (key.name === 'escape') {
if (key.name === 'escape' && !isShellFocused) {
cancelOngoingRequest();
}
},
@@ -1074,6 +1101,7 @@ export const useGeminiStream = (
pendingHistoryItems,
thought,
cancelOngoingRequest,
activePtyId,
loopDetectionConfirmationRequest,
};
};