diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 5df7be9963..d82ab2108c 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -59,6 +59,9 @@ vi.mock('./services/CommandService.js', () => ({ }, })); +vi.mock('./services/FileCommandLoader.js'); +vi.mock('./services/McpPromptLoader.js'); + describe('runNonInteractive', () => { let mockConfig: Config; let mockSettings: LoadedSettings; @@ -938,6 +941,46 @@ describe('runNonInteractive', () => { expect(processStdoutSpy).toHaveBeenCalledWith('Acknowledged'); }); + it('should instantiate CommandService with correct loaders for slash commands', async () => { + // This test indirectly checks that handleSlashCommand is using the right loaders. + const { FileCommandLoader } = await import( + './services/FileCommandLoader.js' + ); + const { McpPromptLoader } = await import('./services/McpPromptLoader.js'); + + mockGetCommands.mockReturnValue([]); // No commands found, so it will fall through + const events: ServerGeminiStreamEvent[] = [ + { type: GeminiEventType.Content, value: 'Acknowledged' }, + { + type: GeminiEventType.Finished, + value: { reason: undefined, usageMetadata: { totalTokenCount: 1 } }, + }, + ]; + mockGeminiClient.sendMessageStream.mockReturnValue( + createStreamFromEvents(events), + ); + + await runNonInteractive( + mockConfig, + mockSettings, + '/mycommand', + 'prompt-id-loaders', + ); + + // Check that loaders were instantiated with the config + expect(FileCommandLoader).toHaveBeenCalledTimes(1); + expect(FileCommandLoader).toHaveBeenCalledWith(mockConfig); + expect(McpPromptLoader).toHaveBeenCalledTimes(1); + expect(McpPromptLoader).toHaveBeenCalledWith(mockConfig); + + // Check that instances were passed to CommandService.create + expect(mockCommandServiceCreate).toHaveBeenCalledTimes(1); + const loadersArg = mockCommandServiceCreate.mock.calls[0][0]; + expect(loadersArg).toHaveLength(2); + expect(loadersArg[0]).toBe(vi.mocked(McpPromptLoader).mock.instances[0]); + expect(loadersArg[1]).toBe(vi.mocked(FileCommandLoader).mock.instances[0]); + }); + it('should allow a normally-excluded tool when --allowed-tools is set', async () => { // By default, ShellTool is excluded in non-interactive mode. // This test ensures that --allowed-tools overrides this exclusion. diff --git a/packages/cli/src/nonInteractiveCliCommands.ts b/packages/cli/src/nonInteractiveCliCommands.ts index 15b9301ad4..912121a2dd 100644 --- a/packages/cli/src/nonInteractiveCliCommands.ts +++ b/packages/cli/src/nonInteractiveCliCommands.ts @@ -14,6 +14,7 @@ import { } from '@google/gemini-cli-core'; import { CommandService } from './services/CommandService.js'; import { FileCommandLoader } from './services/FileCommandLoader.js'; +import { McpPromptLoader } from './services/McpPromptLoader.js'; import type { CommandContext } from './ui/commands/types.js'; import { createNonInteractiveUI } from './ui/noninteractive/nonInteractiveUi.js'; import type { LoadedSettings } from './config/settings.js'; @@ -38,9 +39,8 @@ export const handleSlashCommand = async ( return; } - // Only custom commands are supported for now. const commandService = await CommandService.create( - [new FileCommandLoader(config)], + [new McpPromptLoader(config), new FileCommandLoader(config)], abortController.signal, ); const commands = commandService.getCommands();