update to a dialog for clearing context

This commit is contained in:
A.K.M. Adib
2026-03-05 16:42:55 -05:00
parent defc28e42d
commit ab23cdc6af
8 changed files with 264 additions and 126 deletions
+10
View File
@@ -299,6 +299,16 @@ const SETTINGS_SCHEMA = {
'Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pro for the planning phase and Flash for the implementation phase.',
showInDialog: true,
},
clearContextOnApproval: {
type: 'boolean',
label: 'Clear Context on Plan Approval',
category: 'General',
requiresRestart: false,
default: undefined as boolean | undefined,
description:
'Automatically clear conversation context after a plan is approved and implementation begins.',
showInDialog: true,
},
},
},
retryFetchErrors: {
+14 -2
View File
@@ -399,12 +399,23 @@ export const AppContainer = (props: AppContainerProps) => {
const [isConfigInitialized, setConfigInitialized] = useState(false);
const [planModeUIHistoryStartIndex, setPlanModeUIHistoryStartIndex] =
useState<number | null>(null);
useState<number | null>(() =>
// Initialize if starting in PLAN mode (e.g. session resume)
config.getApprovalMode() === ApprovalMode.PLAN ? 0 : null
);
useEffect(() => {
const handleApprovalModeChanged = ({ mode }: { mode: ApprovalMode }) => {
if (mode === ApprovalMode.PLAN) {
setPlanModeUIHistoryStartIndex(historyManager.history.length);
// Only set the start index if we aren't already tracking one.
// This ensures that if we are already in PLAN mode and another
// event fires, we don't accidentally move the start index forward.
setPlanModeUIHistoryStartIndex((prev) =>
prev === null ? historyManager.history.length : prev,
);
} else {
// Reset the index when leaving PLAN mode
setPlanModeUIHistoryStartIndex(null);
}
};
coreEvents.on(CoreEvent.ApprovalModeChanged, handleApprovalModeChanged);
@@ -1367,6 +1378,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
planModeUIHistoryStartIndex,
);
historyManager.loadHistory(newHistory);
setPlanModeUIHistoryStartIndex(null);
refreshStatic();
}
}, [planModeUIHistoryStartIndex, historyManager, refreshStatic]);
@@ -689,8 +689,9 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
},
);
// Only add custom option for choice type, not yesno
if (question.type !== 'yesno') {
// Add custom option for choice type if allowed
const allowCustom = question.allowCustomOption ?? true;
if (question.type === 'choice' && allowCustom) {
const otherItem: OptionItem = {
key: 'other',
label: customOptionText || '',
@@ -713,7 +714,13 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
}
return list;
}, [questionOptions, question.multiSelect, question.type, customOptionText]);
}, [
questionOptions,
question.allowCustomOption,
question.type,
question.multiSelect,
customOptionText,
]);
const handleHighlight = useCallback(
(itemValue: OptionItem) => {
@@ -163,6 +163,8 @@ Implement a comprehensive authentication system with multiple providers.
writeTextFile: vi.fn(),
}),
getUseAlternateBuffer: () => options?.useAlternateBuffer ?? true,
getClearContextOnPlanApproval: () => undefined,
setClearContextOnPlanApprovalSessionOverride: vi.fn(),
} as unknown as import('@google/gemini-cli-core').Config,
},
);
@@ -206,6 +208,16 @@ Implement a comprehensive authentication system with multiple providers.
writeKey(stdin, '\r');
await waitFor(() => {
expect(lastFrame()).toContain('Clear conversation context');
});
// Select 'No' option (index 3)
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
await waitFor(() => {
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.AUTO_EDIT, false);
});
@@ -222,6 +234,15 @@ Implement a comprehensive authentication system with multiple providers.
expect(lastFrame()).toContain('Add user authentication');
});
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
await waitFor(() => {
expect(lastFrame()).toContain('Clear conversation context');
});
// Select 'No' option (index 3)
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
@@ -350,8 +371,15 @@ Implement a comprehensive authentication system with multiple providers.
expect(lastFrame()).toContain('Add user authentication');
});
// Press '3' to select third option directly
writeKey(stdin, '3');
// Press '2' to select second option directly (Manual)
writeKey(stdin, '2');
await waitFor(() => {
expect(lastFrame()).toContain('Clear conversation context');
});
// Press '4' to select fourth option directly (No)
writeKey(stdin, '4');
await waitFor(() => {
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.DEFAULT, false);
@@ -372,8 +400,6 @@ Implement a comprehensive authentication system with multiple providers.
// Navigate to feedback option and start typing
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r'); // Select to focus input
// Type some feedback
@@ -443,6 +469,8 @@ Implement a comprehensive authentication system with multiple providers.
writeTextFile: vi.fn(),
}),
getUseAlternateBuffer: () => useAlternateBuffer ?? true,
getClearContextOnPlanApproval: () => undefined,
setClearContextOnPlanApprovalSessionOverride: vi.fn(),
} as unknown as import('@google/gemini-cli-core').Config,
},
);
@@ -499,8 +527,6 @@ Implement a comprehensive authentication system with multiple providers.
// Focus feedback option
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
// Press Enter without typing anything
writeKey(stdin, '\r');
@@ -528,8 +554,6 @@ Implement a comprehensive authentication system with multiple providers.
// Navigate to feedback option and start typing
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
// Type some feedback
for (const char of 'test') {
@@ -538,11 +562,20 @@ Implement a comprehensive authentication system with multiple providers.
// Now use up arrow to navigate back to a different option
writeKey(stdin, '\x1b[A'); // Up arrow
writeKey(stdin, '\x1b[A'); // Up arrow
// Press Enter to select the manually accept edits option
writeKey(stdin, '\r');
await waitFor(() => {
expect(lastFrame()).toContain('Clear conversation context');
});
// Select 'No' option (index 3)
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
await waitFor(() => {
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.DEFAULT, false);
});
@@ -19,6 +19,8 @@ import {
} from '@google/gemini-cli-core';
import { theme } from '../semantic-colors.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { useSettingsStore } from '../contexts/SettingsContext.js';
import { SettingScope } from '../../config/settings.js';
import { AskUserDialog } from './AskUserDialog.js';
import { openFileInEditor } from '../utils/editorUtils.js';
import { useKeypress } from '../hooks/useKeypress.js';
@@ -41,6 +43,11 @@ enum PlanStatus {
Error = 'error',
}
enum ApprovalStep {
PLAN_APPROVAL = 'plan_approval',
CONTEXT_CHOICE = 'context_choice',
}
interface PlanContentState {
status: PlanStatus;
content?: string;
@@ -50,9 +57,14 @@ interface PlanContentState {
enum ApprovalOption {
Auto = 'Yes, automatically accept edits',
AutoClear = 'Yes, automatically accept edits & clear conversation',
Manual = 'Yes, manually accept edits',
ManualClear = 'Yes, manually accept edits & clear conversation',
}
enum ContextOption {
Once = 'Allow once',
Session = 'Allow for this session',
Always = 'Allow for all future sessions',
No = 'No',
}
/**
@@ -154,6 +166,10 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
const planState = usePlanContent(planPath, config);
const { refresh } = planState;
const [showLoading, setShowLoading] = useState(false);
const [step, setStep] = useState<ApprovalStep>(ApprovalStep.PLAN_APPROVAL);
const [selectedApprovalMode, setSelectedApprovalMode] =
useState<ApprovalMode | null>(null);
const { setSetting } = useSettingsStore();
const handleOpenEditor = useCallback(async () => {
try {
@@ -227,64 +243,133 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
const editHint = formatCommand(Command.OPEN_EXTERNAL_EDITOR);
return (
<Box flexDirection="column" width={width}>
<AskUserDialog
questions={[
{
type: QuestionType.CHOICE,
header: 'Approval',
question: planContent,
options: [
{
label: ApprovalOption.Auto,
description:
'Approves plan and allows tools to run automatically',
},
{
label: ApprovalOption.AutoClear,
description:
'Approves plan, runs automatically, and clears prior conversation context',
},
{
label: ApprovalOption.Manual,
description:
'Approves plan but requires confirmation for each tool',
},
{
label: ApprovalOption.ManualClear,
description:
'Approves plan, requires confirmation, and clears prior conversation context',
},
],
placeholder: 'Type your feedback...',
multiSelect: false,
},
]}
onSubmit={(answers) => {
const answer = answers['0'];
const clearConversation =
answer === ApprovalOption.AutoClear ||
answer === ApprovalOption.ManualClear;
if (
answer === ApprovalOption.Auto ||
answer === ApprovalOption.AutoClear
) {
onApprove(ApprovalMode.AUTO_EDIT, clearConversation);
} else if (
answer === ApprovalOption.Manual ||
answer === ApprovalOption.ManualClear
) {
onApprove(ApprovalMode.DEFAULT, clearConversation);
} else if (answer) {
onFeedback(answer);
}
}}
onCancel={onCancel}
width={width}
availableHeight={availableHeight}
extraParts={[`${editHint} to edit plan`]}
/>
</Box>
);
if (step === ApprovalStep.PLAN_APPROVAL) {
return (
<Box flexDirection="column" width={width}>
<AskUserDialog
key="plan-approval"
questions={[
{
type: QuestionType.CHOICE,
header: 'Approval',
question: planContent,
options: [
{
label: ApprovalOption.Auto,
description:
'Approves plan and allows tools to run automatically',
},
{
label: ApprovalOption.Manual,
description:
'Approves plan but requires confirmation for each tool',
},
],
placeholder: 'Type your feedback...',
multiSelect: false,
},
]}
onSubmit={(answers) => {
const answer = answers['0'];
let nextMode: ApprovalMode | null = null;
if (answer === ApprovalOption.Auto) {
nextMode = ApprovalMode.AUTO_EDIT;
} else if (answer === ApprovalOption.Manual) {
nextMode = ApprovalMode.DEFAULT;
} else if (answer) {
onFeedback(answer);
return;
}
if (nextMode) {
const clearContext = config.getClearContextOnPlanApproval();
if (clearContext !== undefined) {
onApprove(nextMode, clearContext);
} else {
setSelectedApprovalMode(nextMode);
setStep(ApprovalStep.CONTEXT_CHOICE);
}
}
}}
onCancel={onCancel}
width={width}
availableHeight={availableHeight}
extraParts={[`${editHint} to edit plan`]}
/>
</Box>
);
}
if (step === ApprovalStep.CONTEXT_CHOICE) {
return (
<Box flexDirection="column" width={width}>
<AskUserDialog
key="context-choice"
questions={[
{
type: QuestionType.CHOICE,
header: 'Context',
question:
'Clear conversation context before implementing? (Keeps pre-plan history)',
options: [
{
label: ContextOption.Once,
description: 'Clear context this time only',
},
{
label: ContextOption.Session,
description: 'Clear context for this entire session',
},
{
label: ContextOption.Always,
description: 'Always clear context on plan approval',
},
{ label: ContextOption.No, description: 'Keep context' },
],
multiSelect: false,
allowCustomOption: false,
},
]}
onSubmit={(answers) => {
const answer = answers['0'];
let clearConversation = false;
if (answer === ContextOption.Once) {
clearConversation = true;
} else if (answer === ContextOption.Session) {
clearConversation = true;
config.setClearContextOnPlanApprovalSessionOverride(true);
} else if (answer === ContextOption.Always) {
clearConversation = true;
setSetting(
SettingScope.User,
'general.plan.clearContextOnApproval',
true,
);
} else if (answer === ContextOption.No) {
clearConversation = false;
setSetting(
SettingScope.User,
'general.plan.clearContextOnApproval',
false,
);
}
if (selectedApprovalMode) {
// Wrap in setTimeout to avoid 'Maximum update depth exceeded'
// when setSetting triggers a re-render of the parent.
setTimeout(() => {
onApprove(selectedApprovalMode, clearConversation);
}, 0);
}
}}
onCancel={() => setStep(ApprovalStep.PLAN_APPROVAL)}
width={width}
availableHeight={availableHeight}
/>
</Box>
);
}
return null;
};
@@ -17,14 +17,11 @@ Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 2. Yes, automatically accept edits & clear conversation
Approves plan, runs automatically, and clears prior conversation context
3. Yes, manually accept edits
● 2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
@@ -47,14 +44,11 @@ Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, automatically accept edits & clear conversation
Approves plan, runs automatically, and clears prior conversation context
3. Yes, manually accept edits
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
@@ -82,14 +76,11 @@ Implementation Steps
8. Add multi-factor authentication in src/auth/MFAService.ts
... last 22 lines hidden (Ctrl+O to show) ...
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, automatically accept edits & clear conversation
Approves plan, runs automatically, and clears prior conversation context
3. Yes, manually accept edits
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
@@ -112,14 +103,11 @@ Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, automatically accept edits & clear conversation
Approves plan, runs automatically, and clears prior conversation context
3. Yes, manually accept edits
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
@@ -144,14 +132,9 @@ Files to Modify
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 2. Yes, automatically accept edits & clear conversation
Approves plan, runs automatically, and clears prior conversation context
3. Yes, manually accept edits
● 2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
4. Yes, manually accept edits & clear conversation
Approves plan, requires confirmation, and clears prior conversation
context
5. Type your feedback...
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
@@ -176,14 +159,9 @@ Files to Modify
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, automatically accept edits & clear conversation
Approves plan, runs automatically, and clears prior conversation context
3. Yes, manually accept edits
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
4. Yes, manually accept edits & clear conversation
Approves plan, requires confirmation, and clears prior conversation
context
5. Type your feedback...
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
@@ -234,14 +212,9 @@ Testing Strategy
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, automatically accept edits & clear conversation
Approves plan, runs automatically, and clears prior conversation context
3. Yes, manually accept edits
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
4. Yes, manually accept edits & clear conversation
Approves plan, requires confirmation, and clears prior conversation
context
5. Type your feedback...
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
@@ -266,14 +239,9 @@ Files to Modify
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, automatically accept edits & clear conversation
Approves plan, runs automatically, and clears prior conversation context
3. Yes, manually accept edits
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
4. Yes, manually accept edits & clear conversation
Approves plan, requires confirmation, and clears prior conversation
context
5. Type your feedback...
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
+21
View File
@@ -173,6 +173,7 @@ export interface SummarizeToolOutputSettings {
export interface PlanSettings {
directory?: string;
modelRouting?: boolean;
clearContextOnApproval?: boolean;
}
export interface TelemetrySettings {
@@ -796,6 +797,9 @@ export class Config implements McpContext {
private readonly planEnabled: boolean;
private readonly trackerEnabled: boolean;
private readonly planModeRoutingEnabled: boolean;
private readonly clearContextOnPlanApproval?: boolean;
private clearContextOnPlanApprovalSessionOverride: boolean | undefined =
undefined;
private readonly modelSteering: boolean;
private contextManager?: ContextManager;
private terminalBackground: string | undefined = undefined;
@@ -887,6 +891,8 @@ export class Config implements McpContext {
this.planEnabled = params.plan ?? false;
this.trackerEnabled = params.tracker ?? false;
this.planModeRoutingEnabled = params.planSettings?.modelRouting ?? true;
this.clearContextOnPlanApproval =
params.planSettings?.clearContextOnApproval;
this.enableEventDrivenScheduler = params.enableEventDrivenScheduler ?? true;
this.skillsSupport = params.skillsSupport ?? true;
this.disabledSkills = params.disabledSkills ?? [];
@@ -2467,6 +2473,21 @@ export class Config implements McpContext {
return this.planModeRoutingEnabled;
}
getClearContextOnPlanApproval(): boolean | undefined {
return (
this.clearContextOnPlanApprovalSessionOverride ??
this.clearContextOnPlanApproval
);
}
isClearContextOnPlanApprovalEnabled(): boolean {
return this.getClearContextOnPlanApproval() ?? true;
}
setClearContextOnPlanApprovalSessionOverride(value: boolean): void {
this.clearContextOnPlanApprovalSessionOverride = value;
}
async getNumericalRoutingEnabled(): Promise<boolean> {
await this.ensureExperimentsLoaded();
@@ -160,6 +160,8 @@ export interface Question {
options?: QuestionOption[];
/** Allow multiple selections. Only applies when type='choice'. */
multiSelect?: boolean;
/** Whether to allow a custom 'Other' option for 'choice' types. Defaults to true. */
allowCustomOption?: boolean;
/** Placeholder hint text. For type='text', shown in the input field. For type='choice', shown in the "Other" custom input. */
placeholder?: string;
}