Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
cab2ccd
Fixed session name not saved to the stopped session in the Save & Swi…
Nov 3, 2026
06a850c
Fixed renaming the wrong conversation after /resume inside a running …
Nov 3, 2026
d8416cf
Fixed sibling tabs claiming each other's session file
Nov 3, 2026
e30f26d
Reduced idle session-identity scans on the EDT
Nov 3, 2026
fdd31b8
Fixed stale session claims after in-process switches and terminal exits
Nov 3, 2026
86d40bd
Updated the session-identity Javadoc to describe the claim registry
Nov 3, 2026
f9f8cfc
Fixed unresolved NEW sessions resuming a saved conversation and exit …
Nov 3, 2026
d0a6e50
Fixed identity scans outliving a process restart and moved them off t…
Nov 3, 2026
421bffc
Fixed out-of-order identity scans and store-scoped session claims
Nov 3, 2026
d3a443f
Preserved the selected session across session-list refreshes and move…
Nov 3, 2026
78ea35d
Discarded stale session-list loads and kept session claims atomic wit…
Nov 3, 2026
20ee8ef
Reset identity inputs atomically with the stop and tolerated coarse s…
Nov 3, 2026
a41019f
Kept sessions shared between live tabs eligible for identity scans
Nov 3, 2026
8bf9256
Followed in-process resumes into another tab's session and applied th…
Nov 3, 2026
58c3394
Allowed an unresolved NEW tab to follow an in-process resume into a s…
Nov 3, 2026
67ec84d
Required evidence before following an in-process resume and skipped i…
Nov 3, 2026
7e28a19
Preferred prompt evidence over mtime guesses and prepared the launch …
Nov 3, 2026
1aa434f
Hardened session tracking against terminal input and stale tab state
Nov 3, 2026
27c6931
Kept typed prompts during startup and made session getters null-safe
Nov 3, 2026
bcf7512
Cancelled pending launches when the chat layout is torn down
Nov 3, 2026
d5b73ab
Made prompt evidence prefer the tracked session and survive long turns
Nov 3, 2026
7abb649
Fixed restarts after a pending launch and terminal escape parsing
Nov 3, 2026
66eea74
Fixed multiline pastes and closing during a pending launch
Nov 3, 2026
b356872
Fixed cursor edits in terminal prompts and saving during a pending la…
Nov 3, 2026
101458a
Fixed identity evidence for identical prompts and terminal drafts
Nov 3, 2026
9ea37e9
Fixed session identity swapping between tabs and late submission time…
Nov 4, 2026
d5d2b03
Fixed Unicode prompt edits and duplicate tabs during a pending launch
Nov 4, 2026
46a3f03
Fixed provisional session claims, boundary cursor keys and pending pr…
Nov 4, 2026
4a5c9e3
Fixed displaced provisional claims and history-recalled prompt submis…
Nov 4, 2026
32c6c4a
Fixed standalone Esc, pending config overrides and post-exit identity…
Nov 4, 2026
6e63df3
Fixed menu navigation recall and quadratic record reassembly
Nov 4, 2026
9844be2
Fixed a lost title on stop, pasted tabs and edited history recalls
Nov 4, 2026
865c323
Fixed renaming the previous conversation when switching in the TUI at…
Nov 4, 2026
719d5d2
Fixed explicit resume ownership and stale history-recall state across…
Nov 4, 2026
d3f3d57
Fixed adopting a sibling turn after a history recall and picker text …
Nov 4, 2026
1c6179d
Fixed prompt evidence, Tab completion and Default profile config reso…
Nov 4, 2026
5293c53
Fixed menu Tab being taken as completion and quadratic paste insertion
Nov 4, 2026
148f79c
Fixed shared-message evidence order and Esc sequence-prefix misparsing
Nov 4, 2026
41ea702
Fixed own-session evidence for unknown prompts and rounded cutoffs
Nov 4, 2026
0888b7b
Fixed shared resume identity and the dialog store after a profile move
oleg68 Nov 6, 2026
4ff56ac
Fixed unknown-prompt identity for launch-known tabs and shared candid…
oleg68 Nov 6, 2026
1943b5d
Recovered the launch conversation after a shared guess and batched pa…
oleg68 Nov 6, 2026
51c00ec
Restricted the launch-known preference to the original launch session
oleg68 Nov 6, 2026
be4432b
Kept prompt candidates newest-first and tied launch snapshots to the …
oleg68 Nov 6, 2026
58b0855
Ordered borrowed prompt candidates and kept sibling-owned files out o…
oleg68 Nov 6, 2026
fd7a3a0
Kept launch conversations eligible instead of conclusive in identity …
oleg68 Nov 6, 2026
5688bbc
Preserved held conversations through the final stop lookup
oleg68 Nov 6, 2026
6195673
Preserved only own conversations for the final stop lookup
oleg68 Nov 6, 2026
d3db1ab
Stopped preferring borrowed guesses and limited the first-file priori…
oleg68 Nov 6, 2026
b1aabc3
Kept the resolved NEW session eligible and stopped recording menu digits
oleg68 Nov 6, 2026
2d19ad5
Compared shared own files with other candidates and discarded cancell…
oleg68 Nov 6, 2026
b8622e3
Compared shared conversation matches by recency and scanned past rena…
oleg68 Nov 6, 2026
28662a9
Consumed pending picker cancellations and kept prompt evidence for bl…
oleg68 Nov 6, 2026
8acd1bc
Compared shared launch matches with unclaimed destinations after term…
oleg68 Nov 6, 2026
0ca28b4
Kept prompt evidence when Enter is pressed on an empty terminal line
oleg68 Nov 6, 2026
65ada45
Tracked GUI-driven conversation switches in the identity scan
oleg68 Nov 6, 2026
c8b61ad
Preferred the resolved own conversation over borrowed provisional files
oleg68 Nov 6, 2026
1971c06
Reconciled the terminal draft with GUI sends and followed GUI switche…
oleg68 Nov 6, 2026
9f7b10c
Wrote only the panel text and kept a resolved own conversation after …
oleg68 Nov 7, 2026
9137b75
Followed terminal resume commands and refused fabricated prompt evidence
oleg68 Nov 7, 2026
7124a47
Limited launch ownership to the launch conversation and tracked picke…
oleg68 Nov 7, 2026
be11e71
Preferred the launched conversation over a borrowed one and detected …
oleg68 Nov 8, 2026
dc3ba58
Honored scan exclusions for the preferred session and closed GUI reco…
Nov 9, 2026
ef8c8c4
Timed the GUI Enter before writing it and invalidated evidence on any…
Nov 9, 2026
6d641b7
Serialized terminal writes with the GUI Enter and kept the first subm…
Nov 9, 2026
1012814
Kept a switched-to conversation as own evidence and rejected partial …
Nov 9, 2026
5e76f0d
Stopped a follower's guess from hiding a created session file
Nov 9, 2026
ba6bcea
Preferred the launch conversation over a followed file that holds the…
Nov 9, 2026
f57e91b
Kept prompt evidence exact around menu answers, stale menus and GUI s…
Nov 9, 2026
4dfbd8c
Kept searchable pickers open and edited gap Enters unknown
Nov 9, 2026
b04dac7
Wrote the GUI prompt text atomically with its reconciliation
Nov 9, 2026
a8b86b7
Deferred exact GUI prompt evidence until its Enter and timed it under…
Nov 9, 2026
766303e
Deferred unknown-text GUI evidence until its Enter and published all …
Nov 9, 2026
c2433fe
Timed unknown-text GUI evidence at its Enter and recorded drafts type…
Nov 9, 2026
ca6082e
Timed GUI prompt evidence at the Enter that submits it and published …
Nov 9, 2026
afd98ad
Kept a resumed live conversation over an exited tab's record and reco…
Nov 9, 2026
58455a6
Serialized prompt panel sends so a quick second send cannot replace t…
Nov 9, 2026
9f65ae9
Kept a cancelled picker dismissed across GUI sends and marked a conve…
Nov 10, 2026
a5c29a3
Cancelled stale panel sends across a restart and kept text typed afte…
Nov 10, 2026
a2f280a
Retired menus answered by a panel send and kept the known own session…
Nov 10, 2026
419f62d
Recorded the draft submitted after an early picker selection and appl…
Nov 10, 2026
8338f0f
Cleared the text typed into a numbered menu and consumed a picker can…
Nov 10, 2026
07c5c2b
Kept the launch conversation when a stopping tab only borrowed it and…
Nov 10, 2026
837dfe8
Delivered panel sends through an ordered queue and classified menu di…
Nov 10, 2026
9e10862
Kept a numbered menu open when the typed digit did not name an option
Nov 10, 2026
cb6f859
Retired a numbered menu at the panel's digit and ignored meta records…
Nov 10, 2026
935efee
Kept the launch conversation when the PTY exited before the rename and
Nov 10, 2026
75f532e
Cancelled a paused panel send before the closing lookup and bound wri…
Nov 10, 2026
4f57669
Kept a submitted panel prompt on cancellation and left the replacemen…
Nov 10, 2026
dc1d4de
Tracked the text editor a digit activates and kept a submission throu…
Nov 10, 2026
7096a2a
Ended the tracked text editor at the arrow and guarded the cancelled …
Nov 10, 2026
6f8334b
Tracked where arrows leave the menu cursor and killed the PTY before …
Nov 10, 2026
7f7fa45
Refused to rename an identity two tabs cannot tell apart and scoped t…
Nov 10, 2026
1934998
Recorded the switch at the submitting Enter and kept typed answers in…
Nov 11, 2026
ab83544
Kept a menu answer out of the prompt evidence and the shared guess am…
Nov 11, 2026
229080e
Snapshotted the live and preserved claims under one lock before a stop
Nov 11, 2026
ac0383a
Kept a tentative sibling claim from hiding the other guess at a stop
Nov 11, 2026
807eb32
Checked unknown-text guesses for ambiguity and kept MultiSelect menus…
Nov 11, 2026
3937e83
Tracked forwarded menu focus and kept guesses ambiguous across commands
Nov 11, 2026
6fd917c
Kept numeric menu answers open until Enter and preserved guess evidence
Nov 11, 2026
a164ba6
Tracked forwarded menu focus on the instance the recorder queries
Nov 11, 2026
a188dbb
Forgot the terminal draft submitted by plugin model commands
Nov 11, 2026
e805f4a
Fixed menu-answer and picker-switch attribution when naming a session
Nov 11, 2026
d38267d
Fixed remaining stale-menu and stale-identity cases in session renaming
Nov 11, 2026
b15b4c5
Dropped menu-owned text at submit and kept resumed competitors competing
Nov 11, 2026
57d5579
Fixed the unresolved-switch, early-answer and cancelled-picker identi…
Nov 12, 2026
6e1e7b7
Fixed the external-input cutoff restored by a cancelled picker
Nov 12, 2026
d4b156e
Fixed the picker cancellation seen before the menu is published
Nov 12, 2026
ce5b01c
Fixed the panel cancel of a picker not yet published
Nov 12, 2026
10406ff
Fixed the command, menu-retirement and evidence-order gaps in the ans…
Nov 12, 2026
8d51b3f
Fixed the draft kept after cancelling a picker not yet published
Nov 12, 2026
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 session name entered in the Save & Switch dialog not being saved to the session that was closed (affected named connection profiles, projects with several sessions, and conversations switched with /resume inside the terminal), including when choosing Restart Advanced
# 1.3.45 (2026-10-28)
- Fixed edit-mode detection and switching while background agents or shells are listed under the status line
# 1.3.42 (2026-10-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.69-SNAPSHOT</version>
<version>1.3.71-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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public ClaudeProcess() {}
/** UUID of the active OpenAI proxy session; {@code null} when not using OpenAI proxy. */
private volatile String openAIProxyUuid;

