diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 6d4a75bbb0..7b11ca5040 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -3711,7 +3711,8 @@ describe('loadCliConfig mcpEnabled', () => { ]); const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.storage.getPlansDir()).toContain('ext-plans-dir'); + config.setActiveExtensionContext('ext-plan'); + expect(config.getPlansDir()).toContain('ext-plans-dir'); }); it('should NOT use plan directory from active extension when user has specified one', async () => { diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 3ee537ac35..51dc87025b 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -609,9 +609,12 @@ export async function loadCliConfig( }); await extensionManager.loadExtensions(); - const extensionPlanSettings = extensionManager - .getExtensions() - .find((ext) => ext.isActive && ext.plan?.directory)?.plan; + const extensionPlanDirs: Record = {}; + for (const ext of extensionManager.getExtensions()) { + if (ext.isActive && ext.plan?.directory) { + extensionPlanDirs[ext.name] = ext.plan.directory; + } + } const experimentalJitContext = settings.experimental.jitContext; @@ -969,9 +972,8 @@ export async function loadCliConfig( plan: settings.general?.plan?.enabled ?? true, tracker: settings.experimental?.taskTracker, directWebFetch: settings.experimental?.directWebFetch, - planSettings: settings.general?.plan?.directory - ? settings.general.plan - : (extensionPlanSettings ?? settings.general?.plan), + planSettings: settings.general?.plan, + extensionPlanDirs, enableEventDrivenScheduler: true, skillsSupport: settings.skills?.enabled ?? true, disabledSkills: settings.skills?.disabled, diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 4da8acfdb7..ee0d49f2be 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -1308,6 +1308,17 @@ Logging in with Google... Restarting Gemini CLI to continue. } } + const parsedCommand = parseSlashCommand( + submittedValue, + slashCommands ?? [], + ); + + if (parsedCommand.extensionContext && config) { + if (config.hasExtensionPlanDir(parsedCommand.extensionContext)) { + config.setActiveExtensionContext(parsedCommand.extensionContext); + } + } + const isSlash = isSlashCommand(submittedValue.trim()); const isIdle = streamingState === StreamingState.Idle; const isAgentRunning = @@ -1315,10 +1326,7 @@ Logging in with Google... Restarting Gemini CLI to continue. isToolExecuting(pendingHistoryItems); if (isSlash && isAgentRunning) { - const { commandToExecute } = parseSlashCommand( - submittedValue, - slashCommands ?? [], - ); + const commandToExecute = parsedCommand.commandToExecute; if (commandToExecute?.isSafeConcurrent) { void handleSlashCommand(submittedValue); addInput(submittedValue); diff --git a/packages/cli/src/ui/commands/clearCommand.test.ts b/packages/cli/src/ui/commands/clearCommand.test.ts index 77f6e4854d..0f147cc0d8 100644 --- a/packages/cli/src/ui/commands/clearCommand.test.ts +++ b/packages/cli/src/ui/commands/clearCommand.test.ts @@ -45,6 +45,7 @@ describe('clearCommand', () => { fireSessionEndEvent: vi.fn().mockResolvedValue(undefined), fireSessionStartEvent: vi.fn().mockResolvedValue(undefined), }), + setActiveExtensionContext: vi.fn(), injectionService: { clear: mockHintClear, }, diff --git a/packages/cli/src/ui/commands/clearCommand.ts b/packages/cli/src/ui/commands/clearCommand.ts index fb032da811..e10633294f 100644 --- a/packages/cli/src/ui/commands/clearCommand.ts +++ b/packages/cli/src/ui/commands/clearCommand.ts @@ -30,8 +30,9 @@ export const clearCommand: SlashCommand = { await hookSystem.fireSessionEndEvent(SessionEndReason.Clear); } - // Reset user steering hints + // Reset user steering hints and extension context config?.injectionService.clear(); + config?.setActiveExtensionContext(undefined); // Start a new conversation recording with a new session ID // We MUST do this before calling resetChat() so the new ChatRecordingService diff --git a/packages/cli/src/utils/commands.ts b/packages/cli/src/utils/commands.ts index a96537aadf..ad651248d4 100644 --- a/packages/cli/src/utils/commands.ts +++ b/packages/cli/src/utils/commands.ts @@ -10,6 +10,7 @@ export type ParsedSlashCommand = { commandToExecute: SlashCommand | undefined; args: string; canonicalPath: string[]; + extensionContext?: string; }; /** @@ -69,6 +70,8 @@ export const parseSlashCommand = ( const args = parts.slice(pathIndex).join(' '); + const extensionContext = commandToExecute?.extensionName; + // Backtrack if the matched (sub)command doesn't take arguments but some were provided, // AND the parent command is capable of handling them. if ( @@ -82,8 +85,9 @@ export const parseSlashCommand = ( commandToExecute: parentCommand, args: parts.slice(pathIndex - 1).join(' '), canonicalPath: canonicalPath.slice(0, -1), + extensionContext: parentCommand.extensionName, }; } - return { commandToExecute, args, canonicalPath }; + return { commandToExecute, args, canonicalPath, extensionContext }; };