Sync upstream Gemini-CLI v0.8.2 (#838)

This commit is contained in:
tanzhenxin
2025-10-23 09:27:04 +08:00
committed by GitHub
parent 096fabb5d6
commit eb95c131be
644 changed files with 70389 additions and 23709 deletions

View File

@@ -17,15 +17,16 @@ import {
SlashCommandStatus,
ToolConfirmationOutcome,
Storage,
IdeClient,
} from '@qwen-code/qwen-code-core';
import { useSessionStats } from '../contexts/SessionContext.js';
import { formatDuration } from '../utils/formatters.js';
import { runExitCleanup } from '../../utils/cleanup.js';
import type {
Message,
HistoryItemWithoutId,
HistoryItem,
SlashCommandProcessorResult,
HistoryItem,
ConfirmationRequest,
} from '../types.js';
import { MessageType } from '../types.js';
import type { LoadedSettings } from '../../config/settings.js';
@@ -34,6 +35,28 @@ import { CommandService } from '../../services/CommandService.js';
import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js';
import { FileCommandLoader } from '../../services/FileCommandLoader.js';
import { McpPromptLoader } from '../../services/McpPromptLoader.js';
import { parseSlashCommand } from '../../utils/commands.js';
import {
type ExtensionUpdateAction,
type ExtensionUpdateStatus,
} from '../state/extensions.js';
interface SlashCommandProcessorActions {
openAuthDialog: () => void;
openThemeDialog: () => void;
openEditorDialog: () => void;
openSettingsDialog: () => void;
openModelDialog: () => void;
openPermissionsDialog: () => void;
quit: (messages: HistoryItem[]) => void;
setDebugMessage: (message: string) => void;
toggleCorgiMode: () => void;
dispatchExtensionStateUpdate: (action: ExtensionUpdateAction) => void;
addConfirmUpdateExtensionRequest: (request: ConfirmationRequest) => void;
openSubagentCreateDialog: () => void;
openAgentsManagerDialog: () => void;
_showQuitConfirmation: () => void;
}
/**
* Hook to define and process slash commands (e.g., /help, /clear).
@@ -45,21 +68,12 @@ export const useSlashCommandProcessor = (
clearItems: UseHistoryManagerReturn['clearItems'],
loadHistory: UseHistoryManagerReturn['loadHistory'],
refreshStatic: () => void,
onDebugMessage: (message: string) => void,
openThemeDialog: () => void,
openAuthDialog: () => void,
openEditorDialog: () => void,
toggleCorgiMode: () => void,
setQuittingMessages: (message: HistoryItem[]) => void,
openPrivacyNotice: () => void,
openSettingsDialog: () => void,
openModelSelectionDialog: () => void,
openSubagentCreateDialog: () => void,
openAgentsManagerDialog: () => void,
toggleVimEnabled: () => Promise<boolean>,
setIsProcessing: (isProcessing: boolean) => void,
setGeminiMdFileCount: (count: number) => void,
_showQuitConfirmation: () => void,
actions: SlashCommandProcessorActions,
extensionsUpdateState: Map<string, ExtensionUpdateStatus>,
isConfigInitialized: boolean,
) => {
const session = useSessionStats();
const [commands, setCommands] = useState<readonly SlashCommand[]>([]);
@@ -105,16 +119,17 @@ export const useSlashCommandProcessor = (
return l;
}, [config]);
const [pendingCompressionItem, setPendingCompressionItem] =
useState<HistoryItemWithoutId | null>(null);
const [pendingItem, setPendingItem] = useState<HistoryItemWithoutId | null>(
null,
);
const pendingHistoryItems = useMemo(() => {
const items: HistoryItemWithoutId[] = [];
if (pendingCompressionItem != null) {
items.push(pendingCompressionItem);
if (pendingItem != null) {
items.push(pendingItem);
}
return items;
}, [pendingCompressionItem]);
}, [pendingItem]);
const addMessage = useCallback(
(message: Message) => {
@@ -195,13 +210,17 @@ export const useSlashCommandProcessor = (
refreshStatic();
},
loadHistory,
setDebugMessage: onDebugMessage,
pendingItem: pendingCompressionItem,
setPendingItem: setPendingCompressionItem,
toggleCorgiMode,
setDebugMessage: actions.setDebugMessage,
pendingItem,
setPendingItem,
toggleCorgiMode: actions.toggleCorgiMode,
toggleVimEnabled,
setGeminiMdFileCount,
reloadCommands,
extensionsUpdateState,
dispatchExtensionStateUpdate: actions.dispatchExtensionStateUpdate,
addConfirmUpdateExtensionRequest:
actions.addConfirmUpdateExtensionRequest,
},
session: {
stats: session.stats,
@@ -218,14 +237,14 @@ export const useSlashCommandProcessor = (
clearItems,
refreshStatic,
session.stats,
onDebugMessage,
pendingCompressionItem,
setPendingCompressionItem,
toggleCorgiMode,
actions,
pendingItem,
setPendingItem,
toggleVimEnabled,
sessionShellAllowlist,
setGeminiMdFileCount,
reloadCommands,
extensionsUpdateState,
],
);
@@ -234,15 +253,20 @@ export const useSlashCommandProcessor = (
return;
}
const ideClient = config.getIdeClient();
const listener = () => {
reloadCommands();
};
ideClient.addStatusChangeListener(listener);
(async () => {
const ideClient = await IdeClient.getInstance();
ideClient.addStatusChangeListener(listener);
})();
return () => {
ideClient.removeStatusChangeListener(listener);
(async () => {
const ideClient = await IdeClient.getInstance();
ideClient.removeStatusChangeListener(listener);
})();
};
}, [config, reloadCommands]);
@@ -266,7 +290,7 @@ export const useSlashCommandProcessor = (
return () => {
controller.abort();
};
}, [config, reloadTrigger]);
}, [config, reloadTrigger, isConfigInitialized]);
const handleSlashCommand = useCallback(
async (
@@ -288,47 +312,13 @@ export const useSlashCommandProcessor = (
const userMessageTimestamp = Date.now();
addItem({ type: MessageType.USER, text: trimmed }, userMessageTimestamp);
const parts = trimmed.substring(1).trim().split(/\s+/);
const commandPath = parts.filter((p) => p); // The parts of the command, e.g., ['memory', 'add']
let currentCommands = commands;
let commandToExecute: SlashCommand | undefined;
let pathIndex = 0;
let hasError = false;
const canonicalPath: string[] = [];
const {
commandToExecute,
args,
canonicalPath: resolvedCommandPath,
} = parseSlashCommand(trimmed, commands);
for (const part of commandPath) {
// TODO: For better performance and architectural clarity, this two-pass
// search could be replaced. A more optimal approach would be to
// pre-compute a single lookup map in `CommandService.ts` that resolves
// all name and alias conflicts during the initial loading phase. The
// processor would then perform a single, fast lookup on that map.
// First pass: check for an exact match on the primary command name.
let foundCommand = currentCommands.find((cmd) => cmd.name === part);
// Second pass: if no primary name matches, check for an alias.
if (!foundCommand) {
foundCommand = currentCommands.find((cmd) =>
cmd.altNames?.includes(part),
);
}
if (foundCommand) {
commandToExecute = foundCommand;
canonicalPath.push(foundCommand.name);
pathIndex++;
if (foundCommand.subCommands) {
currentCommands = foundCommand.subCommands;
} else {
break;
}
} else {
break;
}
}
const resolvedCommandPath = canonicalPath;
const subcommand =
resolvedCommandPath.length > 1
? resolvedCommandPath.slice(1).join(' ')
@@ -336,8 +326,6 @@ export const useSlashCommandProcessor = (
try {
if (commandToExecute) {
const args = parts.slice(pathIndex).join(' ');
if (commandToExecute.action) {
const fullCommandContext: CommandContext = {
...commandContext,
@@ -391,28 +379,28 @@ export const useSlashCommandProcessor = (
case 'dialog':
switch (result.dialog) {
case 'auth':
openAuthDialog();
actions.openAuthDialog();
return { type: 'handled' };
case 'theme':
openThemeDialog();
actions.openThemeDialog();
return { type: 'handled' };
case 'editor':
openEditorDialog();
return { type: 'handled' };
case 'privacy':
openPrivacyNotice();
actions.openEditorDialog();
return { type: 'handled' };
case 'settings':
openSettingsDialog();
actions.openSettingsDialog();
return { type: 'handled' };
case 'model':
openModelSelectionDialog();
actions.openModelDialog();
return { type: 'handled' };
case 'permissions':
actions.openPermissionsDialog();
return { type: 'handled' };
case 'subagent_create':
openSubagentCreateDialog();
actions.openSubagentCreateDialog();
return { type: 'handled' };
case 'subagent_list':
openAgentsManagerDialog();
actions.openAgentsManagerDialog();
return { type: 'handled' };
case 'help':
return { type: 'handled' };
@@ -424,9 +412,8 @@ export const useSlashCommandProcessor = (
}
}
case 'load_history': {
await config
?.getGeminiClient()
?.setHistory(result.clientHistory);
config?.getGeminiClient()?.setHistory(result.clientHistory);
config?.getGeminiClient()?.stripThoughtsFromHistory();
fullCommandContext.ui.clear();
result.history.forEach((item, index) => {
fullCommandContext.ui.addItem(item, index);
@@ -484,7 +471,7 @@ export const useSlashCommandProcessor = (
const { sessionStartTime } = session.stats;
const wallDuration = now - sessionStartTime.getTime();
setQuittingMessages([
actions.quit([
{
type: 'user',
text: `/quit`,
@@ -496,10 +483,6 @@ export const useSlashCommandProcessor = (
id: now,
},
]);
setTimeout(async () => {
await runExitCleanup();
process.exit(0);
}, 100);
}
}
},
@@ -507,11 +490,7 @@ export const useSlashCommandProcessor = (
return { type: 'handled' };
case 'quit':
setQuittingMessages(result.messages);
setTimeout(async () => {
await runExitCleanup();
process.exit(0);
}, 100);
actions.quit(result.messages);
return { type: 'handled' };
case 'submit_prompt':
@@ -652,22 +631,14 @@ export const useSlashCommandProcessor = (
[
config,
addItem,
openAuthDialog,
actions,
commands,
commandContext,
addMessage,
openThemeDialog,
openPrivacyNotice,
openEditorDialog,
setQuittingMessages,
openSettingsDialog,
openSubagentCreateDialog,
openAgentsManagerDialog,
setShellConfirmationRequest,
setSessionShellAllowlist,
setIsProcessing,
setConfirmationRequest,
openModelSelectionDialog,
session.stats,
],
);