Merge pull request #311473 from microsoft/connor4312/terminal-nointeract

agentHost: set non-interactive env vars for tool-triggered terminals
This commit is contained in:
Connor Peet
2026-04-20 14:17:05 -07:00
committed by GitHub
parent 2b6f58f8e1
commit dd50d09337
3 changed files with 19 additions and 6 deletions

View File

@@ -38,7 +38,7 @@ export interface ICommandFinishedEvent {
*/
export interface IAgentHostTerminalManager {
readonly _serviceBrand: undefined;
createTerminal(params: ICreateTerminalParams, options?: { shell?: string; preventShellHistory?: boolean }): Promise<void>;
createTerminal(params: ICreateTerminalParams, options?: { shell?: string; preventShellHistory?: boolean; nonInteractive?: boolean }): Promise<void>;
writeInput(uri: string, data: string): void;
onData(uri: string, cb: (data: string) => void): IDisposable;
onExit(uri: string, cb: (exitCode: number) => void): IDisposable;
@@ -172,7 +172,7 @@ export class AgentHostTerminalManager extends Disposable implements IAgentHostTe
* Create a new terminal backed by node-pty.
* Spawns the user's default shell.
*/
async createTerminal(params: ICreateTerminalParams, options?: { shell?: string; preventShellHistory?: boolean }): Promise<void> {
async createTerminal(params: ICreateTerminalParams, options?: { shell?: string; preventShellHistory?: boolean; nonInteractive?: boolean }): Promise<void> {
const uri = params.terminal;
if (this._terminals.has(uri)) {
throw new Error(`Terminal already exists: ${uri}`);
@@ -199,6 +199,18 @@ export class AgentHostTerminalManager extends Disposable implements IAgentHostTe
// prevents agent-executed commands from polluting the user's shell history.
env['VSCODE_PREVENT_SHELL_HISTORY'] = '1';
}
if (options?.nonInteractive) {
// Suppress paging and interactive prompts so that tool-spawned
// terminals produce clean, machine-friendly output. An empty
// string disables paging in git, less, and most CLI tools and
// is safe on all platforms (unlike 'cat' which isn't on Windows PATH).
env['LC_ALL'] = 'C.UTF-8';
env['PAGER'] = '';
env['GIT_PAGER'] = '';
env['GH_PAGER'] = '';
env['GIT_TERMINAL_PROMPT'] = '0';
env['DEBIAN_FRONTEND'] = 'noninteractive';
}
let shellArgs: string[] = [];
const injection = await getShellIntegrationInjection(

View File

@@ -109,7 +109,7 @@ export class ShellManager {
claim,
name: shellDisplayName,
cwd: cwd ?? this._workingDirectory?.fsPath,
}, { shell: getShellExecutable(shellType), preventShellHistory: true });
}, { shell: getShellExecutable(shellType), preventShellHistory: true, nonInteractive: true });
const shell: IManagedShell = { id, terminalUri, shellType };
this._shells.set(id, shell);

View File

@@ -19,9 +19,9 @@ import { ShellManager, prefixForHistorySuppression } from '../../node/copilot/co
class TestAgentHostTerminalManager implements IAgentHostTerminalManager {
declare readonly _serviceBrand: undefined;
readonly created: { params: ICreateTerminalParams; options?: { shell?: string; preventShellHistory?: boolean } }[] = [];
readonly created: { params: ICreateTerminalParams; options?: { shell?: string; preventShellHistory?: boolean; nonInteractive?: boolean } }[] = [];
async createTerminal(params: ICreateTerminalParams, options?: { shell?: string; preventShellHistory?: boolean }): Promise<void> {
async createTerminal(params: ICreateTerminalParams, options?: { shell?: string; preventShellHistory?: boolean; nonInteractive?: boolean }): Promise<void> {
this.created.push({ params, options });
}
writeInput(): void { }
@@ -66,7 +66,7 @@ suite('CopilotShellTools', () => {
]);
});
test('opts every managed shell into shell-history suppression', async () => {
test('opts every managed shell into shell-history suppression and non-interactive mode', async () => {
const terminalManager = new TestAgentHostTerminalManager();
const services = new ServiceCollection();
services.set(ILogService, new NullLogService());
@@ -79,6 +79,7 @@ suite('CopilotShellTools', () => {
assert.strictEqual(terminalManager.created.length, 1);
assert.strictEqual(terminalManager.created[0].options?.preventShellHistory, true);
assert.strictEqual(terminalManager.created[0].options?.nonInteractive, true);
});
test('prefixForHistorySuppression prepends a space for POSIX shells, no-op for PowerShell', () => {