mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 02:24:09 -07:00
complete
This commit is contained in:
@@ -591,6 +591,7 @@ const mockUIActions: UIActions = {
|
||||
setConstrainHeight: vi.fn(),
|
||||
onEscapePromptChange: vi.fn(),
|
||||
refreshStatic: vi.fn(),
|
||||
handleClearPlanContext: vi.fn(),
|
||||
handleFinalSubmit: vi.fn(),
|
||||
handleClearScreen: vi.fn(),
|
||||
handleProQuotaChoice: vi.fn(),
|
||||
|
||||
@@ -50,7 +50,7 @@ import {
|
||||
type GeminiUserTier,
|
||||
type UserFeedbackPayload,
|
||||
type AgentDefinition,
|
||||
type ApprovalMode,
|
||||
ApprovalMode,
|
||||
IdeClient,
|
||||
ideContextStore,
|
||||
getErrorMessage,
|
||||
@@ -398,6 +398,20 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
);
|
||||
|
||||
const [isConfigInitialized, setConfigInitialized] = useState(false);
|
||||
const [planModeUIHistoryStartIndex, setPlanModeUIHistoryStartIndex] =
|
||||
useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleApprovalModeChanged = ({ mode }: { mode: ApprovalMode }) => {
|
||||
if (mode === ApprovalMode.PLAN) {
|
||||
setPlanModeUIHistoryStartIndex(historyManager.history.length);
|
||||
}
|
||||
};
|
||||
coreEvents.on(CoreEvent.ApprovalModeChanged, handleApprovalModeChanged);
|
||||
return () => {
|
||||
coreEvents.off(CoreEvent.ApprovalModeChanged, handleApprovalModeChanged);
|
||||
};
|
||||
}, [historyManager.history.length]);
|
||||
|
||||
const logger = useLogger(config.storage);
|
||||
const { inputHistory, addInput, initializeFromLogger } =
|
||||
@@ -1346,6 +1360,17 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
triggerExpandHint,
|
||||
]);
|
||||
|
||||
const handleClearPlanContext = useCallback(() => {
|
||||
if (planModeUIHistoryStartIndex !== null) {
|
||||
const newHistory = historyManager.history.slice(
|
||||
0,
|
||||
planModeUIHistoryStartIndex,
|
||||
);
|
||||
historyManager.loadHistory(newHistory);
|
||||
refreshStatic();
|
||||
}
|
||||
}, [planModeUIHistoryStartIndex, historyManager, refreshStatic]);
|
||||
|
||||
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
|
||||
|
||||
/**
|
||||
@@ -2456,6 +2481,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
setConstrainHeight,
|
||||
onEscapePromptChange: handleEscapePromptChange,
|
||||
refreshStatic,
|
||||
handleClearPlanContext,
|
||||
handleFinalSubmit,
|
||||
handleClearScreen,
|
||||
handleProQuotaChoice,
|
||||
@@ -2548,6 +2574,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
setConstrainHeight,
|
||||
handleEscapePromptChange,
|
||||
refreshStatic,
|
||||
handleClearPlanContext,
|
||||
handleFinalSubmit,
|
||||
handleClearScreen,
|
||||
handleProQuotaChoice,
|
||||
|
||||
@@ -207,7 +207,7 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.AUTO_EDIT);
|
||||
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.AUTO_EDIT, false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -222,11 +222,12 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
expect(lastFrame()).toContain('Add user authentication');
|
||||
});
|
||||
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.DEFAULT);
|
||||
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.DEFAULT, false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -349,11 +350,11 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
expect(lastFrame()).toContain('Add user authentication');
|
||||
});
|
||||
|
||||
// Press '2' to select second option directly
|
||||
writeKey(stdin, '2');
|
||||
// Press '3' to select third option directly
|
||||
writeKey(stdin, '3');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.DEFAULT);
|
||||
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.DEFAULT, false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -371,6 +372,8 @@ 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
|
||||
@@ -493,7 +496,9 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
expect(lastFrame()).toContain('Add user authentication');
|
||||
});
|
||||
|
||||
// Navigate to feedback option
|
||||
// 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
|
||||
|
||||
@@ -523,6 +528,8 @@ 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') {
|
||||
@@ -531,12 +538,13 @@ 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 second option (manually accept edits)
|
||||
// Press Enter to select the manually accept edits option
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.DEFAULT);
|
||||
expect(onApprove).toHaveBeenCalledWith(ApprovalMode.DEFAULT, false);
|
||||
});
|
||||
expect(onFeedback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ import { formatCommand } from '../utils/keybindingUtils.js';
|
||||
|
||||
export interface ExitPlanModeDialogProps {
|
||||
planPath: string;
|
||||
onApprove: (approvalMode: ApprovalMode) => void;
|
||||
onApprove: (approvalMode: ApprovalMode, clearConversation?: boolean) => void;
|
||||
onFeedback: (feedback: string) => void;
|
||||
onCancel: () => void;
|
||||
getPreferredEditor: () => EditorType | undefined;
|
||||
@@ -50,7 +50,9 @@ 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',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,11 +241,21 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
|
||||
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,
|
||||
@@ -251,10 +263,19 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
|
||||
]}
|
||||
onSubmit={(answers) => {
|
||||
const answer = answers['0'];
|
||||
if (answer === ApprovalOption.Auto) {
|
||||
onApprove(ApprovalMode.AUTO_EDIT);
|
||||
} else if (answer === ApprovalOption.Manual) {
|
||||
onApprove(ApprovalMode.DEFAULT);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -17,43 +17,19 @@ 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, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
● 2. Yes, automatically accept edits & clear conversation
|
||||
Approves plan, runs automatically, and clears prior conversation context
|
||||
3. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
▼
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: false > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
|
||||
"Overview
|
||||
|
||||
Add user authentication to the CLI application.
|
||||
|
||||
Implementation Steps
|
||||
|
||||
1. Create src/auth/AuthService.ts with login/logout methods
|
||||
2. Add session storage in src/storage/SessionStore.ts
|
||||
3. Update src/commands/index.ts to check auth status
|
||||
4. Add tests in src/auth/__tests__/
|
||||
|
||||
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, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
● 3. Type your feedback...
|
||||
|
||||
Enter to submit · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 1`] = `
|
||||
"Overview
|
||||
|
||||
@@ -71,43 +47,19 @@ 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, manually accept edits
|
||||
2. Yes, automatically accept edits & clear conversation
|
||||
Approves plan, runs automatically, and clears prior conversation context
|
||||
3. 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
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 2`] = `
|
||||
"Overview
|
||||
|
||||
Add user authentication to the CLI application.
|
||||
|
||||
Implementation Steps
|
||||
|
||||
1. Create src/auth/AuthService.ts with login/logout methods
|
||||
2. Add session storage in src/storage/SessionStore.ts
|
||||
3. Update src/commands/index.ts to check auth status
|
||||
4. Add tests in src/auth/__tests__/
|
||||
|
||||
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, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
● 3. Add tests
|
||||
|
||||
Enter to submit · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: false > displays error state when file read fails 1`] = `
|
||||
" Error reading plan: File not found
|
||||
"
|
||||
@@ -130,11 +82,14 @@ 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, manually accept edits
|
||||
2. Yes, automatically accept edits & clear conversation
|
||||
Approves plan, runs automatically, and clears prior conversation context
|
||||
3. 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
|
||||
"
|
||||
@@ -157,11 +112,14 @@ 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, manually accept edits
|
||||
2. Yes, automatically accept edits & clear conversation
|
||||
Approves plan, runs automatically, and clears prior conversation context
|
||||
3. 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
|
||||
"
|
||||
@@ -186,41 +144,19 @@ Files to Modify
|
||||
|
||||
1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
● 2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
● 2. Yes, automatically accept edits & clear conversation
|
||||
Approves plan, runs automatically, and clears prior conversation context
|
||||
3. 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...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: true > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
|
||||
"Overview
|
||||
|
||||
Add user authentication to the CLI application.
|
||||
|
||||
Implementation Steps
|
||||
|
||||
1. Create src/auth/AuthService.ts with login/logout methods
|
||||
2. Add session storage in src/storage/SessionStore.ts
|
||||
3. Update src/commands/index.ts to check auth status
|
||||
4. Add tests in src/auth/__tests__/
|
||||
|
||||
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, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
● 3. Type your feedback...
|
||||
|
||||
Enter to submit · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 1`] = `
|
||||
"Overview
|
||||
|
||||
@@ -240,41 +176,19 @@ Files to Modify
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
2. Yes, automatically accept edits & clear conversation
|
||||
Approves plan, runs automatically, and clears prior conversation context
|
||||
3. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
4. Yes, manually accept edits & clear conversation
|
||||
Approves plan, requires confirmation, and clears prior conversation
|
||||
context
|
||||
5. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 2`] = `
|
||||
"Overview
|
||||
|
||||
Add user authentication to the CLI application.
|
||||
|
||||
Implementation Steps
|
||||
|
||||
1. Create src/auth/AuthService.ts with login/logout methods
|
||||
2. Add session storage in src/storage/SessionStore.ts
|
||||
3. Update src/commands/index.ts to check auth status
|
||||
4. Add tests in src/auth/__tests__/
|
||||
|
||||
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, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
● 3. Add tests
|
||||
|
||||
Enter to submit · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: true > displays error state when file read fails 1`] = `
|
||||
" Error reading plan: File not found
|
||||
"
|
||||
@@ -320,9 +234,14 @@ Testing Strategy
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
2. Yes, automatically accept edits & clear conversation
|
||||
Approves plan, runs automatically, and clears prior conversation context
|
||||
3. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
4. Yes, manually accept edits & clear conversation
|
||||
Approves plan, requires confirmation, and clears prior conversation
|
||||
context
|
||||
5. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
@@ -347,9 +266,14 @@ Files to Modify
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
2. Yes, automatically accept edits & clear conversation
|
||||
Approves plan, runs automatically, and clears prior conversation context
|
||||
3. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
4. Yes, manually accept edits & clear conversation
|
||||
Approves plan, requires confirmation, and clears prior conversation
|
||||
context
|
||||
5. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import { useKeypress } from '../../hooks/useKeypress.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import { useSettings } from '../../contexts/SettingsContext.js';
|
||||
import { useUIActions } from '../../contexts/UIActionsContext.js';
|
||||
import { keyMatchers, Command } from '../../keyMatchers.js';
|
||||
import { formatCommand } from '../../utils/keybindingUtils.js';
|
||||
import {
|
||||
@@ -68,6 +69,7 @@ export const ToolConfirmationMessage: React.FC<
|
||||
terminalWidth,
|
||||
}) => {
|
||||
const { confirm, isDiffingEnabled } = useToolActions();
|
||||
const { handleClearPlanContext } = useUIActions();
|
||||
const [mcpDetailsExpansionState, setMcpDetailsExpansionState] = useState<{
|
||||
callId: string;
|
||||
expanded: boolean;
|
||||
@@ -428,10 +430,14 @@ export const ToolConfirmationMessage: React.FC<
|
||||
<ExitPlanModeDialog
|
||||
planPath={confirmationDetails.planPath}
|
||||
getPreferredEditor={getPreferredEditor}
|
||||
onApprove={(approvalMode) => {
|
||||
onApprove={(approvalMode, clearConversation) => {
|
||||
if (clearConversation) {
|
||||
handleClearPlanContext();
|
||||
}
|
||||
handleConfirm(ToolConfirmationOutcome.ProceedOnce, {
|
||||
approved: true,
|
||||
approvalMode,
|
||||
clearConversation,
|
||||
});
|
||||
}}
|
||||
onFeedback={(feedback) => {
|
||||
@@ -623,17 +629,18 @@ export const ToolConfirmationMessage: React.FC<
|
||||
|
||||
return { question, bodyContent, options, securityWarnings };
|
||||
}, [
|
||||
confirmationDetails,
|
||||
getOptions,
|
||||
availableBodyContentHeight,
|
||||
terminalWidth,
|
||||
handleConfirm,
|
||||
deceptiveUrlWarningText,
|
||||
isMcpToolDetailsExpanded,
|
||||
hasMcpToolDetails,
|
||||
mcpToolDetailsText,
|
||||
expandDetailsHintKey,
|
||||
confirmationDetails,
|
||||
terminalWidth,
|
||||
availableBodyContentHeight,
|
||||
handleConfirm,
|
||||
getPreferredEditor,
|
||||
handleClearPlanContext,
|
||||
hasMcpToolDetails,
|
||||
isMcpToolDetailsExpanded,
|
||||
expandDetailsHintKey,
|
||||
mcpToolDetailsText,
|
||||
]);
|
||||
|
||||
const bodyOverflowDirection: 'top' | 'bottom' =
|
||||
|
||||
@@ -57,6 +57,7 @@ export interface UIActions {
|
||||
setConstrainHeight: (value: boolean) => void;
|
||||
onEscapePromptChange: (show: boolean) => void;
|
||||
refreshStatic: () => void;
|
||||
handleClearPlanContext: () => void;
|
||||
handleFinalSubmit: (value: string) => Promise<void>;
|
||||
handleClearScreen: () => void;
|
||||
handleProQuotaChoice: (
|
||||
|
||||
@@ -1960,6 +1960,12 @@ export class Config implements McpContext {
|
||||
this.geminiMdFilePaths = paths;
|
||||
}
|
||||
|
||||
private planModeHistoryStartIndex = 0;
|
||||
|
||||
getPlanModeHistoryStartIndex(): number {
|
||||
return this.planModeHistoryStartIndex;
|
||||
}
|
||||
|
||||
getApprovalMode(): ApprovalMode {
|
||||
return this.policyEngine.getApprovalMode();
|
||||
}
|
||||
@@ -2017,6 +2023,14 @@ export class Config implements McpContext {
|
||||
|
||||
this.policyEngine.setApprovalMode(mode);
|
||||
|
||||
if (mode === ApprovalMode.PLAN && currentMode !== ApprovalMode.PLAN) {
|
||||
this.planModeHistoryStartIndex = this.geminiClient?.isInitialized()
|
||||
? this.geminiClient.getHistory().length
|
||||
: 0;
|
||||
}
|
||||
|
||||
coreEvents.emitApprovalModeChanged(mode);
|
||||
|
||||
const isPlanModeTransition =
|
||||
currentMode !== mode &&
|
||||
(currentMode === ApprovalMode.PLAN || mode === ApprovalMode.PLAN);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ExitPlanModeTool, ExitPlanModeInvocation } from './exit-plan-mode.js';
|
||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
||||
import path from 'node:path';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type { GeminiClient } from '../core/client.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { ToolConfirmationOutcome } from './tools.js';
|
||||
import { ApprovalMode } from '../policy/types.js';
|
||||
@@ -44,6 +45,8 @@ describe('ExitPlanModeTool', () => {
|
||||
getTargetDir: vi.fn().mockReturnValue(tempRootDir),
|
||||
setApprovalMode: vi.fn(),
|
||||
setApprovedPlanPath: vi.fn(),
|
||||
getGeminiClient: vi.fn(),
|
||||
getPlanModeHistoryStartIndex: vi.fn().mockReturnValue(0),
|
||||
storage: {
|
||||
getPlansDir: vi.fn().mockReturnValue(mockPlansDir),
|
||||
} as unknown as Config['storage'],
|
||||
@@ -240,6 +243,49 @@ Read and follow the plan strictly during implementation.`,
|
||||
expect(mockConfig.setApprovedPlanPath).toHaveBeenCalledWith(expectedPath);
|
||||
});
|
||||
|
||||
it('should truncate history surgically when clearConversation is true', async () => {
|
||||
const planRelativePath = createPlanFile('test.md', '# Content');
|
||||
const invocation = tool.build({ plan_path: planRelativePath });
|
||||
|
||||
const mockSetHistory = vi.fn();
|
||||
const mockHistory = [
|
||||
{ role: 'user', parts: [{ text: 'Pre-plan prompt' }] },
|
||||
{ role: 'model', parts: [{ text: 'Pre-plan response' }] },
|
||||
{ role: 'user', parts: [{ text: 'Enter plan mode' }] }, // Planning start turn (index 2)
|
||||
{ role: 'model', parts: [{ text: 'Draft 1' }] },
|
||||
{ role: 'user', parts: [{ text: 'No, change it' }] },
|
||||
{
|
||||
role: 'model',
|
||||
parts: [{ functionCall: { name: 'exit_plan_mode', args: {} } }],
|
||||
},
|
||||
];
|
||||
vi.mocked(mockConfig.getGeminiClient!).mockReturnValue({
|
||||
getHistory: vi.fn().mockReturnValue(mockHistory),
|
||||
setHistory: mockSetHistory,
|
||||
} as unknown as GeminiClient);
|
||||
vi.mocked(mockConfig.getPlanModeHistoryStartIndex!).mockReturnValue(2);
|
||||
|
||||
const confirmDetails = await invocation.shouldConfirmExecute(
|
||||
new AbortController().signal,
|
||||
);
|
||||
if (confirmDetails === false) return;
|
||||
|
||||
await confirmDetails.onConfirm(ToolConfirmationOutcome.ProceedOnce, {
|
||||
approved: true,
|
||||
approvalMode: ApprovalMode.AUTO_EDIT,
|
||||
clearConversation: true,
|
||||
});
|
||||
|
||||
await invocation.execute(new AbortController().signal);
|
||||
|
||||
expect(mockSetHistory).toHaveBeenCalledWith([
|
||||
mockHistory[0], // pre-plan user
|
||||
mockHistory[1], // pre-plan model
|
||||
mockHistory[2], // first planning user message
|
||||
mockHistory[5], // last model message (exit_plan_mode)
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return feedback message when plan is rejected with feedback', async () => {
|
||||
const planRelativePath = createPlanFile('test.md', '# Content');
|
||||
const invocation = tool.build({ plan_path: planRelativePath });
|
||||
|
||||
@@ -214,6 +214,34 @@ export class ExitPlanModeInvocation extends BaseToolInvocation<
|
||||
this.config.setApprovalMode(newMode);
|
||||
this.config.setApprovedPlanPath(resolvedPlanPath);
|
||||
|
||||
if (payload.clearConversation) {
|
||||
const geminiClient = this.config.getGeminiClient();
|
||||
if (geminiClient) {
|
||||
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.
|
||||
let planningUserIndex = -1;
|
||||
for (let i = startIndex; i < history.length - 1; i++) {
|
||||
if (history[i].role === 'user') {
|
||||
planningUserIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (planningUserIndex !== -1) {
|
||||
const lastModelMessage = history[history.length - 1];
|
||||
// Keep everything before the plan, plus the initial plan request turn.
|
||||
const newHistory = [
|
||||
...history.slice(0, planningUserIndex + 1),
|
||||
lastModelMessage,
|
||||
];
|
||||
geminiClient.setHistory(newHistory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logPlanExecution(this.config, new PlanExecutionEvent(newMode));
|
||||
|
||||
const exitMessage = getPlanModeExitMessage(newMode);
|
||||
|
||||
@@ -757,6 +757,8 @@ export interface ToolExitPlanModeConfirmationPayload {
|
||||
approvalMode?: ApprovalMode;
|
||||
/** If rejected, the user's feedback */
|
||||
feedback?: string;
|
||||
/** If the user wants to clear the conversation context upon approval */
|
||||
clearConversation?: boolean;
|
||||
}
|
||||
|
||||
export type ToolConfirmationPayload =
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { AgentDefinition } from '../agents/types.js';
|
||||
import type { McpClient } from '../tools/mcp-client.js';
|
||||
import type { ExtensionEvents } from './extensionLoader.js';
|
||||
import type { EditorType } from './editor.js';
|
||||
import type { ApprovalMode } from '../policy/types.js';
|
||||
import type {
|
||||
TokenStorageInitializationEvent,
|
||||
KeychainAvailabilityEvent,
|
||||
@@ -52,6 +53,16 @@ export interface ModelChangedPayload {
|
||||
model: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload for the 'approval-mode-changed' event.
|
||||
*/
|
||||
export interface ApprovalModeChangedPayload {
|
||||
/**
|
||||
* The new approval mode that was set.
|
||||
*/
|
||||
mode: ApprovalMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload for the 'console-log' event.
|
||||
*/
|
||||
@@ -167,6 +178,7 @@ export interface QuotaChangedPayload {
|
||||
export enum CoreEvent {
|
||||
UserFeedback = 'user-feedback',
|
||||
ModelChanged = 'model-changed',
|
||||
ApprovalModeChanged = 'approval-mode-changed',
|
||||
ConsoleLog = 'console-log',
|
||||
Output = 'output',
|
||||
MemoryChanged = 'memory-changed',
|
||||
@@ -200,6 +212,7 @@ export interface EditorSelectedPayload {
|
||||
export interface CoreEvents extends ExtensionEvents {
|
||||
[CoreEvent.UserFeedback]: [UserFeedbackPayload];
|
||||
[CoreEvent.ModelChanged]: [ModelChangedPayload];
|
||||
[CoreEvent.ApprovalModeChanged]: [ApprovalModeChangedPayload];
|
||||
[CoreEvent.ConsoleLog]: [ConsoleLogPayload];
|
||||
[CoreEvent.Output]: [OutputPayload];
|
||||
[CoreEvent.MemoryChanged]: [MemoryChangedPayload];
|
||||
@@ -301,6 +314,14 @@ export class CoreEventEmitter extends EventEmitter<CoreEvents> {
|
||||
this.emit(CoreEvent.ModelChanged, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers that the approval mode has changed.
|
||||
*/
|
||||
emitApprovalModeChanged(mode: ApprovalMode): void {
|
||||
const payload: ApprovalModeChangedPayload = { mode };
|
||||
this.emit(CoreEvent.ApprovalModeChanged, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers that settings have been modified.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user