mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-13 12:57:12 -07:00
feat(cli): add top-level /reload command to refresh all systems
- Consolidates existing reload subcommands (/agents, /commands, /extensions, /mcp, /memory, /skills) into a single top-level /reload (alias /refresh) command. - Adds support for refreshing settings.json from disk via LoadedSettings.reload(). - Updates all relevant tests for LoadedSettings constructor changes.
This commit is contained in:
@@ -2857,6 +2857,7 @@ describe('Settings Loading and Merging', () => {
|
||||
{ ...emptySettingsFile, path: MOCK_WORKSPACE_SETTINGS_PATH },
|
||||
true, // isTrusted
|
||||
[],
|
||||
MOCK_WORKSPACE_DIR,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3181,6 +3182,8 @@ describe('LoadedSettings Isolation and Serializability', () => {
|
||||
{ ...emptyScope }, // user
|
||||
emptyScope, // workspace
|
||||
true, // isTrusted
|
||||
[],
|
||||
'',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -312,6 +312,7 @@ export class LoadedSettings {
|
||||
workspace: SettingsFile,
|
||||
isTrusted: boolean,
|
||||
errors: SettingsError[] = [],
|
||||
workspaceDir: string,
|
||||
) {
|
||||
this.system = system;
|
||||
this.systemDefaults = systemDefaults;
|
||||
@@ -322,21 +323,23 @@ export class LoadedSettings {
|
||||
? workspace
|
||||
: this.createEmptyWorkspace(workspace);
|
||||
this.errors = errors;
|
||||
this._workspaceDir = workspaceDir;
|
||||
this._merged = this.computeMergedSettings();
|
||||
this._snapshot = this.computeSnapshot();
|
||||
}
|
||||
|
||||
readonly system: SettingsFile;
|
||||
readonly systemDefaults: SettingsFile;
|
||||
readonly user: SettingsFile;
|
||||
system: SettingsFile;
|
||||
systemDefaults: SettingsFile;
|
||||
user: SettingsFile;
|
||||
workspace: SettingsFile;
|
||||
isTrusted: boolean;
|
||||
readonly errors: SettingsError[];
|
||||
errors: SettingsError[];
|
||||
|
||||
private _workspaceFile: SettingsFile;
|
||||
private _merged: MergedSettings;
|
||||
private _snapshot: LoadedSettingsSnapshot;
|
||||
private _remoteAdminSettings: Partial<Settings> | undefined;
|
||||
private _workspaceDir: string;
|
||||
|
||||
get merged(): MergedSettings {
|
||||
return this._merged;
|
||||
@@ -492,6 +495,30 @@ export class LoadedSettings {
|
||||
this._remoteAdminSettings = { admin };
|
||||
this._merged = this.computeMergedSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this instance with data from another instance.
|
||||
* This preserves the object identity of this instance while refreshing its content.
|
||||
*/
|
||||
updateFrom(other: LoadedSettings): void {
|
||||
this.system = other.system;
|
||||
this.systemDefaults = other.systemDefaults;
|
||||
this.user = other.user;
|
||||
this._workspaceFile = other._workspaceFile;
|
||||
this.isTrusted = other.isTrusted;
|
||||
this.workspace = other.workspace;
|
||||
this.errors = [...other.errors];
|
||||
this._merged = this.computeMergedSettings();
|
||||
this._snapshot = this.computeSnapshot();
|
||||
coreEvents.emitSettingsChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads settings from disk for the current workspace.
|
||||
*/
|
||||
reload(): void {
|
||||
this.updateFrom(loadSettings(this._workspaceDir));
|
||||
}
|
||||
}
|
||||
|
||||
function findEnvFile(startDir: string): string | null {
|
||||
@@ -816,6 +843,7 @@ function _doLoadSettings(workspaceDir: string): LoadedSettings {
|
||||
},
|
||||
isTrusted,
|
||||
settingsErrors,
|
||||
workspaceDir,
|
||||
);
|
||||
|
||||
// Automatically migrate deprecated settings when loading.
|
||||
|
||||
@@ -122,6 +122,9 @@ vi.mock('../ui/commands/toolsCommand.js', () => ({ toolsCommand: {} }));
|
||||
vi.mock('../ui/commands/skillsCommand.js', () => ({
|
||||
skillsCommand: { name: 'skills' },
|
||||
}));
|
||||
vi.mock('../ui/commands/reloadCommand.js', () => ({
|
||||
reloadCommand: { name: 'reload' },
|
||||
}));
|
||||
vi.mock('../ui/commands/planCommand.js', async () => {
|
||||
const { CommandKind } = await import('../ui/commands/types.js');
|
||||
return {
|
||||
@@ -247,6 +250,9 @@ describe('BuiltinCommandLoader', () => {
|
||||
|
||||
const mcpCmd = commands.find((c) => c.name === 'mcp');
|
||||
expect(mcpCmd).toBeDefined();
|
||||
|
||||
const reloadCmd = commands.find((c) => c.name === 'reload');
|
||||
expect(reloadCmd).toBeDefined();
|
||||
});
|
||||
|
||||
it('should include permissions command when folder trust is enabled', async () => {
|
||||
|
||||
@@ -55,6 +55,7 @@ import { statsCommand } from '../ui/commands/statsCommand.js';
|
||||
import { themeCommand } from '../ui/commands/themeCommand.js';
|
||||
import { toolsCommand } from '../ui/commands/toolsCommand.js';
|
||||
import { skillsCommand } from '../ui/commands/skillsCommand.js';
|
||||
import { reloadCommand } from '../ui/commands/reloadCommand.js';
|
||||
import { settingsCommand } from '../ui/commands/settingsCommand.js';
|
||||
import { tasksCommand } from '../ui/commands/tasksCommand.js';
|
||||
import { vimCommand } from '../ui/commands/vimCommand.js';
|
||||
@@ -187,6 +188,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
...(this.config?.isPlanEnabled() ? [planCommand] : []),
|
||||
policiesCommand,
|
||||
privacyCommand,
|
||||
reloadCommand,
|
||||
...(isDevelopment ? [profileCommand] : []),
|
||||
quitCommand,
|
||||
restoreCommand(this.config),
|
||||
|
||||
@@ -63,6 +63,7 @@ export const createMockSettings = (
|
||||
(workspace as any) || { path: '', settings: {}, originalSettings: {} },
|
||||
isTrusted ?? true,
|
||||
errors || [],
|
||||
'',
|
||||
);
|
||||
|
||||
if (mergedOverride) {
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { refreshMemory } from '@google/gemini-cli-core';
|
||||
import { MessageType } from '../types.js';
|
||||
import {
|
||||
CommandKind,
|
||||
type CommandContext,
|
||||
type SlashCommand,
|
||||
type SlashCommandActionReturn,
|
||||
} from './types.js';
|
||||
|
||||
/**
|
||||
* Action for the top-level `/reload` command.
|
||||
* Orchestrates re-syncing the agent by reloading skills, agents, MCP servers,
|
||||
* memory, and then refreshing the slash commands.
|
||||
*/
|
||||
async function reloadAllAction(
|
||||
context: CommandContext,
|
||||
): Promise<void | SlashCommandActionReturn> {
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Could not retrieve configuration for reload.',
|
||||
};
|
||||
}
|
||||
|
||||
context.ui.addItem({
|
||||
type: MessageType.INFO,
|
||||
text: 'Reloading all agent systems...',
|
||||
});
|
||||
|
||||
const errors: string[] = [];
|
||||
|
||||
// 0. Reload settings.json
|
||||
try {
|
||||
context.services.settings.reload();
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
`Settings: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 1. Reload Skills & Extensions
|
||||
try {
|
||||
await config.reloadSkills();
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
`Skills: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Reload Agent Registry
|
||||
const agentRegistry = config.getAgentRegistry();
|
||||
if (agentRegistry) {
|
||||
try {
|
||||
await agentRegistry.reload();
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
`Agents: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Reload MCP Servers
|
||||
const mcpClientManager = config.getMcpClientManager();
|
||||
if (mcpClientManager) {
|
||||
try {
|
||||
await mcpClientManager.restart();
|
||||
// Update the client with the new tools
|
||||
if (agentContext.geminiClient?.isInitialized()) {
|
||||
await agentContext.geminiClient.setTools();
|
||||
}
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
`MCP: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Reload Memory
|
||||
try {
|
||||
const memoryResult = await refreshMemory(config);
|
||||
context.ui.addItem({
|
||||
type: MessageType.INFO,
|
||||
text: memoryResult.content,
|
||||
});
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
`Memory: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Finally, reload slash commands to reflect all changes
|
||||
try {
|
||||
context.ui.reloadCommands();
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
`Commands: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: `Reload completed with errors:\n- ${errors.join('\n- ')}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: 'All systems reloaded successfully.',
|
||||
};
|
||||
}
|
||||
|
||||
export const reloadCommand: SlashCommand = {
|
||||
name: 'reload',
|
||||
altNames: ['refresh'],
|
||||
description:
|
||||
'Reload all agent systems (skills, agents, MCP, memory, and commands)',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: reloadAllAction,
|
||||
};
|
||||
@@ -71,6 +71,7 @@ const createMockSettings = (
|
||||
},
|
||||
true,
|
||||
[],
|
||||
'',
|
||||
);
|
||||
|
||||
// Mock setValue
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('colorizeCode', () => {
|
||||
{ path: '', settings: {}, originalSettings: {} },
|
||||
true,
|
||||
[],
|
||||
'',
|
||||
);
|
||||
|
||||
const result = colorizeCode({
|
||||
@@ -63,6 +64,7 @@ describe('colorizeCode', () => {
|
||||
{ path: '', settings: {}, originalSettings: {} },
|
||||
true,
|
||||
[],
|
||||
'',
|
||||
);
|
||||
|
||||
const result = colorizeCode({
|
||||
@@ -89,6 +91,7 @@ describe('colorizeCode', () => {
|
||||
{ path: '', settings: {}, originalSettings: {} },
|
||||
true,
|
||||
[],
|
||||
'',
|
||||
);
|
||||
|
||||
const result = colorizeCode({
|
||||
|
||||
@@ -213,6 +213,7 @@ Another paragraph.
|
||||
{ path: '', settings: {}, originalSettings: {} },
|
||||
true,
|
||||
[],
|
||||
'',
|
||||
);
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
|
||||
Reference in New Issue
Block a user