From 0fbe4e15355cbc0def70276908899d8b2000f135 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 14 Jun 2026 07:19:37 +0000 Subject: [PATCH] fix(coding-agent): preserved prompted paused plan commands Kept /plan from paused plan mode on the prompted entry path while retaining the no-arg third-toggle exit. Added regression coverage for paused plan mode resuming and submitting the prompt. Fixes #2510 --- packages/coding-agent/CHANGELOG.md | 2 +- .../src/modes/interactive-mode.ts | 10 +++++----- .../test/interactive-mode-resume-mode.test.ts | 20 +++++++++++++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a648f3f6d..d67164b4d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed `/plan` cycling between `plan` and `plan_paused` with no path back to mode `none`. `handlePlanModeCommand` had branches for entering and pausing but fell through to `#enterPlanMode()` when invoked from the paused state, so once a session entered plan mode the only operator-visible toggle re-entered it. The handler now matches `planModePaused` and fully exits — clearing `planModeHasEntered` and appending a `mode_change` to `"none"` — so `/goal` (and any other mode gated on `planModeEnabled || planModePaused`) can run again after a third `/plan` ([#2510](https://github.com/can1357/oh-my-pi/issues/2510)). +- Fixed `/plan` cycling between `plan` and `plan_paused` with no path back to mode `none`, while preserving prompted paused-mode requests. The no-arg third toggle now fully exits — clearing `planModeHasEntered` and appending a `mode_change` to `"none"` — and `/plan ` from `plan_paused` re-enters plan mode and submits the prompt as the first turn ([#2510](https://github.com/can1357/oh-my-pi/issues/2510)). ## [15.12.5] - 2026-06-13 ### Changed diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index a4dd198e4..959043f68 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -2237,12 +2237,12 @@ export class InteractiveMode implements InteractiveModeContext { await this.#exitPlanMode({ paused: true }); return; } - if (this.planModePaused) { - // Third toggle: paused → off. Tools, model, and plan state were already - // restored by the prior #exitPlanMode({ paused: true }); only the + if (this.planModePaused && !initialPrompt) { + // No-arg third toggle: paused → off. Tools, model, and plan state were + // already restored by the prior #exitPlanMode({ paused: true }); only the // paused flag, the reentry marker, and the session mode entry remain. - // Without this branch the handler fell through to #enterPlanMode and - // the session was stuck cycling plan ↔ plan_paused (issue #2510). + // Prompted /plan invocations fall through to #enterPlanMode below so the + // supplied prompt is still submitted as the first plan-mode turn. this.planModePaused = false; this.#planModeHasEntered = false; this.#updatePlanModeStatus(); diff --git a/packages/coding-agent/test/interactive-mode-resume-mode.test.ts b/packages/coding-agent/test/interactive-mode-resume-mode.test.ts index b821bab3c..7c761dd3a 100644 --- a/packages/coding-agent/test/interactive-mode-resume-mode.test.ts +++ b/packages/coding-agent/test/interactive-mode-resume-mode.test.ts @@ -212,6 +212,26 @@ describe("InteractiveMode resume mode restoration", () => { expect(created.session.getActiveToolNames()).toContain("resolve"); }); + it("submits prompted plan commands when plan mode is paused", async () => { + const created = await createHarness(); + await created.mode.handlePlanModeCommand(); + await created.mode.handlePlanModeCommand(); + expect(created.mode.planModeEnabled).toBe(false); + expect(created.mode.planModePaused).toBe(true); + + let submittedText: string | undefined; + created.mode.onInputCallback = input => { + submittedText = input.text; + }; + + await created.mode.handlePlanModeCommand("write the plan"); + + expect(created.mode.planModeEnabled).toBe(true); + expect(created.mode.planModePaused).toBe(false); + expect(created.session.getPlanModeState()).toMatchObject({ enabled: true }); + expect(submittedText).toBe("write the plan"); + }); + it("clears stale plan mode state when switching to a non-plan session", async () => { const registry = modelRegistry(); const defaultModel = modelOrThrow(registry, "claude-sonnet-4-5");