mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Sync upstream Gemini-CLI v0.8.2 (#838)
This commit is contained in:
@@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user