feat: Add /config refresh command (#4993)
Co-authored-by: Bryan Morgan <bryanmorgan@google.com>
This commit is contained in:
parent
e275441651
commit
0170791800
|
@ -29,6 +29,7 @@ import { statsCommand } from '../ui/commands/statsCommand.js';
|
||||||
import { themeCommand } from '../ui/commands/themeCommand.js';
|
import { themeCommand } from '../ui/commands/themeCommand.js';
|
||||||
import { toolsCommand } from '../ui/commands/toolsCommand.js';
|
import { toolsCommand } from '../ui/commands/toolsCommand.js';
|
||||||
import { vimCommand } from '../ui/commands/vimCommand.js';
|
import { vimCommand } from '../ui/commands/vimCommand.js';
|
||||||
|
import { configCommand } from '../ui/commands/configCommand.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the core, hard-coded slash commands that are an integral part
|
* Loads the core, hard-coded slash commands that are an integral part
|
||||||
|
@ -54,6 +55,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||||
compressCommand,
|
compressCommand,
|
||||||
copyCommand,
|
copyCommand,
|
||||||
corgiCommand,
|
corgiCommand,
|
||||||
|
configCommand,
|
||||||
docsCommand,
|
docsCommand,
|
||||||
editorCommand,
|
editorCommand,
|
||||||
extensionsCommand,
|
extensionsCommand,
|
||||||
|
|
|
@ -39,8 +39,12 @@ import { EditorSettingsDialog } from './components/EditorSettingsDialog.js';
|
||||||
import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js';
|
import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js';
|
||||||
import { Colors } from './colors.js';
|
import { Colors } from './colors.js';
|
||||||
import { Help } from './components/Help.js';
|
import { Help } from './components/Help.js';
|
||||||
import { loadHierarchicalGeminiMemory } from '../config/config.js';
|
import {
|
||||||
import { LoadedSettings } from '../config/settings.js';
|
loadHierarchicalGeminiMemory,
|
||||||
|
loadCliConfig,
|
||||||
|
parseArguments,
|
||||||
|
} from '../config/config.js';
|
||||||
|
import { LoadedSettings, loadSettings } from '../config/settings.js';
|
||||||
import { Tips } from './components/Tips.js';
|
import { Tips } from './components/Tips.js';
|
||||||
import { ConsolePatcher } from './utils/ConsolePatcher.js';
|
import { ConsolePatcher } from './utils/ConsolePatcher.js';
|
||||||
import { registerCleanup } from '../utils/cleanup.js';
|
import { registerCleanup } from '../utils/cleanup.js';
|
||||||
|
@ -62,6 +66,7 @@ import {
|
||||||
AuthType,
|
AuthType,
|
||||||
type IdeContext,
|
type IdeContext,
|
||||||
ideContext,
|
ideContext,
|
||||||
|
sessionId,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import { validateAuthMethod } from '../config/auth.js';
|
import { validateAuthMethod } from '../config/auth.js';
|
||||||
import { useLogger } from './hooks/useLogger.js';
|
import { useLogger } from './hooks/useLogger.js';
|
||||||
|
@ -89,6 +94,7 @@ import { OverflowProvider } from './contexts/OverflowContext.js';
|
||||||
import { ShowMoreLines } from './components/ShowMoreLines.js';
|
import { ShowMoreLines } from './components/ShowMoreLines.js';
|
||||||
import { PrivacyNotice } from './privacy/PrivacyNotice.js';
|
import { PrivacyNotice } from './privacy/PrivacyNotice.js';
|
||||||
import { appEvents, AppEvent } from '../utils/events.js';
|
import { appEvents, AppEvent } from '../utils/events.js';
|
||||||
|
import { loadExtensions } from '../config/extension.js';
|
||||||
|
|
||||||
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
|
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
|
||||||
|
|
||||||
|
@ -107,12 +113,14 @@ export const AppWrapper = (props: AppProps) => (
|
||||||
</SessionStatsProvider>
|
</SessionStatsProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
const App = (props: AppProps) => {
|
||||||
|
const [config, setConfig] = useState<Config>(props.config);
|
||||||
|
const [settings, setSettings] = useState<LoadedSettings>(props.settings);
|
||||||
const isFocused = useFocus();
|
const isFocused = useFocus();
|
||||||
useBracketedPaste();
|
useBracketedPaste();
|
||||||
const [updateMessage, setUpdateMessage] = useState<string | null>(null);
|
const [updateMessage, setUpdateMessage] = useState<string | null>(null);
|
||||||
const { stdout } = useStdout();
|
const { stdout } = useStdout();
|
||||||
const nightly = version.includes('nightly');
|
const nightly = props.version.includes('nightly');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkForUpdates().then(setUpdateMessage);
|
checkForUpdates().then(setUpdateMessage);
|
||||||
|
@ -307,6 +315,22 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||||
}
|
}
|
||||||
}, [config, addItem, settings.merged]);
|
}, [config, addItem, settings.merged]);
|
||||||
|
|
||||||
|
const refreshConfig = useCallback(async () => {
|
||||||
|
const newSettings = loadSettings(process.cwd());
|
||||||
|
const newExtensions = loadExtensions(process.cwd());
|
||||||
|
const argv = await parseArguments();
|
||||||
|
const newConfig = await loadCliConfig(
|
||||||
|
newSettings.merged,
|
||||||
|
newExtensions,
|
||||||
|
sessionId,
|
||||||
|
argv,
|
||||||
|
);
|
||||||
|
await newConfig.initialize();
|
||||||
|
setConfig(newConfig);
|
||||||
|
setSettings(newSettings);
|
||||||
|
setGeminiMdFileCount(newConfig.getGeminiMdFileCount());
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Watch for model changes (e.g., from Flash fallback)
|
// Watch for model changes (e.g., from Flash fallback)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkModelChange = () => {
|
const checkModelChange = () => {
|
||||||
|
@ -474,6 +498,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||||
openPrivacyNotice,
|
openPrivacyNotice,
|
||||||
toggleVimEnabled,
|
toggleVimEnabled,
|
||||||
setIsProcessing,
|
setIsProcessing,
|
||||||
|
refreshConfig,
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -777,7 +802,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||||
{!settings.merged.hideBanner && (
|
{!settings.merged.hideBanner && (
|
||||||
<Header
|
<Header
|
||||||
terminalWidth={terminalWidth}
|
terminalWidth={terminalWidth}
|
||||||
version={version}
|
version={props.version}
|
||||||
nightly={nightly}
|
nightly={nightly}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -821,7 +846,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||||
{showHelp && <Help commands={slashCommands} />}
|
{showHelp && <Help commands={slashCommands} />}
|
||||||
|
|
||||||
<Box flexDirection="column" ref={mainControlsRef}>
|
<Box flexDirection="column" ref={mainControlsRef}>
|
||||||
{startupWarnings.length > 0 && (
|
{props.startupWarnings && props.startupWarnings.length > 0 && (
|
||||||
<Box
|
<Box
|
||||||
borderStyle="round"
|
borderStyle="round"
|
||||||
borderColor={Colors.AccentYellow}
|
borderColor={Colors.AccentYellow}
|
||||||
|
@ -829,7 +854,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||||
marginY={1}
|
marginY={1}
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
>
|
>
|
||||||
{startupWarnings.map((warning, index) => (
|
{props.startupWarnings.map((warning, index) => (
|
||||||
<Text key={index} color={Colors.AccentYellow}>
|
<Text key={index} color={Colors.AccentYellow}>
|
||||||
{warning}
|
{warning}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
CommandKind,
|
||||||
|
SlashCommand,
|
||||||
|
SlashCommandActionReturn,
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
|
export const configCommand: SlashCommand = {
|
||||||
|
name: 'config',
|
||||||
|
description: 'Commands for interacting with the CLI configuration.',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
subCommands: [
|
||||||
|
{
|
||||||
|
name: 'refresh',
|
||||||
|
description: 'Reload settings and extensions from the filesystem.',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
action: async (context): Promise<SlashCommandActionReturn> => {
|
||||||
|
await context.ui.refreshConfig();
|
||||||
|
return {
|
||||||
|
type: 'message',
|
||||||
|
messageType: 'info',
|
||||||
|
content:
|
||||||
|
'Configuration, extensions, memory, and tools have been refreshed.',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -59,6 +59,7 @@ export interface CommandContext {
|
||||||
/** Toggles a special display mode. */
|
/** Toggles a special display mode. */
|
||||||
toggleCorgiMode: () => void;
|
toggleCorgiMode: () => void;
|
||||||
toggleVimEnabled: () => Promise<boolean>;
|
toggleVimEnabled: () => Promise<boolean>;
|
||||||
|
refreshConfig: () => Promise<void>;
|
||||||
};
|
};
|
||||||
// Session-specific data
|
// Session-specific data
|
||||||
session: {
|
session: {
|
||||||
|
|
|
@ -50,6 +50,7 @@ export const useSlashCommandProcessor = (
|
||||||
openPrivacyNotice: () => void,
|
openPrivacyNotice: () => void,
|
||||||
toggleVimEnabled: () => Promise<boolean>,
|
toggleVimEnabled: () => Promise<boolean>,
|
||||||
setIsProcessing: (isProcessing: boolean) => void,
|
setIsProcessing: (isProcessing: boolean) => void,
|
||||||
|
refreshConfig: () => Promise<void>,
|
||||||
) => {
|
) => {
|
||||||
const session = useSessionStats();
|
const session = useSessionStats();
|
||||||
const [commands, setCommands] = useState<readonly SlashCommand[]>([]);
|
const [commands, setCommands] = useState<readonly SlashCommand[]>([]);
|
||||||
|
@ -158,6 +159,7 @@ export const useSlashCommandProcessor = (
|
||||||
setPendingItem: setPendingCompressionItem,
|
setPendingItem: setPendingCompressionItem,
|
||||||
toggleCorgiMode,
|
toggleCorgiMode,
|
||||||
toggleVimEnabled,
|
toggleVimEnabled,
|
||||||
|
refreshConfig,
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
stats: session.stats,
|
stats: session.stats,
|
||||||
|
@ -180,6 +182,7 @@ export const useSlashCommandProcessor = (
|
||||||
toggleCorgiMode,
|
toggleCorgiMode,
|
||||||
toggleVimEnabled,
|
toggleVimEnabled,
|
||||||
sessionShellAllowlist,
|
sessionShellAllowlist,
|
||||||
|
refreshConfig,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -188,60 +188,62 @@ export interface ConfigParameters {
|
||||||
export class Config {
|
export class Config {
|
||||||
private toolRegistry!: ToolRegistry;
|
private toolRegistry!: ToolRegistry;
|
||||||
private promptRegistry!: PromptRegistry;
|
private promptRegistry!: PromptRegistry;
|
||||||
private readonly sessionId: string;
|
private sessionId: string;
|
||||||
private contentGeneratorConfig!: ContentGeneratorConfig;
|
private contentGeneratorConfig!: ContentGeneratorConfig;
|
||||||
private readonly embeddingModel: string;
|
private embeddingModel: string;
|
||||||
private readonly sandbox: SandboxConfig | undefined;
|
private sandbox: SandboxConfig | undefined;
|
||||||
private readonly targetDir: string;
|
private targetDir: string;
|
||||||
private readonly debugMode: boolean;
|
private debugMode: boolean;
|
||||||
private readonly question: string | undefined;
|
private question: string | undefined;
|
||||||
private readonly fullContext: boolean;
|
private fullContext: boolean;
|
||||||
private readonly coreTools: string[] | undefined;
|
private coreTools: string[] | undefined;
|
||||||
private readonly excludeTools: string[] | undefined;
|
private excludeTools: string[] | undefined;
|
||||||
private readonly toolDiscoveryCommand: string | undefined;
|
private toolDiscoveryCommand: string | undefined;
|
||||||
private readonly toolCallCommand: string | undefined;
|
private toolCallCommand: string | undefined;
|
||||||
private readonly mcpServerCommand: string | undefined;
|
private mcpServerCommand: string | undefined;
|
||||||
private readonly mcpServers: Record<string, MCPServerConfig> | undefined;
|
private mcpServers: Record<string, MCPServerConfig> | undefined;
|
||||||
private userMemory: string;
|
private userMemory: string;
|
||||||
private geminiMdFileCount: number;
|
private geminiMdFileCount: number;
|
||||||
private approvalMode: ApprovalMode;
|
private approvalMode: ApprovalMode;
|
||||||
private readonly showMemoryUsage: boolean;
|
private showMemoryUsage: boolean;
|
||||||
private readonly accessibility: AccessibilitySettings;
|
private accessibility: AccessibilitySettings;
|
||||||
private readonly telemetrySettings: TelemetrySettings;
|
private telemetrySettings: TelemetrySettings;
|
||||||
private readonly usageStatisticsEnabled: boolean;
|
private usageStatisticsEnabled: boolean;
|
||||||
private geminiClient!: GeminiClient;
|
private geminiClient!: GeminiClient;
|
||||||
private readonly fileFiltering: {
|
private fileFiltering: {
|
||||||
respectGitIgnore: boolean;
|
respectGitIgnore: boolean;
|
||||||
respectGeminiIgnore: boolean;
|
respectGeminiIgnore: boolean;
|
||||||
enableRecursiveFileSearch: boolean;
|
enableRecursiveFileSearch: boolean;
|
||||||
};
|
};
|
||||||
private fileDiscoveryService: FileDiscoveryService | null = null;
|
private fileDiscoveryService: FileDiscoveryService | null = null;
|
||||||
private gitService: GitService | undefined = undefined;
|
private gitService: GitService | undefined = undefined;
|
||||||
private readonly checkpointing: boolean;
|
private checkpointing: boolean;
|
||||||
private readonly proxy: string | undefined;
|
private proxy: string | undefined;
|
||||||
private readonly cwd: string;
|
private cwd: string;
|
||||||
private readonly bugCommand: BugCommandSettings | undefined;
|
private bugCommand: BugCommandSettings | undefined;
|
||||||
private readonly model: string;
|
private model: string;
|
||||||
private readonly extensionContextFilePaths: string[];
|
private extensionContextFilePaths: string[];
|
||||||
private readonly noBrowser: boolean;
|
private noBrowser: boolean;
|
||||||
private readonly ideMode: boolean;
|
private ideMode: boolean;
|
||||||
private readonly ideClient: IdeClient | undefined;
|
private ideClient: IdeClient | undefined;
|
||||||
private modelSwitchedDuringSession: boolean = false;
|
private modelSwitchedDuringSession: boolean = false;
|
||||||
private readonly maxSessionTurns: number;
|
private maxSessionTurns: number;
|
||||||
private readonly listExtensions: boolean;
|
private listExtensions: boolean;
|
||||||
private readonly _extensions: GeminiCLIExtension[];
|
private _extensions: GeminiCLIExtension[];
|
||||||
private readonly _blockedMcpServers: Array<{
|
private _blockedMcpServers: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
extensionName: string;
|
extensionName: string;
|
||||||
}>;
|
}>;
|
||||||
flashFallbackHandler?: FlashFallbackHandler;
|
flashFallbackHandler?: FlashFallbackHandler;
|
||||||
private quotaErrorOccurred: boolean = false;
|
private quotaErrorOccurred: boolean = false;
|
||||||
private readonly summarizeToolOutput:
|
private summarizeToolOutput:
|
||||||
| Record<string, SummarizeToolOutputSettings>
|
| Record<string, SummarizeToolOutputSettings>
|
||||||
| undefined;
|
| undefined;
|
||||||
private readonly experimentalAcp: boolean = false;
|
private experimentalAcp: boolean = false;
|
||||||
|
private _params: ConfigParameters;
|
||||||
|
|
||||||
constructor(params: ConfigParameters) {
|
constructor(params: ConfigParameters) {
|
||||||
|
this._params = params;
|
||||||
this.sessionId = params.sessionId;
|
this.sessionId = params.sessionId;
|
||||||
this.embeddingModel =
|
this.embeddingModel =
|
||||||
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
|
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
|
||||||
|
@ -310,6 +312,68 @@ export class Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async refresh() {
|
||||||
|
// Re-run initialization logic.
|
||||||
|
await this.initialize();
|
||||||
|
// After re-initializing, the tool registry will be updated.
|
||||||
|
// We need to update the gemini client with the new tools.
|
||||||
|
await this.geminiClient.setTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
update(params: ConfigParameters) {
|
||||||
|
this._params = params;
|
||||||
|
// Re-assign all properties from the new params.
|
||||||
|
this.sessionId = params.sessionId;
|
||||||
|
this.embeddingModel =
|
||||||
|
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
|
||||||
|
this.sandbox = params.sandbox;
|
||||||
|
this.targetDir = path.resolve(params.targetDir);
|
||||||
|
this.debugMode = params.debugMode;
|
||||||
|
this.question = params.question;
|
||||||
|
this.fullContext = params.fullContext ?? false;
|
||||||
|
this.coreTools = params.coreTools;
|
||||||
|
this.excludeTools = params.excludeTools;
|
||||||
|
this.toolDiscoveryCommand = params.toolDiscoveryCommand;
|
||||||
|
this.toolCallCommand = params.toolCallCommand;
|
||||||
|
this.mcpServerCommand = params.mcpServerCommand;
|
||||||
|
this.mcpServers = params.mcpServers;
|
||||||
|
this.userMemory = params.userMemory ?? '';
|
||||||
|
this.geminiMdFileCount = params.geminiMdFileCount ?? 0;
|
||||||
|
this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT;
|
||||||
|
this.showMemoryUsage = params.showMemoryUsage ?? false;
|
||||||
|
this.accessibility = params.accessibility ?? {};
|
||||||
|
this.telemetrySettings = {
|
||||||
|
enabled: params.telemetry?.enabled ?? false,
|
||||||
|
target: params.telemetry?.target ?? DEFAULT_TELEMETRY_TARGET,
|
||||||
|
otlpEndpoint: params.telemetry?.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT,
|
||||||
|
logPrompts: params.telemetry?.logPrompts ?? true,
|
||||||
|
outfile: params.telemetry?.outfile,
|
||||||
|
};
|
||||||
|
this.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true;
|
||||||
|
this.fileFiltering = {
|
||||||
|
respectGitIgnore: params.fileFiltering?.respectGitIgnore ?? true,
|
||||||
|
respectGeminiIgnore: params.fileFiltering?.respectGeminiIgnore ?? true,
|
||||||
|
enableRecursiveFileSearch:
|
||||||
|
params.fileFiltering?.enableRecursiveFileSearch ?? true,
|
||||||
|
};
|
||||||
|
this.checkpointing = params.checkpointing ?? false;
|
||||||
|
this.proxy = params.proxy;
|
||||||
|
this.cwd = params.cwd ?? process.cwd();
|
||||||
|
this.fileDiscoveryService = params.fileDiscoveryService ?? null;
|
||||||
|
this.bugCommand = params.bugCommand;
|
||||||
|
this.model = params.model;
|
||||||
|
this.extensionContextFilePaths = params.extensionContextFilePaths ?? [];
|
||||||
|
this.maxSessionTurns = params.maxSessionTurns ?? -1;
|
||||||
|
this.experimentalAcp = params.experimentalAcp ?? false;
|
||||||
|
this.listExtensions = params.listExtensions ?? false;
|
||||||
|
this._extensions = params.extensions ?? [];
|
||||||
|
this._blockedMcpServers = params.blockedMcpServers ?? [];
|
||||||
|
this.noBrowser = params.noBrowser ?? false;
|
||||||
|
this.summarizeToolOutput = params.summarizeToolOutput;
|
||||||
|
this.ideMode = params.ideMode ?? false;
|
||||||
|
this.ideClient = params.ideClient;
|
||||||
|
}
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
// Initialize centralized FileDiscoveryService
|
// Initialize centralized FileDiscoveryService
|
||||||
this.getFileService();
|
this.getFileService();
|
||||||
|
|
Loading…
Reference in New Issue