Persistent shell support

This commit is contained in:
jacob314
2026-03-06 13:31:54 -08:00
parent 3d4956aa57
commit 39d1a1bf2f
19 changed files with 1119 additions and 47 deletions

View File

@@ -783,6 +783,7 @@ export async function loadCliConfig(
useAlternateBuffer: settings.ui?.useAlternateBuffer,
useRipgrep: settings.tools?.useRipgrep,
enableInteractiveShell: settings.tools?.shell?.enableInteractiveShell,
enablePersistentShell: settings.tools?.shell?.enablePersistentShell,
shellToolInactivityTimeout: settings.tools?.shell?.inactivityTimeout,
enableShellOutputEfficiency:
settings.tools?.shell?.enableShellOutputEfficiency ?? true,

View File

@@ -1282,6 +1282,17 @@ const SETTINGS_SCHEMA = {
`,
showInDialog: true,
},
enablePersistentShell: {
type: 'boolean',
label: 'Enable Persistent Shell',
category: 'Tools',
requiresRestart: true,
default: true,
description: oneLine`
Maintain a persistent shell session across commands to preserve environment variables, directory changes, and aliases.
`,
showInDialog: true,
},
pager: {
type: 'string',
label: 'Pager',

View File

@@ -77,6 +77,7 @@ describe('ShellProcessor', () => {
getTargetDir: vi.fn().mockReturnValue('/test/dir'),
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
getEnableInteractiveShell: vi.fn().mockReturnValue(false),
getEnablePersistentShell: vi.fn().mockReturnValue(false),
getShellExecutionConfig: vi.fn().mockReturnValue({}),
getPolicyEngine: vi.fn().mockReturnValue({
check: mockPolicyEngineCheck,

View File

@@ -167,6 +167,7 @@ export class ShellProcessor implements IPromptProcessor {
...config.getShellExecutionConfig(),
defaultFg: activeTheme.colors.Foreground,
defaultBg: activeTheme.colors.Background,
persistent: config.getEnablePersistentShell(),
};
const { result } = await ShellExecutionService.execute(
injection.resolvedCommand,

View File

@@ -9,6 +9,7 @@ import {
SessionEndReason,
SessionStartSource,
flushTelemetry,
ShellExecutionService,
} from '@google/gemini-cli-core';
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
@@ -70,6 +71,7 @@ export const clearCommand: SlashCommand = {
}
uiTelemetryService.setLastPromptTokenCount(0);
ShellExecutionService.clearPersistentSession();
context.ui.clear();
if (result?.systemMessage) {

View File

@@ -106,6 +106,7 @@ describe('useShellCommandProcessor', () => {
mockConfig = {
getTargetDir: () => '/test/dir',
getEnableInteractiveShell: () => false,
getEnablePersistentShell: () => false,
getShellExecutionConfig: () => ({
terminalHeight: 20,
terminalWidth: 80,

View File

@@ -283,6 +283,7 @@ export const useShellCommandProcessor = (
let commandToExecute = rawQuery;
let pwdFilePath: string | undefined;
const isPersistent = config.getEnablePersistentShell();
// On non-windows, wrap the command to capture the final working directory.
if (!isWindows) {
let command = rawQuery.trim();
@@ -292,7 +293,11 @@ export const useShellCommandProcessor = (
if (!command.endsWith(';') && !command.endsWith('&')) {
command += ';';
}
commandToExecute = `{ ${command} }; __code=$?; pwd > "${pwdFilePath}"; exit $__code`;
if (isPersistent) {
commandToExecute = `{ ${command} }; __code=$?; pwd > "${pwdFilePath}"; (exit $__code)`;
} else {
commandToExecute = `{ ${command} }; __code=$?; pwd > "${pwdFilePath}"; exit $__code`;
}
}
const executeCommand = async () => {
@@ -324,7 +329,9 @@ export const useShellCommandProcessor = (
};
abortSignal.addEventListener('abort', abortHandler, { once: true });
onDebugMessage(`Executing in ${targetDir}: ${commandToExecute}`);
onDebugMessage(
`Executing in ${targetDir} (persistent=${isPersistent}): ${commandToExecute}`,
);
try {
const activeTheme = themeManager.getActiveTheme();
@@ -334,6 +341,7 @@ export const useShellCommandProcessor = (
terminalHeight,
defaultFg: activeTheme.colors.Foreground,
defaultBg: activeTheme.colors.Background,
persistent: isPersistent,
};
const { pid, result: resultPromise } =

View File

@@ -1799,7 +1799,6 @@ export const useGeminiStream = (
addItem,
registerBackgroundShell,
consumeUserHint,
config,
isLowErrorVerbosity,
maybeAddSuppressedToolErrorNote,
maybeAddLowVerbosityFailureNote,