feat(cli): apply MRU active extension sticky context based on UI events

This commit is contained in:
Mahima Shanware
2026-04-14 08:23:41 +00:00
parent bc999138b7
commit dfb641c2e9
5 changed files with 132 additions and 22 deletions
@@ -670,6 +670,8 @@ describe('useSlashCommandProcessor', () => {
expect(actionResult).toEqual({
type: 'submit_prompt',
content: [{ text: 'The actual prompt from the TOML file.' }],
activeExtensionName: undefined,
clearExtensionMRU: false,
});
expect(mockAddItem).toHaveBeenCalledWith(
@@ -704,6 +706,8 @@ describe('useSlashCommandProcessor', () => {
expect(actionResult).toEqual({
type: 'submit_prompt',
content: [{ text: 'The actual prompt from the mcp command.' }],
activeExtensionName: undefined,
clearExtensionMRU: false,
});
expect(mockAddItem).toHaveBeenCalledWith(
@@ -448,6 +448,8 @@ export const useSlashCommandProcessor = (
toolName: result.toolName,
toolArgs: result.toolArgs,
postSubmitPrompt: result.postSubmitPrompt,
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'message':
addItem(
@@ -460,7 +462,11 @@ export const useSlashCommandProcessor = (
},
Date.now(),
);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'logout':
// Show logout confirmation dialog with Login/Exit options
setCustomDialog(
@@ -476,30 +482,62 @@ export const useSlashCommandProcessor = (
},
}),
);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'dialog':
switch (result.dialog) {
case 'auth':
actions.openAuthDialog();
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'theme':
actions.openThemeDialog();
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'editor':
actions.openEditorDialog();
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'privacy':
actions.openPrivacyNotice();
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'sessionBrowser':
actions.openSessionBrowser();
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'settings':
actions.openSettingsDialog();
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'model':
actions.openModelDialog();
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'agentConfig': {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const props = result.props as Record<string, unknown>;
@@ -522,16 +560,28 @@ export const useSlashCommandProcessor = (
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
props['definition'] as AgentDefinition,
);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
}
case 'permissions':
actions.openPermissionsDialog(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
result.props as { targetDirectory?: string },
);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'help':
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
default: {
const unhandled: never = result.dialog;
throw new Error(
@@ -545,16 +595,26 @@ export const useSlashCommandProcessor = (
result.history.forEach((item, index) => {
fullCommandContext.ui.addItem(item, index);
});
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
}
case 'quit':
actions.quit(result.messages);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'submit_prompt':
return {
type: 'submit_prompt',
content: result.content,
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
case 'confirm_shell_commands': {
const callId = `expansion-${Date.now()}`;
@@ -611,7 +671,11 @@ export const useSlashCommandProcessor = (
},
Date.now(),
);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
}
if (outcome === ToolConfirmationOutcome.ProceedAlways) {
@@ -649,7 +713,11 @@ export const useSlashCommandProcessor = (
},
Date.now(),
);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
}
return await handleSlashCommand(
@@ -660,7 +728,11 @@ export const useSlashCommandProcessor = (
}
case 'custom_dialog': {
setCustomDialog(result.component);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
}
default: {
const unhandled: never = result;
@@ -671,7 +743,11 @@ export const useSlashCommandProcessor = (
}
}
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
} else if (commandToExecute.subCommands) {
const helpText = `Command '/${commandToExecute.name}' requires a subcommand. Available:\n${commandToExecute.subCommands
.map((sc) => ` - ${sc.name}: ${sc.description || ''}`)
@@ -681,11 +757,19 @@ export const useSlashCommandProcessor = (
content: helpText,
timestamp: new Date(),
});
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
}
}
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
} catch (e: unknown) {
hasError = true;
if (config) {
@@ -704,7 +788,11 @@ export const useSlashCommandProcessor = (
},
Date.now(),
);
return { type: 'handled' };
return {
type: 'handled',
activeExtensionName: commandToExecute?.extensionName,
clearExtensionMRU: commandToExecute?.name === 'clear',
};
} finally {
if (config && resolvedCommandPath[0] && !hasError) {
const event = makeSlashCommandEvent({
@@ -935,6 +935,13 @@ export const useGeminiStream = (
: false;
if (slashCommandResult) {
if (slashCommandResult.clearExtensionMRU) {
config.setActiveExtensionName(undefined);
} else if (slashCommandResult.activeExtensionName) {
config.setActiveExtensionName(
slashCommandResult.activeExtensionName,
);
}
switch (slashCommandResult.type) {
case 'schedule_tool': {
const { toolName, toolArgs, postSubmitPrompt } =
@@ -1750,6 +1757,12 @@ export const useGeminiStream = (
const handleApprovalModeChange = useCallback(
async (newApprovalMode: ApprovalMode) => {
if (
previousApprovalModeRef.current === ApprovalMode.PLAN &&
newApprovalMode !== ApprovalMode.PLAN
) {
config.setActiveExtensionName(undefined);
}
if (
previousApprovalModeRef.current === ApprovalMode.PLAN &&
newApprovalMode !== ApprovalMode.PLAN &&
+6
View File
@@ -498,6 +498,8 @@ export interface ConsoleMessageItem {
export interface SubmitPromptResult {
type: 'submit_prompt';
content: PartListUnion;
activeExtensionName?: string;
clearExtensionMRU?: boolean;
}
/**
@@ -509,9 +511,13 @@ export type SlashCommandProcessorResult =
toolName: string;
toolArgs: Record<string, unknown>;
postSubmitPrompt?: PartListUnion;
activeExtensionName?: string;
clearExtensionMRU?: boolean;
}
| {
type: 'handled'; // Indicates the command was processed and no further action is needed.
activeExtensionName?: string;
clearExtensionMRU?: boolean;
}
| SubmitPromptResult;
@@ -64,7 +64,6 @@ export class ExitPlanModeTool extends BaseDeclarativeTool<
}
return this.config.storage.getPlansDir(customDir);
}
protected override validateToolParamValues(
params: ExitPlanModeParams,
): string | null {