diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx index 6c69aa332f..1e0911bb2e 100644 --- a/packages/cli/src/ui/AppContainer.test.tsx +++ b/packages/cli/src/ui/AppContainer.test.tsx @@ -3196,6 +3196,92 @@ describe('AppContainer State Management', () => { unmount(); }); + it('sets activeExtensionContext when an extension command WITH a plan dir is executed', async () => { + const { checkPermissions } = await import( + './hooks/atCommandProcessor.js' + ); + vi.mocked(checkPermissions).mockResolvedValue([]); + + mockedUseSlashCommandProcessor.mockReturnValue({ + handleSlashCommand: vi.fn(), + slashCommands: [ + { + name: 'conductor:setup', + extensionName: 'conductor', + description: 'test', + action: vi.fn(), + }, + ], + pendingHistoryItems: [], + commandContext: {}, + shellConfirmationRequest: null, + confirmationRequest: null, + }); + + const spyHasExtensionPlanDir = vi + .spyOn(mockConfig, 'hasExtensionPlanDir') + .mockReturnValue(true); + const spySetActiveExtensionContext = vi.spyOn( + mockConfig, + 'setActiveExtensionContext', + ); + + const { unmount } = await act(async () => renderAppContainer()); + + expect(capturedUIActions).toBeTruthy(); + + await act(async () => + capturedUIActions.handleFinalSubmit('/conductor:setup'), + ); + + expect(spyHasExtensionPlanDir).toHaveBeenCalledWith('conductor'); + expect(spySetActiveExtensionContext).toHaveBeenCalledWith('conductor'); + + unmount(); + }); + + it('clears activeExtensionContext when an extension command WITHOUT a plan dir is executed', async () => { + const { checkPermissions } = await import( + './hooks/atCommandProcessor.js' + ); + vi.mocked(checkPermissions).mockResolvedValue([]); + + mockedUseSlashCommandProcessor.mockReturnValue({ + handleSlashCommand: vi.fn(), + slashCommands: [ + { + name: 'other:cmd', + extensionName: 'other', + description: 'test', + action: vi.fn(), + }, + ], + pendingHistoryItems: [], + commandContext: {}, + shellConfirmationRequest: null, + confirmationRequest: null, + }); + + const spyHasExtensionPlanDir = vi + .spyOn(mockConfig, 'hasExtensionPlanDir') + .mockReturnValue(false); + const spySetActiveExtensionContext = vi.spyOn( + mockConfig, + 'setActiveExtensionContext', + ); + + const { unmount } = await act(async () => renderAppContainer()); + + expect(capturedUIActions).toBeTruthy(); + + await act(async () => capturedUIActions.handleFinalSubmit('/other:cmd')); + + expect(spyHasExtensionPlanDir).toHaveBeenCalledWith('other'); + expect(spySetActiveExtensionContext).toHaveBeenCalledWith(undefined); + + unmount(); + }); + it('clears activeExtensionContext when /plan is explicitly executed', async () => { const { checkPermissions } = await import( './hooks/atCommandProcessor.js' @@ -3228,6 +3314,38 @@ describe('AppContainer State Management', () => { unmount(); }); + + it('does NOT clear activeExtensionContext when a standard non-plan command is executed', async () => { + const { checkPermissions } = await import( + './hooks/atCommandProcessor.js' + ); + vi.mocked(checkPermissions).mockResolvedValue([]); + + mockedUseSlashCommandProcessor.mockReturnValue({ + handleSlashCommand: vi.fn(), + slashCommands: [{ name: 'help', description: 'test', action: vi.fn() }], + pendingHistoryItems: [], + commandContext: {}, + shellConfirmationRequest: null, + confirmationRequest: null, + }); + + const spySetActiveExtensionContext = vi.spyOn( + mockConfig, + 'setActiveExtensionContext', + ); + + const { unmount } = await act(async () => renderAppContainer()); + + expect(capturedUIActions).toBeTruthy(); + + await act(async () => capturedUIActions.handleFinalSubmit('/help')); + + // It should not touch the context at all + expect(spySetActiveExtensionContext).not.toHaveBeenCalled(); + + unmount(); + }); }); describe('Overflow Hint Handling', () => { diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index d7c5c2cc5d..73da24cdef 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -1349,6 +1349,8 @@ Logging in with Google... Restarting Gemini CLI to continue. if (parsedCommand.extensionContext) { if (config.hasExtensionPlanDir(parsedCommand.extensionContext)) { config.setActiveExtensionContext(parsedCommand.extensionContext); + } else { + config.setActiveExtensionContext(undefined); } } else if (parsedCommand.commandToExecute?.name === 'plan') { config.setActiveExtensionContext(undefined);