/**
* Effective {@code CLAUDE_CONFIG_DIR} used by the running process (never
* {@code null} while a process is running). Defaults to {@code ~/.claude}.
*/
private volatile Path effectiveConfigDir;

/**
* Starts a Claude CLI PTY process in the given working directory using
* the Default profile (no extra env vars injected).
Expand Down Expand Up @@ -158,6 +164,7 @@ public PtyProcess start(String workingDir, ClaudeProfile profile, String extraCl
String executable = ClaudeCodePreferences.resolveClaudeExecutable();

Map<String, String> env = buildEnv(profile, ClaudeCodePreferences.getProfilesDir());
effectiveConfigDir = configDirFrom(env);

// OpenAI proxy: register session and inject ANTHROPIC_BASE_URL pointing to internal proxy
if (profile != null
Expand Down Expand Up @@ -333,10 +340,7 @@ void appendSessionFlags(List<String> cmd, String workingDir, Map<String, String>
}

if (mode == SessionMode.CONTINUE_LAST) {
String configDirStr = env.get("CLAUDE_CONFIG_DIR");
Path claudeConfigDir = configDirStr != null
? Path.of(configDirStr)
: Path.of(System.getProperty("user.home"), ".claude");
Path claudeConfigDir = configDirFrom(env);
if (ClaudeSessionStore.hasAnySessions(Path.of(workingDir), claudeConfigDir)) {
cmd.add("--continue");
LOG.fine("Session mode: --continue (existing session found)");
Expand Down Expand Up @@ -822,6 +826,42 @@ static Map<String, String> buildEnv(ClaudeProfile profile, java.nio.file.Path pr
return env;
}

/**
* Resolves the effective {@code CLAUDE_CONFIG_DIR} from an environment map.
*
* @param env environment map used for the child process
* @return the configured directory, or {@code ~/.claude} when not overridden
*/
static java.nio.file.Path configDirFrom(Map<String, String> env) {
String configDirStr = env.get("CLAUDE_CONFIG_DIR");
return configDirStr != null && !configDirStr.isBlank()
? java.nio.file.Path.of(configDirStr)
: java.nio.file.Path.of(System.getProperty("user.home"), ".claude");
}

