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 the plugin's Auto Mode indicator incorrectly turning on when a permission prompt (e.g. for a Bash command) merely mentioned "auto mode" in its tip text or "switch to auto mode" menu option, instead of the mode actually changing
- Stopped showing Claude Code's "auto mode classifier billing" session notice when using an OpenAI-compatible or ChatGPT Subscription connection type — classifier requests are billed the same as before, just without the interruption, since this connection type was never able to hand those checks off to the server in the first place

# 1.3.8 (2026-07-22)
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.30-SNAPSHOT</version>
<version>1.3.31-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 @@ -12,13 +12,15 @@
* <p>Constants are ordered from <em>least</em> to <em>most</em> permissive, so
* ordinal comparisons ({@code >=}, {@code <=}) express permission thresholds naturally.
*
* <p>Screen-text mapping (used by {@code ScreenContentDetector.detectEditMode}):
* <p>Screen-text mapping (used by {@code ScreenContentDetector.detectEditMode}), checked
* against the single bottom-most non-blank screen line only:
* <ul>
* <li>{@link #PLAN} — bottom line contains {@code "plan mode"} or {@code "plan-mode"}</li>
* <li>{@link #DEFAULT} — bottom line starts with {@code " esc to interrupt"}
* (two leading spaces)</li>
* <li>{@link #DEFAULT} — bottom line contains {@code "manual mode on"}, or (fallback for
* Claude Code versions that do not emit that text) starts with
* {@code " esc to interrupt"} (two leading spaces)</li>
* <li>{@link #ACCEPT_EDITS} — bottom line contains {@code "accept edits"}</li>
* <li>{@link #AUTO} — bottom line contains {@code "auto mode"};
* <li>{@link #AUTO} — bottom line contains {@code "auto mode on"};
* available on Max/Team/Enterprise/API plans (Claude Code 2.1.83+)</li>
* <li>{@link #BYPASS_PERMISSIONS} — bottom line contains {@code "bypass permissions"};
* appears when Claude was launched with {@code --dangerously-skip-permissions}</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,10 @@ private static boolean isBoxDrawingLine(String trimmed) {
/**
* Detect the Claude CLI edit mode from the rendered screen footer.
*
* <p>Only the single bottom-most non-blank line is inspected — text further up the
* screen (e.g. a permission-prompt tip or menu option that merely mentions a mode by
* name) must never be mistaken for the actual mode-footer line.
*
* <p>Possible return values:
* <ul>
* <li>{@link EditMode#PLAN} — bottom line contains {@code "plan mode"} or
Expand All @@ -468,9 +472,12 @@ private static boolean isBoxDrawingLine(String trimmed) {
* <li>{@link EditMode#BYPASS_PERMISSIONS} — bottom line contains
* {@code "bypass permissions"}; present when Claude was launched with
* {@code --dangerously-skip-permissions}</li>
* <li>{@link EditMode#DEFAULT} — bottom line starts with
* <li>{@link EditMode#AUTO} — bottom line contains {@code "auto mode on"}</li>
* <li>{@link EditMode#DEFAULT} — bottom line contains {@code "manual mode on"}, or
* (fallback for Claude Code versions that do not emit that text) starts with
* {@code " esc to interrupt"} (two leading spaces)</li>
* <li>{@link Optional#empty()} — screen is blank or transitioning</li>
* <li>{@link Optional#empty()} — screen is blank, transitioning, or the bottom line is
* not a recognized mode indicator</li>
* </ul>
*
* @param lines rendered screen lines
Expand All @@ -480,22 +487,24 @@ public Optional<EditMode> detectEditMode(List<String> lines) {
if (lines == null || lines.isEmpty()) return Optional.empty();
List<String> bottom = bottomNonBlankLines(lines, 3);
if (bottom.isEmpty()) return Optional.empty();
for (String line : bottom) {
String lower = line.toLowerCase();
if (lower.contains("plan mode") || lower.contains("plan-mode")) {
return Optional.of(EditMode.PLAN);
}
if (lower.contains("accept edits")) {
return Optional.of(EditMode.ACCEPT_EDITS);
}
if (lower.contains("bypass permissions")) {
return Optional.of(EditMode.BYPASS_PERMISSIONS);
}
if (lower.contains("auto mode")) {
return Optional.of(EditMode.AUTO);
}
String lower = bottom.get(0).toLowerCase();
if (lower.contains("plan mode") || lower.contains("plan-mode")) {
return Optional.of(EditMode.PLAN);
}
if (lower.contains("accept edits")) {
return Optional.of(EditMode.ACCEPT_EDITS);
}
if (lower.contains("bypass permissions")) {
return Optional.of(EditMode.BYPASS_PERMISSIONS);
}
if (lower.contains("auto mode on")) {
return Optional.of(EditMode.AUTO);
}
if (lower.contains("manual mode on")) {
return Optional.of(EditMode.DEFAULT);
}
// " esc to interrupt" with two leading spaces is the reliable Ask/default-mode signal.
// " esc to interrupt" with two leading spaces is the reliable Ask/default-mode signal,
// kept as a fallback for older Claude Code versions that never emit "manual mode on".
// Without leading spaces the screen is in an unknown / transitioning state.
if (bottom.get(0).startsWith(" esc to interrupt")) {
return Optional.of(EditMode.DEFAULT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,51 @@ void detectEditModeEmptyWhenNullOrEmpty() {
assertTrue(detector.detectEditMode(List.of()).isEmpty());
}

@org.junit.jupiter.api.Test
void detectEditModeNotAutoWhenAutoModeOnlyMentionedInPermissionPromptTip() {
// Regression: a Bash-command permission prompt whose tip/menu text merely *mentions*
// "auto mode" must NOT be misdetected as EditMode.AUTO — only the actual bottom-most
// footer line indicates the current mode.
List<String> lines = new ArrayList<>(List.of(
" Bash command",
" Tip: auto mode handles these prompts for you — choose \"switch to auto mode\" below",
"",
" │ cd /home/oleg/my-projects/grandorgue/GrandOrgue && mkdir -p build/perf-baseline-debug",
" Configure a scratch Debug build with testing enabled",
"",
" Do you want to proceed?",
" ❯ 1. Yes",
" 2. Yes, and switch to auto mode · auto mode handles these prompts for you",
" 3. No",
"",
" Esc to cancel · Tab to amend"
));
java.util.Optional<EditMode> result = detector.detectEditMode(lines);
assertTrue(result.isEmpty(),
"Expected empty Optional — permission-prompt tip/menu text merely mentions "
+ "\"auto mode\", it is not the mode-footer line");
}

@org.junit.jupiter.api.Test
void detectEditModeManualMode() {
List<String> lines = new ArrayList<>(List.of(
"Some output",
"⏵⏵ manual mode on (shift+tab to cycle)"
));
for (int i = 0; i < 10; i++) lines.add("");
java.util.Optional<EditMode> result = detector.detectEditMode(lines);
assertTrue(result.isPresent());
assertEquals(EditMode.DEFAULT, result.get());
}

@org.junit.jupiter.api.Test
void detectEditModeManualModeCaseInsensitive() {
List<String> lines = List.of("Some output", "Manual Mode on | esc to interrupt");
java.util.Optional<EditMode> result = detector.detectEditMode(lines);
assertTrue(result.isPresent());
assertEquals(EditMode.DEFAULT, result.get());
}

// -------------------------------------------------------------------------
// detectPlanName
// -------------------------------------------------------------------------
Expand Down
Loading