From f5c143ec747a5314ebd08a438be7b88ed3a7b33d Mon Sep 17 00:00:00 2001 From: paulbettner Date: Sun, 8 Feb 2026 21:10:21 -0600 Subject: [PATCH] feat: restore previous permission mode when exiting plan mode (#866) --- src/cli/App.tsx | 8 ++++--- src/permissions/mode.ts | 36 ++++++++++++++++++++++++++++++ src/tests/permissions-mode.test.ts | 17 ++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 59f7f02..9a4ec13 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -9559,9 +9559,11 @@ ${SYSTEM_REMINDER_CLOSE} lastPlanFilePathRef.current = planFilePath; // Exit plan mode - const newMode = acceptEdits ? "acceptEdits" : "default"; - permissionMode.setMode(newMode); - setUiPermissionMode(newMode); + const restoreMode = acceptEdits + ? "acceptEdits" + : (permissionMode.getModeBeforePlan() ?? "default"); + permissionMode.setMode(restoreMode); + setUiPermissionMode(restoreMode); try { // Execute ExitPlanMode tool to get the result diff --git a/src/permissions/mode.ts b/src/permissions/mode.ts index 69ba25a..182ad74 100644 --- a/src/permissions/mode.ts +++ b/src/permissions/mode.ts @@ -16,10 +16,12 @@ export type PermissionMode = // This prevents Bun's bundler from creating duplicate instances of the mode manager const MODE_KEY = Symbol.for("@letta/permissionMode"); const PLAN_FILE_KEY = Symbol.for("@letta/planFilePath"); +const MODE_BEFORE_PLAN_KEY = Symbol.for("@letta/permissionModeBeforePlan"); type GlobalWithMode = typeof globalThis & { [MODE_KEY]: PermissionMode; [PLAN_FILE_KEY]: string | null; + [MODE_BEFORE_PLAN_KEY]?: PermissionMode | null; }; function getGlobalMode(): PermissionMode { @@ -45,6 +47,16 @@ function setGlobalPlanFilePath(value: string | null): void { global[PLAN_FILE_KEY] = value; } +function getGlobalModeBeforePlan(): PermissionMode | null { + const global = globalThis as GlobalWithMode; + return global[MODE_BEFORE_PLAN_KEY] ?? null; +} + +function setGlobalModeBeforePlan(value: PermissionMode | null): void { + const global = globalThis as GlobalWithMode; + global[MODE_BEFORE_PLAN_KEY] = value; +} + /** * Permission mode state for the current session. * Set via CLI --permission-mode flag or settings.json defaultMode. @@ -62,11 +74,33 @@ class PermissionModeManager { * Set the permission mode for this session */ setMode(mode: PermissionMode): void { + const prevMode = this.currentMode; + + // If we are entering plan mode, remember what mode we were previously in so + // ExitPlanMode can restore it (e.g. YOLO). + if (mode === "plan" && prevMode !== "plan") { + setGlobalModeBeforePlan(prevMode); + } + this.currentMode = mode; + // Clear plan file path when exiting plan mode if (mode !== "plan") { setGlobalPlanFilePath(null); } + + // Once we leave plan mode, the remembered mode has been consumed. + if (prevMode === "plan" && mode !== "plan") { + setGlobalModeBeforePlan(null); + } + } + + /** + * Get the permission mode that was active before entering plan mode. + * Used to restore the user's previous setting (e.g., bypassPermissions). + */ + getModeBeforePlan(): PermissionMode | null { + return getGlobalModeBeforePlan(); } /** @@ -261,6 +295,8 @@ class PermissionModeManager { */ reset(): void { this.currentMode = "default"; + setGlobalPlanFilePath(null); + setGlobalModeBeforePlan(null); } } diff --git a/src/tests/permissions-mode.test.ts b/src/tests/permissions-mode.test.ts index 5ed5bb7..d0e838b 100644 --- a/src/tests/permissions-mode.test.ts +++ b/src/tests/permissions-mode.test.ts @@ -452,3 +452,20 @@ test("Permission mode takes precedence over CLI allowedTools", () => { // Clean up cliPermissions.clear(); }); + +test("plan mode - remembers and restores previous mode", () => { + permissionMode.setMode("bypassPermissions"); + expect(permissionMode.getMode()).toBe("bypassPermissions"); + + // Enter plan mode - should remember prior mode. + permissionMode.setMode("plan"); + expect(permissionMode.getMode()).toBe("plan"); + expect(permissionMode.getModeBeforePlan()).toBe("bypassPermissions"); + + // Exit plan mode by restoring previous mode. + permissionMode.setMode(permissionMode.getModeBeforePlan() ?? "default"); + expect(permissionMode.getMode()).toBe("bypassPermissions"); + + // Once we leave plan mode, the remembered mode is consumed. + expect(permissionMode.getModeBeforePlan()).toBe(null); +});