Merge pull request #2822 from qqqys/fix/cli_command

fix(cli): prevent ideCommand failure from breaking all slash commands…
This commit is contained in:
tanzhenxin 2026-04-05 14:27:01 +08:00 committed by GitHub
commit fe4f2567c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 12 deletions

View file

@ -207,6 +207,33 @@ describe('BuiltinCommandLoader', () => {
expect(modelCmd?.name).toBe('model');
});
it('should still load all other commands when ideCommand() throws', async () => {
// Simulate ideCommand() failure (e.g., platform-specific process detection fails)
const { ideCommand: ideCommandMock } = await import(
'../ui/commands/ideCommand.js'
);
(ideCommandMock as Mock).mockRejectedValueOnce(
new Error('PowerShell not available'),
);
const loader = new BuiltinCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
// IDE command should NOT be present
const ideCmd = commands.find((c) => c.name === 'ide');
expect(ideCmd).toBeUndefined();
// But all other built-in commands should still be loaded
const modelCmd = commands.find((c) => c.name === 'model');
expect(modelCmd).toBeDefined();
const statusCmd = commands.find((c) => c.name === 'status');
expect(statusCmd).toBeDefined();
const mcpCmd = commands.find((c) => c.name === 'mcp');
expect(mcpCmd).toBeDefined();
});
it('should always include hooks command regardless of disableAllHooks', async () => {
// When disableAllHooks is false
const loader1 = new BuiltinCommandLoader(mockConfig);

View file

@ -26,6 +26,7 @@ import { extensionsCommand } from '../ui/commands/extensionsCommand.js';
import { helpCommand } from '../ui/commands/helpCommand.js';
import { hooksCommand } from '../ui/commands/hooksCommand.js';
import { ideCommand } from '../ui/commands/ideCommand.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
import { initCommand } from '../ui/commands/initCommand.js';
import { languageCommand } from '../ui/commands/languageCommand.js';
import { mcpCommand } from '../ui/commands/mcpCommand.js';
@ -47,6 +48,8 @@ import { vimCommand } from '../ui/commands/vimCommand.js';
import { setupGithubCommand } from '../ui/commands/setupGithubCommand.js';
import { insightCommand } from '../ui/commands/insightCommand.js';
const builtinDebugLogger = createDebugLogger('BUILTIN_COMMAND_LOADER');
/**
* Loads the core, hard-coded slash commands that are an integral part
* of the Qwen Code application.
@ -62,6 +65,19 @@ export class BuiltinCommandLoader implements ICommandLoader {
* @returns A promise that resolves to an array of `SlashCommand` objects.
*/
async loadCommands(_signal: AbortSignal): Promise<SlashCommand[]> {
// Load ideCommand separately with error handling so that a failure
// (e.g., platform-specific process detection on Windows) does not
// prevent ALL built-in commands from loading.
let resolvedIdeCommand: SlashCommand | null = null;
try {
resolvedIdeCommand = await ideCommand();
} catch (error) {
builtinDebugLogger.warn(
'Failed to load IDE command:',
error instanceof Error ? error.message : String(error),
);
}
const allDefinitions: Array<SlashCommand | null> = [
aboutCommand,
agentsCommand,
@ -81,7 +97,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
extensionsCommand,
helpCommand,
hooksCommand,
await ideCommand(),
resolvedIdeCommand,
initCommand,
languageCommand,
mcpCommand,

View file

@ -333,17 +333,24 @@ export const useSlashCommandProcessor = (
useEffect(() => {
const controller = new AbortController();
const load = async () => {
const loaders = [
new McpPromptLoader(config),
new BuiltinCommandLoader(config),
new BundledSkillLoader(config),
new FileCommandLoader(config),
];
const commandService = await CommandService.create(
loaders,
controller.signal,
);
setCommands(commandService.getCommands());
try {
const loaders = [
new McpPromptLoader(config),
new BuiltinCommandLoader(config),
new BundledSkillLoader(config),
new FileCommandLoader(config),
];
const commandService = await CommandService.create(
loaders,
controller.signal,
);
// Avoid overwriting newer results from a subsequent effect run
if (!controller.signal.aborted) {
setCommands(commandService.getCommands());
}
} catch (error) {
debugLogger.error('Failed to load slash commands:', error);
}
};
load();