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