From f05732a60c27086b77d2b7efa8a0090096aedb59 Mon Sep 17 00:00:00 2001 From: abel533 Date: Sat, 4 Apr 2026 20:27:15 +0800 Subject: [PATCH] fix: terminal title uses jink writeRaw instead of raw PrintStream Terminal title now writes OSC 0 through Ink.Instance.writeRaw() which goes directly to the JLine terminal. This eliminates garbled characters caused by writing through intercepted System.out/err or to streams that bypass the terminal's ANSI processing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../claudecode/tui/ClaudeCodeComponent.java | 26 ++++++++++++------- .../com/claudecode/tui/JinkReplSession.java | 3 +++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/claudecode/tui/ClaudeCodeComponent.java b/src/main/java/com/claudecode/tui/ClaudeCodeComponent.java index d2a2c31..349551e 100644 --- a/src/main/java/com/claudecode/tui/ClaudeCodeComponent.java +++ b/src/main/java/com/claudecode/tui/ClaudeCodeComponent.java @@ -77,6 +77,8 @@ public class ClaudeCodeComponent extends Component private int historyIndex = -1; private String savedInput = ""; private final AtomicBoolean agentRunning = new AtomicBoolean(false); + /** jink 渲染实例引用(用于 writeRaw 设置终端标题等) */ + private volatile io.mybatis.jink.Ink.Instance inkApp; /** 思考动画帧 */ private static final String[] SPINNER_FRAMES = {"◐", "◓", "◑", "◒"}; @@ -1476,6 +1478,11 @@ public class ClaudeCodeComponent extends Component return agentRunning.get(); } + /** 设置 jink Ink.Instance 引用(用于 writeRaw 终端标题等) */ + public void setInkApp(io.mybatis.jink.Ink.Instance app) { + this.inkApp = app; + } + /** 从 JSON 工具参数中提取人类可读的摘要 */ private static String extractToolSummary(String toolName, String args) { if (args == null || args.isBlank()) return null; @@ -1506,19 +1513,18 @@ public class ClaudeCodeComponent extends Component /** * 设置终端标题(OSC 0 escape sequence)。 - * 匹配官方 Claude Code 的 useTerminalTitle hook 行为。 - * 必须绕过 jink 的 ConsolePatcher(它会拦截 System.out/err), - * 直接写入原始输出流。 + * 匹配官方 Claude Code 的 useTerminalTitle hook / TerminalWriteContext 行为。 + * 通过 jink 的 Ink.Instance.writeRaw() 直接写入 JLine 终端。 */ - private static void setTerminalTitle(String title) { + private void setTerminalTitle(String title) { if (title == null || title.isBlank()) return; try { - // 绕过 ConsolePatcher 拦截,直接写入终端 - PrintStream raw = io.mybatis.jink.util.ConsolePatcher.getOriginalOut(); - // OSC 0: Set window title and icon name - // Format: ESC ] 0 ; BEL - raw.print("\033]0;" + title + "\007"); - raw.flush(); + var app = this.inkApp; + if (app != null) { + // OSC 0: Set window title and icon name + // Format: ESC ] 0 ; <title> BEL + app.writeRaw("\033]0;" + title + "\007"); + } } catch (Exception ignored) {} } diff --git a/src/main/java/com/claudecode/tui/JinkReplSession.java b/src/main/java/com/claudecode/tui/JinkReplSession.java index f5e48cc..45d3802 100644 --- a/src/main/java/com/claudecode/tui/JinkReplSession.java +++ b/src/main/java/com/claudecode/tui/JinkReplSession.java @@ -95,6 +95,9 @@ public class JinkReplSession { // 启动 jink 渲染(exitOnCtrlC=false,让组件处理 Ctrl+C) inkApp = Ink.render(component, false); + // 设置 inkApp 引用,使组件可以通过 writeRaw 设置终端标题 + component.setInkApp(inkApp); + // 拦截 System.out/err,防止日志干扰 TUI inkApp.patchConsole();