diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 21b47e0ee5..39764693c9 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -401,7 +401,9 @@ export const AppContainer = (props: AppContainerProps) => { const [planModeUIHistoryStartIndex, setPlanModeUIHistoryStartIndex] = useState(() => // Initialize if starting in PLAN mode (e.g. session resume) - config.getApprovalMode() === ApprovalMode.PLAN ? 0 : null, + config.getApprovalMode() === ApprovalMode.PLAN + ? config.getPlanModeHistoryStartIndex() + : null, ); useEffect(() => { @@ -411,7 +413,7 @@ export const AppContainer = (props: AppContainerProps) => { // 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, + prev === null ? config.getPlanModeHistoryStartIndex() : prev, ); } else { // Reset the index when leaving PLAN mode @@ -422,7 +424,7 @@ export const AppContainer = (props: AppContainerProps) => { return () => { coreEvents.off(CoreEvent.ApprovalModeChanged, handleApprovalModeChanged); }; - }, [historyManager.history.length]); + }, [config]); const logger = useLogger(config.storage); const { inputHistory, addInput, initializeFromLogger } = diff --git a/packages/cli/src/ui/components/ExitPlanModeDialog.tsx b/packages/cli/src/ui/components/ExitPlanModeDialog.tsx index 1be6163ebd..4e97b1709e 100644 --- a/packages/cli/src/ui/components/ExitPlanModeDialog.tsx +++ b/packages/cli/src/ui/components/ExitPlanModeDialog.tsx @@ -169,8 +169,18 @@ export const ExitPlanModeDialog: React.FC = ({ const [step, setStep] = useState(ApprovalStep.PLAN_APPROVAL); const [selectedApprovalMode, setSelectedApprovalMode] = useState(null); + const [pendingApproval, setPendingApproval] = useState<{ + mode: ApprovalMode; + clear: boolean; + } | null>(null); const { setSetting } = useSettingsStore(); + useEffect(() => { + if (pendingApproval) { + onApprove(pendingApproval.mode, pendingApproval.clear); + } + }, [pendingApproval, onApprove]); + const handleOpenEditor = useCallback(async () => { try { await openFileInEditor(planPath, stdin, setRawMode, getPreferredEditor()); @@ -356,11 +366,10 @@ export const ExitPlanModeDialog: React.FC = ({ } 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); + setPendingApproval({ + mode: selectedApprovalMode, + clear: clearConversation, + }); } }} onCancel={() => setStep(ApprovalStep.PLAN_APPROVAL)} diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 3844138d50..fbfc96e662 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -3117,3 +3117,42 @@ describe('Model Persistence Bug Fix (#19864)', () => { expect(config.getModel()).toBe(PREVIEW_GEMINI_3_1_MODEL); }); }); + +describe('isClearContextOnPlanApprovalEnabled', () => { + const baseParams: ConfigParameters = { + sessionId: 'test', + targetDir: '.', + debugMode: false, + model: 'test-model', + cwd: '.', + }; + + it('should return false by default when no setting or override is present', () => { + const config = new Config(baseParams); + expect(config.isClearContextOnPlanApprovalEnabled()).toBe(false); + }); + + it('should return true when a session override is set to true', () => { + const config = new Config(baseParams); + config.setClearContextOnPlanApprovalSessionOverride(true); + expect(config.isClearContextOnPlanApprovalEnabled()).toBe(true); + }); + + it('should return false when a session override is set to false', () => { + const config = new Config(baseParams); + config.setClearContextOnPlanApprovalSessionOverride(false); + expect(config.isClearContextOnPlanApprovalEnabled()).toBe(false); + }); + + it('should prefer session override over persistent setting', () => { + const config = new Config({ + ...baseParams, + planSettings: { + clearContextOnApproval: true, + }, + } as unknown as ConfigParameters); + expect(config.isClearContextOnPlanApprovalEnabled()).toBe(true); + config.setClearContextOnPlanApprovalSessionOverride(false); + expect(config.isClearContextOnPlanApprovalEnabled()).toBe(false); + }); +}); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index e08ceeb408..b3b59f89ca 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -2487,7 +2487,7 @@ export class Config implements McpContext { } isClearContextOnPlanApprovalEnabled(): boolean { - return this.getClearContextOnPlanApproval() ?? true; + return this.getClearContextOnPlanApproval() ?? false; } setClearContextOnPlanApprovalSessionOverride(value: boolean): void { diff --git a/packages/core/src/tools/exit-plan-mode.ts b/packages/core/src/tools/exit-plan-mode.ts index ee17d35d62..307cfbcd13 100644 --- a/packages/core/src/tools/exit-plan-mode.ts +++ b/packages/core/src/tools/exit-plan-mode.ts @@ -220,13 +220,28 @@ export class ExitPlanModeInvocation extends BaseToolInvocation< const history = geminiClient.getHistory(); const startIndex = this.config.getPlanModeHistoryStartIndex(); - // Find the first user message at or after the start index. - // This represents the beginning of the current planning phase. + // Find the user message that initiated the plan mode. let planningUserIndex = -1; - for (let i = startIndex; i < history.length - 1; i++) { - if (history[i].role === 'user') { - planningUserIndex = i; - break; + + // If the message exactly at startIndex is a user message, then plan mode + // was initiated via a UI/CLI command before the message was added to history. + if ( + startIndex < history.length && + history[startIndex].role === 'user' + ) { + planningUserIndex = startIndex; + } else { + // Otherwise, Plan mode was initiated via the `enter_plan_mode` tool. + // The initiating user message is the last user message before startIndex. + for ( + let i = Math.min(startIndex, history.length - 1); + i >= 0; + i-- + ) { + if (history[i].role === 'user') { + planningUserIndex = i; + break; + } } }