From d17beb8a852a082a8928e7bb050fb32dba959f99 Mon Sep 17 00:00:00 2001 From: liuzh Date: Thu, 2 Apr 2026 01:29:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E5=89=8D=E5=88=B7=E6=96=B0=E6=B5=81=E5=BC=8F=E8=A1=8C=E7=BC=93?= =?UTF-8?q?=E5=86=B2=20-=20=E4=BF=AE=E5=A4=8DAI=E6=96=87=E6=9C=AC=E4=B8=A2?= =?UTF-8?q?=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lineBuffer/mdState提升为实例字段(工具事件回调可访问) - 工具START事件先flushStreamLineBuffer()再渲染工具状态 - 修复: AI在工具调用前发送的文本(无\\n结尾)现在正确显示 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../java/com/claudecode/repl/ReplSession.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/claudecode/repl/ReplSession.java b/src/main/java/com/claudecode/repl/ReplSession.java index a5180c2..c94e454 100644 --- a/src/main/java/com/claudecode/repl/ReplSession.java +++ b/src/main/java/com/claudecode/repl/ReplSession.java @@ -60,6 +60,11 @@ public class ReplSession { /** 流式输出换行跟踪:工具渲染和流式回调共享,保证缩进一致 */ private volatile boolean streamNewLine = false; + /** 流式行缓冲:累积 token 到换行再 Markdown 渲染输出 */ + private final StringBuilder streamLineBuffer = new StringBuilder(); + /** 流式 Markdown 渲染状态:跨行追踪代码块 */ + private MarkdownRenderer.StreamState streamMdState = new MarkdownRenderer.StreamState(); + /** 当前活跃的 LineReader(JLine 模式下用于 AskUser 和权限确认) */ private volatile LineReader activeReader; /** 当前活跃的 Scanner(Scanner 模式下用于 AskUser 和权限确认) */ @@ -102,7 +107,8 @@ public class ReplSession { switch (event.phase()) { case START -> { spinner.stop(); - // 如果前面的流式文本没有换行结尾,先换行 + // 刷新行缓冲(AI 文本可能在工具调用前没有换行结尾) + flushStreamLineBuffer(); if (!streamNewLine) { out.println(); } @@ -333,13 +339,11 @@ public class ReplSession { try { spinner.start("Thinking..."); streamNewLine = true; // spinner 停止后 onStreamStart 会打印 ● 前缀 + streamLineBuffer.setLength(0); // 重置行缓冲 + streamMdState = new MarkdownRenderer.StreamState(); // 重置 Markdown 状态 long startTime = System.currentTimeMillis(); - // 行缓冲:累积 token 直到换行,再用 MarkdownRenderer 渲染整行输出 - StringBuilder lineBuffer = new StringBuilder(); - MarkdownRenderer.StreamState mdState = new MarkdownRenderer.StreamState(); - String response = agentLoop.runStreaming(input, token -> { for (int i = 0; i < token.length(); i++) { char c = token.charAt(i); @@ -349,26 +353,19 @@ public class ReplSession { out.print(" "); // 续行缩进(与 ● 后文本对齐) streamNewLine = false; } - String rendered = markdownRenderer.renderStreamingLine(lineBuffer.toString(), mdState); + String rendered = markdownRenderer.renderStreamingLine(streamLineBuffer.toString(), streamMdState); out.println(rendered); - lineBuffer.setLength(0); + streamLineBuffer.setLength(0); streamNewLine = true; } else { - lineBuffer.append(c); + streamLineBuffer.append(c); } } out.flush(); }); // 刷新残留缓冲(最后一行可能无 \n 结尾) - if (!lineBuffer.isEmpty()) { - if (streamNewLine) { - out.print(" "); - streamNewLine = false; - } - String rendered = markdownRenderer.renderStreamingLine(lineBuffer.toString(), mdState); - out.print(rendered); - } + flushStreamLineBuffer(); spinner.stop(); out.println(); // 流式输出结束后换行 @@ -392,6 +389,22 @@ public class ReplSession { } } + /** + * 刷新流式行缓冲 —— 将未输出的缓冲内容渲染并打印。 + * 在工具调用前、流式结束后调用,防止 AI 文本丢失。 + */ + private void flushStreamLineBuffer() { + if (!streamLineBuffer.isEmpty()) { + if (streamNewLine) { + out.print(" "); + streamNewLine = false; + } + String rendered = markdownRenderer.renderStreamingLine(streamLineBuffer.toString(), streamMdState); + out.print(rendered); + streamLineBuffer.setLength(0); + } + } + /** 退出时保存对话历史 */ private void saveConversation() { var history = agentLoop.getMessageHistory();