mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 09:17:53 +00:00
avoid loading and initializing CLI config twice in non-interactive mode (#5793)
This commit is contained in:
@@ -8,6 +8,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ShellTool, EditTool, WriteFileTool } from '@google/gemini-cli-core';
|
||||
import { loadCliConfig, parseArguments } from './config.js';
|
||||
import { Settings } from './settings.js';
|
||||
import { Extension } from './extension.js';
|
||||
@@ -561,6 +562,17 @@ describe('mergeMcpServers', () => {
|
||||
});
|
||||
|
||||
describe('mergeExcludeTools', () => {
|
||||
const defaultExcludes = [ShellTool.Name, EditTool.Name, WriteFileTool.Name];
|
||||
const originalIsTTY = process.stdin.isTTY;
|
||||
|
||||
beforeEach(() => {
|
||||
process.stdin.isTTY = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.stdin.isTTY = originalIsTTY;
|
||||
});
|
||||
|
||||
it('should merge excludeTools from settings and extensions', async () => {
|
||||
const settings: Settings = { excludeTools: ['tool1', 'tool2'] };
|
||||
const extensions: Extension[] = [
|
||||
@@ -655,7 +667,8 @@ describe('mergeExcludeTools', () => {
|
||||
expect(config.getExcludeTools()).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('should return an empty array when no excludeTools are specified', async () => {
|
||||
it('should return an empty array when no excludeTools are specified and it is interactive', async () => {
|
||||
process.stdin.isTTY = true;
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
process.argv = ['node', 'script.js'];
|
||||
@@ -669,6 +682,21 @@ describe('mergeExcludeTools', () => {
|
||||
expect(config.getExcludeTools()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return default excludes when no excludeTools are specified and it is not interactive', async () => {
|
||||
process.stdin.isTTY = false;
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
process.argv = ['node', 'script.js', '-p', 'test'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
extensions,
|
||||
'test-session',
|
||||
argv,
|
||||
);
|
||||
expect(config.getExcludeTools()).toEqual(defaultExcludes);
|
||||
});
|
||||
|
||||
it('should handle settings with excludeTools but no extensions', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments();
|
||||
@@ -1214,3 +1242,115 @@ describe('loadCliConfig chatCompression', () => {
|
||||
expect(config.getChatCompression()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCliConfig tool exclusions', () => {
|
||||
const originalArgv = process.argv;
|
||||
const originalEnv = { ...process.env };
|
||||
const originalIsTTY = process.stdin.isTTY;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
|
||||
process.env.GEMINI_API_KEY = 'test-api-key';
|
||||
process.stdin.isTTY = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.argv = originalArgv;
|
||||
process.env = originalEnv;
|
||||
process.stdin.isTTY = originalIsTTY;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should not exclude interactive tools in interactive mode without YOLO', async () => {
|
||||
process.stdin.isTTY = true;
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig({}, [], 'test-session', argv);
|
||||
expect(config.getExcludeTools()).not.toContain('run_shell_command');
|
||||
expect(config.getExcludeTools()).not.toContain('replace');
|
||||
expect(config.getExcludeTools()).not.toContain('write_file');
|
||||
});
|
||||
|
||||
it('should not exclude interactive tools in interactive mode with YOLO', async () => {
|
||||
process.stdin.isTTY = true;
|
||||
process.argv = ['node', 'script.js', '--yolo'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig({}, [], 'test-session', argv);
|
||||
expect(config.getExcludeTools()).not.toContain('run_shell_command');
|
||||
expect(config.getExcludeTools()).not.toContain('replace');
|
||||
expect(config.getExcludeTools()).not.toContain('write_file');
|
||||
});
|
||||
|
||||
it('should exclude interactive tools in non-interactive mode without YOLO', async () => {
|
||||
process.stdin.isTTY = false;
|
||||
process.argv = ['node', 'script.js', '-p', 'test'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig({}, [], 'test-session', argv);
|
||||
expect(config.getExcludeTools()).toContain('run_shell_command');
|
||||
expect(config.getExcludeTools()).toContain('replace');
|
||||
expect(config.getExcludeTools()).toContain('write_file');
|
||||
});
|
||||
|
||||
it('should not exclude interactive tools in non-interactive mode with YOLO', async () => {
|
||||
process.stdin.isTTY = false;
|
||||
process.argv = ['node', 'script.js', '-p', 'test', '--yolo'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig({}, [], 'test-session', argv);
|
||||
expect(config.getExcludeTools()).not.toContain('run_shell_command');
|
||||
expect(config.getExcludeTools()).not.toContain('replace');
|
||||
expect(config.getExcludeTools()).not.toContain('write_file');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCliConfig interactive', () => {
|
||||
const originalArgv = process.argv;
|
||||
const originalEnv = { ...process.env };
|
||||
const originalIsTTY = process.stdin.isTTY;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
|
||||
process.env.GEMINI_API_KEY = 'test-api-key';
|
||||
process.stdin.isTTY = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.argv = originalArgv;
|
||||
process.env = originalEnv;
|
||||
process.stdin.isTTY = originalIsTTY;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should be interactive if isTTY and no prompt', async () => {
|
||||
process.stdin.isTTY = true;
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig({}, [], 'test-session', argv);
|
||||
expect(config.isInteractive()).toBe(true);
|
||||
});
|
||||
|
||||
it('should be interactive if prompt-interactive is set', async () => {
|
||||
process.stdin.isTTY = false;
|
||||
process.argv = ['node', 'script.js', '--prompt-interactive', 'test'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig({}, [], 'test-session', argv);
|
||||
expect(config.isInteractive()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not be interactive if not isTTY and no prompt', async () => {
|
||||
process.stdin.isTTY = false;
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig({}, [], 'test-session', argv);
|
||||
expect(config.isInteractive()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not be interactive if prompt is set', async () => {
|
||||
process.stdin.isTTY = true;
|
||||
process.argv = ['node', 'script.js', '--prompt', 'test'];
|
||||
const argv = await parseArguments();
|
||||
const config = await loadCliConfig({}, [], 'test-session', argv);
|
||||
expect(config.isInteractive()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,9 @@ import {
|
||||
FileDiscoveryService,
|
||||
TelemetryTarget,
|
||||
FileFilteringOptions,
|
||||
ShellTool,
|
||||
EditTool,
|
||||
WriteFileTool,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { Settings } from './settings.js';
|
||||
|
||||
@@ -365,7 +368,22 @@ export async function loadCliConfig(
|
||||
);
|
||||
|
||||
let mcpServers = mergeMcpServers(settings, activeExtensions);
|
||||
const excludeTools = mergeExcludeTools(settings, activeExtensions);
|
||||
const question = argv.promptInteractive || argv.prompt || '';
|
||||
const approvalMode =
|
||||
argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT;
|
||||
const interactive =
|
||||
!!argv.promptInteractive || (process.stdin.isTTY && question.length === 0);
|
||||
// In non-interactive and non-yolo mode, exclude interactive built in tools.
|
||||
const extraExcludes =
|
||||
!interactive && approvalMode !== ApprovalMode.YOLO
|
||||
? [ShellTool.Name, EditTool.Name, WriteFileTool.Name]
|
||||
: undefined;
|
||||
|
||||
const excludeTools = mergeExcludeTools(
|
||||
settings,
|
||||
activeExtensions,
|
||||
extraExcludes,
|
||||
);
|
||||
const blockedMcpServers: Array<{ name: string; extensionName: string }> = [];
|
||||
|
||||
if (!argv.allowedMcpServerNames) {
|
||||
@@ -427,7 +445,7 @@ export async function loadCliConfig(
|
||||
settings.loadMemoryFromIncludeDirectories ||
|
||||
false,
|
||||
debugMode,
|
||||
question: argv.promptInteractive || argv.prompt || '',
|
||||
question,
|
||||
fullContext: argv.allFiles || argv.all_files || false,
|
||||
coreTools: settings.coreTools || undefined,
|
||||
excludeTools,
|
||||
@@ -437,7 +455,7 @@ export async function loadCliConfig(
|
||||
mcpServers,
|
||||
userMemory: memoryContent,
|
||||
geminiMdFileCount: fileCount,
|
||||
approvalMode: argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT,
|
||||
approvalMode,
|
||||
showMemoryUsage:
|
||||
argv.showMemoryUsage ||
|
||||
argv.show_memory_usage ||
|
||||
@@ -486,6 +504,7 @@ export async function loadCliConfig(
|
||||
ideModeFeature,
|
||||
chatCompression: settings.chatCompression,
|
||||
folderTrustFeature,
|
||||
interactive,
|
||||
folderTrust,
|
||||
});
|
||||
}
|
||||
@@ -514,8 +533,12 @@ function mergeMcpServers(settings: Settings, extensions: Extension[]) {
|
||||
function mergeExcludeTools(
|
||||
settings: Settings,
|
||||
extensions: Extension[],
|
||||
extraExcludes?: string[] | undefined,
|
||||
): string[] {
|
||||
const allExcludeTools = new Set(settings.excludeTools || []);
|
||||
const allExcludeTools = new Set([
|
||||
...(settings.excludeTools || []),
|
||||
...(extraExcludes || []),
|
||||
]);
|
||||
for (const extension of extensions) {
|
||||
for (const tool of extension.config.excludeTools || []) {
|
||||
allExcludeTools.add(tool);
|
||||
|
||||
Reference in New Issue
Block a user