diff --git a/CHANGELOG.md b/CHANGELOG.md
index 114b2b41..0855beea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+- Fixed edit-mode detection and switching while background agents or shells are listed under the status line
# 1.3.42 (2026-10-22)
- Fixed OpenAI-compatible models with strict chat templates (e.g. vLLM behind LiteLLM) failing every request with "System message must be at the beginning"
# 1.3.39 (2026-10-18)
diff --git a/pom.xml b/pom.xml
index 53b79107..9683e912 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.github.nbplugins
netbeans-plugin-claude-code-gui
- 1.3.67-SNAPSHOT
+ 1.3.69-SNAPSHOT
nbm
Netbeans Plugin Claude Code GUI
NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.
diff --git a/src/main/java/io/github/nbplugins/claudecodegui/process/ScreenContentDetector.java b/src/main/java/io/github/nbplugins/claudecodegui/process/ScreenContentDetector.java
index 152e5c20..3360a41a 100644
--- a/src/main/java/io/github/nbplugins/claudecodegui/process/ScreenContentDetector.java
+++ b/src/main/java/io/github/nbplugins/claudecodegui/process/ScreenContentDetector.java
@@ -530,6 +530,7 @@ private static boolean isBoxDrawingLine(String trimmed) {
*/
public Optional detectEditMode(List lines) {
if (lines == null || lines.isEmpty()) return Optional.empty();
+ lines = stripAgentPanel(lines);
List bottom = bottomNonBlankLines(lines, 3);
if (bottom.isEmpty()) return Optional.empty();
Optional mode = editModeFromFooterLine(bottom.get(0));
@@ -543,6 +544,28 @@ public Optional detectEditMode(List lines) {
return Optional.empty();
}
+ /**
+ * Cuts off the background-agent panel that newer Claude Code versions render below the
+ * mode footer ({@code ● main} followed by one row per agent), so that the footer is again
+ * among the bottom lines regardless of how many agents are listed. Only rows below the
+ * input prompt are considered, so a {@code ● main} line in the response is never cut.
+ */
+ static List stripAgentPanel(List lines) {
+ int promptRow = -1;
+ for (int i = lines.size() - 1; i >= 0; i--) {
+ if (INPUT_PROMPT.matcher(lines.get(i).trim()).find()) {
+ promptRow = i;
+ break;
+ }
+ }
+ for (int i = lines.size() - 1; i > promptRow; i--) {
+ if (lines.get(i).strip().startsWith("\u25CF main")) {
+ return lines.subList(0, i);
+ }
+ }
+ return lines;
+ }
+
/** Maps a single mode-footer line to its {@link EditMode}; see {@link #detectEditMode}. */
private static Optional editModeFromFooterLine(String line) {
String lower = line.toLowerCase();
@@ -618,6 +641,7 @@ public SessionState detectSessionStateLegacy(List lines) {
*/
public DetectedSessionState detectSessionState(List lines) {
if (lines == null || lines.isEmpty()) return DetectedSessionState.UNKNOWN;
+ lines = stripAgentPanel(lines);
// Search all lines (bottom-up) for an input prompt adjacent to a separator line.
// Claude Code uses the alternate screen buffer and draws content from the top,
diff --git a/src/test/java/io/github/nbplugins/claudecodegui/process/ScreenContentDetectorTest.java b/src/test/java/io/github/nbplugins/claudecodegui/process/ScreenContentDetectorTest.java
index c5a9cd72..320fab6d 100644
--- a/src/test/java/io/github/nbplugins/claudecodegui/process/ScreenContentDetectorTest.java
+++ b/src/test/java/io/github/nbplugins/claudecodegui/process/ScreenContentDetectorTest.java
@@ -317,6 +317,53 @@ void detectEditModeWhenEffortLineRenderedBelowModeLine(String modeLine, EditMode
assertEquals(java.util.Optional.of(expected), detector.detectEditMode(lines));
}
+ @org.junit.jupiter.params.ParameterizedTest
+ @org.junit.jupiter.params.provider.CsvSource(delimiter = '|', value = {
+ " ⏵⏵ auto mode on · 3 shells · esc to interrupt · ← 1 agent · ↓ to manage | AUTO",
+ " ⏵⏵ accept edits on · 2 shells · ← 1 agent | ACCEPT_EDITS",
+ " ⏸ plan mode on (shift+tab to cycle) · ← 1 agent | PLAN",
+ " ⏵⏵ bypass permissions on · ← 2 agents | BYPASS_PERMISSIONS"
+ })
+ void detectEditModeWhenAgentListRenderedBelowModeLine(String modeLine, EditMode expected) {
+ List lines = new ArrayList<>(List.of(
+ "❯ ",
+ "────────────────────────────────────────────────────────────",
+ modeLine,
+ "",
+ " ● main",
+ " ◯ Plan Parsing superclasses 2h 18m 31s · ↓ 145.7k tokens",
+ " ◯ Explore Reading mutabilityUtils.kt 23m 6s · ↓ 90.5k tokens"
+ ));
+ for (int i = 0; i < 3; i++) lines.add("");
+ assertEquals(java.util.Optional.of(expected), detector.detectEditMode(lines));
+ }
+
+ @org.junit.jupiter.params.ParameterizedTest
+ @org.junit.jupiter.params.provider.ValueSource(ints = {1, 5, 10})
+ void detectEditModeWithCompactHintAndManyAgentRows(int agents) {
+ List lines = new ArrayList<>(List.of(
+ "❯ ",
+ "────────────────────────────────────────────────────────────",
+ " ⏸ plan mode on · 3 shells · esc to interrupt · ← 1 agent ✘ Auto-update failed",
+ " 0% until auto-compact",
+ "",
+ " ● main"
+ ));
+ for (int i = 0; i < agents; i++) lines.add(" ◯ Agent" + i + " Working on something 1m · ↓ 1k tokens");
+ assertEquals(java.util.Optional.of(EditMode.PLAN), detector.detectEditMode(lines));
+ }
+
+ @org.junit.jupiter.api.Test
+ void detectEditModeMainBulletAbovePromptIsNotCut() {
+ List lines = new ArrayList<>(List.of(
+ " ● main branch looks fine",
+ "❯ ",
+ "────────────────────────────────────────────────────────────",
+ " ⏵⏵ auto mode on (shift+tab to cycle)"
+ ));
+ assertEquals(java.util.Optional.of(EditMode.AUTO), detector.detectEditMode(lines));
+ }
+
@org.junit.jupiter.api.Test
void detectEditModeNotAcceptEditsWhenOnlyMentionedInMenuOptionAboveBottom() {
// A line above the bottom one is only trusted when it starts with the mode glyph;
diff --git a/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-auto-mode-agent-list-below-footer.expectedState.txt b/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-auto-mode-agent-list-below-footer.expectedState.txt
new file mode 100644
index 00000000..e0ac30e1
--- /dev/null
+++ b/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-auto-mode-agent-list-below-footer.expectedState.txt
@@ -0,0 +1 @@
+WORKING
\ No newline at end of file
diff --git a/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-auto-mode-agent-list-below-footer.src.txt b/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-auto-mode-agent-list-below-footer.src.txt
new file mode 100644
index 00000000..2825cac9
--- /dev/null
+++ b/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-auto-mode-agent-list-below-footer.src.txt
@@ -0,0 +1,11 @@
+* Simmering… (32m 29s · ↓ 7.5k tokens)
+ ⎿ Tip: Use /clear to start fresh when switching topics and free up context
+
+───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── j2k-conversion-plugin-stage ─
+❯
+───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+ ⏵⏵ auto mode on · 3 shells · esc to interrupt · ← 1 agent · ↓ to manage ✘ Auto-update failed: no write permission to npm prefix · Run claude doctor
+
+ ● main
+ ◯ Plan Parsing superclasses in java-analysis-impl classes 2h 18m 31s · ↓ 145.7k tokens
+ ◯ Explore Reading mutabilityUtils.kt getImmutableLocalVariablesInBlock 23m 6s · ↓ 90.5k tokens
diff --git a/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-plan-mode-compact-hint-agent-list.expectedState.txt b/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-plan-mode-compact-hint-agent-list.expectedState.txt
new file mode 100644
index 00000000..e0ac30e1
--- /dev/null
+++ b/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-plan-mode-compact-hint-agent-list.expectedState.txt
@@ -0,0 +1 @@
+WORKING
\ No newline at end of file
diff --git a/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-plan-mode-compact-hint-agent-list.src.txt b/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-plan-mode-compact-hint-agent-list.src.txt
new file mode 100644
index 00000000..72308e99
--- /dev/null
+++ b/src/test/resources/io/github/nbplugins/claudecodegui/process/screen-content-detector/detect-session-state/working-plan-mode-compact-hint-agent-list.src.txt
@@ -0,0 +1,10 @@
+* Simmering… (32m 29s · ↓ 7.5k tokens)
+
+───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── j2k-conversion-plugin-stage ─
+❯
+───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+ ⏸ plan mode on · 3 shells · esc to interrupt · ← 1 agent · ↓ to manage ✘ Auto-update failed: no write permission to npm prefix · Run claude doctor
+ 0% until auto-compact
+
+ ● main
+ ◯ Plan Checking KotlinPluginModeProvider for duplicate definitions 2h 40m 42s · ↓ 70.2k tokens