Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbplugins</groupId>
<artifactId>netbeans-plugin-claude-code-gui</artifactId>
<version>1.3.67-SNAPSHOT</version>
<version>1.3.69-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Plugin Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ private static boolean isBoxDrawingLine(String trimmed) {
*/
public Optional<EditMode> detectEditMode(List<String> lines) {
if (lines == null || lines.isEmpty()) return Optional.empty();
lines = stripAgentPanel(lines);
List<String> bottom = bottomNonBlankLines(lines, 3);
if (bottom.isEmpty()) return Optional.empty();
Optional<EditMode> mode = editModeFromFooterLine(bottom.get(0));
Expand All @@ -543,6 +544,28 @@ public Optional<EditMode> detectEditMode(List<String> 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<String> stripAgentPanel(List<String> 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<EditMode> editModeFromFooterLine(String line) {
String lower = line.toLowerCase();
Expand Down Expand Up @@ -618,6 +641,7 @@ public SessionState detectSessionStateLegacy(List<String> lines) {
*/
public DetectedSessionState detectSessionState(List<String> 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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<String> 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<String> 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WORKING
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WORKING
Original file line number Diff line number Diff line change
@@ -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
Loading