feat: Implement Plan Mode for Safe Code Planning (#658)

This commit is contained in:
tanzhenxin
2025-09-24 14:26:17 +08:00
committed by GitHub
parent 8379bc4d81
commit 4e7a7e2656
43 changed files with 2895 additions and 281 deletions

View File

@@ -710,6 +710,18 @@ describe('setApprovalMode with folder trust', () => {
expect(() => config.setApprovalMode(ApprovalMode.DEFAULT)).not.toThrow();
});
it('should NOT throw an error when setting PLAN mode in an untrusted folder', () => {
const config = new Config({
sessionId: 'test',
targetDir: '.',
debugMode: false,
model: 'test-model',
cwd: '.',
trustedFolder: false, // Untrusted
});
expect(() => config.setApprovalMode(ApprovalMode.PLAN)).not.toThrow();
});
it('should NOT throw an error when setting any mode in a trusted folder', () => {
const config = new Config({
sessionId: 'test',
@@ -722,6 +734,7 @@ describe('setApprovalMode with folder trust', () => {
expect(() => config.setApprovalMode(ApprovalMode.YOLO)).not.toThrow();
expect(() => config.setApprovalMode(ApprovalMode.AUTO_EDIT)).not.toThrow();
expect(() => config.setApprovalMode(ApprovalMode.DEFAULT)).not.toThrow();
expect(() => config.setApprovalMode(ApprovalMode.PLAN)).not.toThrow();
});
it('should NOT throw an error when setting any mode if trustedFolder is undefined', () => {
@@ -736,6 +749,7 @@ describe('setApprovalMode with folder trust', () => {
expect(() => config.setApprovalMode(ApprovalMode.YOLO)).not.toThrow();
expect(() => config.setApprovalMode(ApprovalMode.AUTO_EDIT)).not.toThrow();
expect(() => config.setApprovalMode(ApprovalMode.DEFAULT)).not.toThrow();
expect(() => config.setApprovalMode(ApprovalMode.PLAN)).not.toThrow();
});
describe('Model Switch Logging', () => {

View File

@@ -33,6 +33,7 @@ import {
import { logCliConfiguration, logIdeConnection } from '../telemetry/loggers.js';
import { IdeConnectionEvent, IdeConnectionType } from '../telemetry/types.js';
import { EditTool } from '../tools/edit.js';
import { ExitPlanModeTool } from '../tools/exitPlanMode.js';
import { GlobTool } from '../tools/glob.js';
import { GrepTool } from '../tools/grep.js';
import { LSTool } from '../tools/ls.js';
@@ -62,11 +63,14 @@ import { Logger, type ModelSwitchEvent } from '../core/logger.js';
export type { AnyToolInvocation, MCPOAuthConfig };
export enum ApprovalMode {
PLAN = 'plan',
DEFAULT = 'default',
AUTO_EDIT = 'autoEdit',
AUTO_EDIT = 'auto-edit',
YOLO = 'yolo',
}
export const APPROVAL_MODES = Object.values(ApprovalMode);
export interface AccessibilitySettings {
disableLoadingPhrases?: boolean;
screenReader?: boolean;
@@ -700,7 +704,11 @@ export class Config {
}
setApprovalMode(mode: ApprovalMode): void {
if (this.isTrustedFolder() === false && mode !== ApprovalMode.DEFAULT) {
if (
this.isTrustedFolder() === false &&
mode !== ApprovalMode.DEFAULT &&
mode !== ApprovalMode.PLAN
) {
throw new Error(
'Cannot enable privileged approval modes in an untrusted folder.',
);
@@ -1043,11 +1051,12 @@ export class Config {
registerCoreTool(GlobTool, this);
registerCoreTool(EditTool, this);
registerCoreTool(WriteFileTool, this);
registerCoreTool(WebFetchTool, this);
registerCoreTool(ReadManyFilesTool, this);
registerCoreTool(ShellTool, this);
registerCoreTool(MemoryTool);
registerCoreTool(TodoWriteTool, this);
registerCoreTool(ExitPlanModeTool, this);
registerCoreTool(WebFetchTool, this);
// Conditionally register web search tool only if Tavily API key is set
if (this.getTavilyApiKey()) {
registerCoreTool(WebSearchTool, this);