/**
* Resolves the config dir the given profile will use once started.
*
* <p>This mirrors {@link #buildEnv} (including profile extra env vars), so it
* can be used to inspect the session store before the process is launched.
*
* @param profile connection profile; {@code null} for Default
* @param profilesDir base directory for profile config dirs
* @return effective config dir of a session started with this profile
*/
public static java.nio.file.Path resolveConfigDir(ClaudeProfile profile, java.nio.file.Path profilesDir) {
return configDirFrom(buildEnv(profile, profilesDir));
}

/**
* Returns the effective config dir of the current (or most recent) process.
*
* @return resolved config dir, or {@code null} if no process was started yet
*/
public java.nio.file.Path getConfigDir() {
return effectiveConfigDir;
}

private static void ensureLocalhostInNoProxy(Map<String, String> env) {
// Normalize lowercase no_proxy → NO_PROXY (Linux systems may use either)
if (!env.containsKey("NO_PROXY") && env.containsKey("no_proxy")) {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public final class PtyTtyConnector implements TtyConnector {
private final PtyProcess process;
private final InputStreamReader reader;

/** Time source for the submission timestamps passed to the input listener. */
private final java.util.function.LongSupplier clock;

private String tag = "";

/** Accumulates raw chars between newlines for line-based detection. */
Expand All @@ -42,13 +45,41 @@ public final class PtyTtyConnector implements TtyConnector {
/** Invoked with each stripped line; {@code null} means no listener. */
private volatile Consumer<String> lineListener;

/** Invoked with every byte block written to the PTY and the time the write
* started; {@code null} means no listener. */
private volatile java.util.function.BiConsumer<byte[], Long> inputListener;

/**
* Lock held while bytes are forwarded to the child and the input listener
* is notified; {@code null} means no serialization. The controller passes
* the lock it also holds while it writes the GUI prompt's Enter, so
* terminal input cannot reach the child on one side of that Enter while
* being recorded on the other.
*/
private volatile Object inputLock;

/**
* Sets the lock that serializes forwarding with the plugin's own writes.
*
* @param lock the lock, or {@code null} for none
*/
public void setInputLock(Object lock) {
this.inputLock = lock;
}

/**
* Creates a connector for the given PTY process.
*
* @param process a running PTY process
*/
public PtyTtyConnector(PtyProcess process) {
this(process, System::currentTimeMillis);
}

/** Package-private for tests: injects the clock used for terminal submissions. */
PtyTtyConnector(PtyProcess process, java.util.function.LongSupplier clock) {
this.process = process;
this.clock = clock;
this.reader = new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8);
}

Expand All @@ -64,6 +95,21 @@ public void setLineListener(Consumer<String> listener) {
this.lineListener = listener;
}

/**
* Sets a listener that receives every block of bytes written to the PTY —
* both plugin-sent input and text typed in the terminal — together with the
* time the write started.
*
* <p>The timestamp is sampled before the bytes are forwarded so that a
* child which appends the submitted prompt to its session file right after
* reading them cannot produce a record later than the reported time.
*
* @param listener the listener, or {@code null} to clear
*/
public void setInputListener(java.util.function.BiConsumer<byte[], Long> listener) {
this.inputListener = listener;
}

/**
* Sets a session tag used as a log prefix for debugging.
*
Expand Down Expand Up @@ -107,8 +153,32 @@ public int read(char[] buf, int offset, int length) throws IOException {

@Override
public void write(byte[] bytes) throws IOException {
// Terminal input and the plugin's own writes (in particular the GUI
// prompt's Enter) must reach the child in the same order in which they
// are recorded: forwarding bytes before the GUI Enter while notifying
// the listener only after the plugin reset its recorder would leave
// those bytes behind as the draft of the next line. The controller
// takes the same lock for the GUI Enter.
Object lock = inputLock;
if (lock == null) {
forward(bytes);
return;
}
synchronized (lock) {
forward(bytes);
}
}

private void forward(byte[] bytes) throws IOException {
// Sampled before the bytes are forwarded: the child can append the
// submitted prompt to its session file as soon as it reads them, so a
// timestamp taken after the write/listener could be later than the
// record the controller uses as evidence.
long sentAt = clock.getAsLong();
process.getOutputStream().write(bytes);
process.getOutputStream().flush();
java.util.function.BiConsumer<byte[], Long> l = inputListener;
if (l != null) l.accept(bytes, sentAt);
Comment thread
oleg68 marked this conversation as resolved.
Resolved
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ public final class ClaudePromptPanel extends JPanel {
private final Runnable onShowSessionStatistics;
/** Returns whether Session Statistics is available for the current session's connection type. */
private final Supplier<Boolean> sessionStatisticsAvailable;
/**
* Returns whether a prompt can be delivered right now. The send key must
* respect it: while a session is still starting, {@code onSend} would drop
* the text, so the field keeps it instead.
*/
private volatile Supplier<Boolean> sendAvailable = () -> true;

/** Current position in the history list; {@code -1} = newest (empty field). */
private int historyIndex = -1;
Expand Down Expand Up @@ -243,6 +249,19 @@ public void setReadyState(boolean ready) {
cancelButton.setEnabled(!ready);
}

/**
* Sets the predicate that decides whether a sent prompt can be delivered.
*
* <p>When it returns {@code false}, {@code doSend()} leaves the input text
* untouched instead of clearing it for a prompt that would be dropped —
* e.g. while the session process is still starting.
*
* @param sendAvailable the availability predicate, or {@code null} to always allow
*/
public void setSendAvailable(Supplier<Boolean> sendAvailable) {
this.sendAvailable = sendAvailable != null ? sendAvailable : () -> true;
}

/**
* Requests keyboard focus on the text input area.
*/
Expand Down Expand Up @@ -321,6 +340,9 @@ public void keyPressed(KeyEvent e) {
private void doSend() {
String text = inputArea.getText();
if (text.isEmpty()) return;
// Nothing can consume the prompt yet (e.g. the session is still
// starting): keep the text so the user does not lose it.
if (!Boolean.TRUE.equals(sendAvailable.get())) return;

inputArea.setText("");
inputArea.requestFocusInWindow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,46 @@ private void reloadSessionModePanel() {
}
}

/**
* Reloads the session list of the embedded mode panel without changing the
* selected mode. Called after a session stops so a name saved by the
* Save &amp; Switch dialog is visible immediately.
*
* <p>The transcript files are parsed on a worker (the reload runs while the
* tab tears down its chat UI); only the table update happens on the EDT.
*/
public void refreshSessionList() {
if (sessionModePanel == null) return;
File dir = resolveDir();
java.nio.file.Path workingDir = dir != null ? dir.toPath() : null;
java.nio.file.Path configDir = resolveClaudeConfigDir();
if (workingDir == null) {
sessionModePanel.reload(null, configDir, null);
return;
}
long requestId = ++sessionListRequestSeq;
Thread loader = new Thread(() -> {
java.util.List<io.github.nbplugins.claudecodegui.model.SavedSession> sessions;
try {
sessions = io.github.nbplugins.claudecodegui.process.ClaudeSessionStore
.listSessions(workingDir, configDir);
} catch (RuntimeException e) {
LOG.fine("Could not reload the session list: " + e);
sessions = java.util.List.of();
}
java.util.List<io.github.nbplugins.claudecodegui.model.SavedSession> loaded = sessions;
javax.swing.SwingUtilities.invokeLater(() -> {
if (requestId != sessionListRequestSeq) return; // superseded
sessionModePanel.applySessionsIfCurrent(workingDir, configDir, loaded);
});
}, "session-list-reload");
loader.setDaemon(true);
loader.start();
}

/** Sequence of the last asynchronous refresh, to drop superseded results. */
private long sessionListRequestSeq;

private File resolveDir() {
Object item = pathCombo != null ? pathCombo.getSelectedItem() : null;
if (item == null || item.toString().isBlank()) return null;
Expand Down Expand Up @@ -453,6 +493,12 @@ private void onOpen() {
saveToHistory(dir.getAbsolutePath());
}

if (!sessionModePanel.isSelectionValid()) {
showError(NbBundle.getMessage(ClaudeSessionSelectorPanel.class,
"ERR_NoSessionSelected"));
return;
}

errorLabel.setVisible(false);
openListener.onOpen(dir, getSelectedProfileName(), extraArgsField.getText().trim(),
sessionModePanel.getSelectedMode(),
Expand Down
Loading
Loading