fix(cli): consistently clear sticky extension context

This fixes a bug where the active extension context would remain sticky when a user switched from an extension command to a standard non-plan command, or to an extension without a plan directory.

The context is now correctly reset to undefined when an extension command without a plan directory is executed, preventing subsequent plan mode invocations from incorrectly targeting the previous extension's folder.
This commit is contained in:
Mahima Shanware
2026-04-06 18:45:06 +00:00
parent 0a8195fb3a
commit b2f7c157ce
2 changed files with 120 additions and 0 deletions
+118
View File
@@ -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', () => {
+2
View File
@@ -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);