From df114ae0421b1e9ae13ddc546e657b0d4ee64d39 Mon Sep 17 00:00:00 2001 From: ephraimduncan Date: Fri, 5 Jun 2026 10:59:21 +0000 Subject: [PATCH] fix(coding-agent): gate plan.defaultOnStartup on empty session history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The startup plan-mode auto-entry gated on the CLI-derived `resuming` flag (continue || resume || fork). But `omp --continue` with no recent session calls SessionManager.continueRecent(), which creates a fresh, empty session while `resuming` stays true — so the new setting was skipped on what is really a brand-new session. Gate on the actual signal instead: this.sessionManager.getEntries().length === 0, the same "has prior history" check the auto-resume path already uses. A session that opens empty (new, or a continue/auto-resume that found nothing) honors the default; one with restored history keeps its reconciled mode. Removed the now-dead `resuming` InteractiveModeInitOptions field and its init() argument (the runtime flag is still used for setup-scene selection). Updated the regression test to seed real history and corrected the CHANGELOG wording. --- packages/coding-agent/CHANGELOG.md | 2 +- packages/coding-agent/src/main.ts | 2 +- .../src/modes/interactive-mode.ts | 20 ++++++++++--------- packages/coding-agent/src/modes/types.ts | 4 ---- ...interactive-mode-default-plan-mode.test.ts | 9 +++++++-- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index bcb99567f..a23c98a29 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- Added a `plan.defaultOnStartup` setting (Tasks tab, default off) that automatically enters plan mode at the start of every new interactive session. It applies only to fresh sessions — resumed, continued, and forked sessions keep the mode restored from their history — and is a silent no-op when plan mode is globally disabled (`plan.enabled`). The setting is hidden from the settings UI unless plan mode is enabled. +- Added a `plan.defaultOnStartup` setting (Tasks tab, default off) that automatically enters plan mode at the start of a new interactive session. It applies to any session that opens with no prior history — including a `--continue`/auto-resume that finds no recent session and starts fresh — while sessions with restored history keep the mode reconciled from that history. It is a silent no-op when plan mode is globally disabled (`plan.enabled`), and the setting is hidden from the settings UI unless plan mode is enabled. ## [15.9.1] - 2026-06-04 diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 92fc9a9d2..8f730793b 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -268,7 +268,7 @@ async function runInteractiveMode( force: forceSetupWizard, }); - await mode.init({ suppressWelcomeIntro: setupScenes.length > 0, resuming }); + await mode.init({ suppressWelcomeIntro: setupScenes.length > 0 }); if (setupScenes.length > 0) { await runSetupWizard(mode, setupScenes); diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index b175e2a04..4a4e8f387 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -610,16 +610,18 @@ export class InteractiveMode implements InteractiveModeContext { this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession()); await this.#reconcileModeFromSession(); - // Brand-new (non-resumed) sessions optionally start in plan mode when the - // user has made it the startup default. Resumed/continued/forked sessions - // keep whatever mode #reconcileModeFromSession just restored. Scoped to - // launch (not the switch reconciler above) so /new and the plan-approval - // → execution handoff clear never get dragged back into plan mode. - // #enterPlanMode is idempotent and self-guards against an already-active - // plan/goal mode, so the only conditions worth checking here are the two - // settings (it does not check plan.enabled itself). + // Brand-new sessions optionally start in plan mode when the user has made + // it the startup default. "Brand-new" is keyed off the session having no + // prior entries — not the CLI resume flags — so `omp --continue` (or + // auto-resume) that finds no recent session and creates a fresh one still + // honors the default, while a session with restored history keeps whatever + // mode #reconcileModeFromSession just reconciled. Scoped to launch (not the + // switch reconciler above) so /new and the plan-approval → execution handoff + // clear never get dragged back into plan mode. #enterPlanMode is idempotent + // and self-guards against an already-active plan/goal mode; it does not check + // plan.enabled itself. if ( - !options.resuming && + this.sessionManager.getEntries().length === 0 && this.session.settings.get("plan.defaultOnStartup") && this.session.settings.get("plan.enabled") ) { diff --git a/packages/coding-agent/src/modes/types.ts b/packages/coding-agent/src/modes/types.ts index 9987edc97..4f734d53c 100644 --- a/packages/coding-agent/src/modes/types.ts +++ b/packages/coding-agent/src/modes/types.ts @@ -62,10 +62,6 @@ export type TodoPhase = { export interface InteractiveModeInitOptions { suppressWelcomeIntro?: boolean; - /** True when the session was continued/resumed/forked. Suppresses the - * `plan.defaultOnStartup` auto-entry so resumed sessions keep their own - * reconciled mode. */ - resuming?: boolean; } export type InteractiveSelectorDialogOptions = ExtensionUIDialogOptions & Pick; diff --git a/packages/coding-agent/test/interactive-mode-default-plan-mode.test.ts b/packages/coding-agent/test/interactive-mode-default-plan-mode.test.ts index f370712bd..e1dbacb7c 100644 --- a/packages/coding-agent/test/interactive-mode-default-plan-mode.test.ts +++ b/packages/coding-agent/test/interactive-mode-default-plan-mode.test.ts @@ -116,10 +116,15 @@ describe("InteractiveMode plan.defaultOnStartup", () => { expect(session?.getPlanModeState()).toBeUndefined(); }); - it("does not enter plan mode when the session is resumed", async () => { + it("does not enter plan mode when the session has restored history", async () => { + // A genuinely resumed session has prior entries; gating on getEntries() + // (not the CLI resume flag) means a `--continue` that created a *fresh*, + // empty session still gets the startup default (the "enters" case above), + // while one with restored history is left in its reconciled mode. const created = createHarness(Settings.isolated({ "plan.defaultOnStartup": true, "compaction.enabled": false })); + created.sessionManager.appendMessage({ role: "user", content: "prior turn", timestamp: Date.now() }); - await created.init({ suppressWelcomeIntro: true, resuming: true }); + await created.init({ suppressWelcomeIntro: true }); expect(created.planModeEnabled).toBe(false); expect(session?.getPlanModeState()).toBeUndefined();