From d39e322bf0c10ede336ab59ea8d32429838447ec Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 3 Jun 2026 13:26:19 +0800 Subject: [PATCH] fix(web/cli): do not truncate the terminal final-response body in trace rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HWLAB v0.2 Final Response was being truncated to 5000 characters in traceAssistantSummaryRow and then again to 1200 characters in the CLI compactTraceRenderRow, even when the response was the assistant’s terminal (last) message. The same Final Response is what /v1/agent/chat/result returns as reply.content, so the trace view should show the full text and stay consistent with the CLI assistantText path. - Mark each rendered row with a terminal flag (true only for the terminal Final Response) and lift the body cap to Number.POSITIVE_INFINITY for terminal rows. The CLI Web-render path now passes the full body through when row.terminal is true and keeps the 1200 char preview for non-terminal streaming rows. - Add unit tests in web/hwlab-cloud-web/app-trace.test.ts and tools/hwlab-cli/client.test.ts to lock the behavior in for the terminal/no-truncate path and the streaming/2200 cap path. Refs: pikasTech/HWLAB#722 --- tools/hwlab-cli/client.test.ts | 20 ++++++++++++ tools/src/hwlab-cli-lib.ts | 5 ++- web/hwlab-cloud-web/app-trace.test.ts | 46 +++++++++++++++++++++++++++ web/hwlab-cloud-web/app-trace.ts | 3 +- 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/tools/hwlab-cli/client.test.ts b/tools/hwlab-cli/client.test.ts index 4eba9332..917dc865 100644 --- a/tools/hwlab-cli/client.test.ts +++ b/tools/hwlab-cli/client.test.ts @@ -1266,6 +1266,26 @@ test("hwlab-cli Web trace render keeps AgentRun middle assistant message", async assert.match(text, /助手最后一条消息/u); }); +test("hwlab-cli Web trace render keeps the full final-response body without truncating long agent summaries", async () => { + const longSummary = "下面是详细总结。".repeat(2000); + const result = await runHwlabCli(["client", "agent", "trace", "trc_final_long", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], { + fetchImpl: async () => new Response(JSON.stringify({ + status: "completed", + traceId: "trc_final_long", + events: [ + { traceId: "trc_final_long", seq: 1, label: "request:accepted", status: "accepted", createdAt: "2026-06-01T13:00:00.000Z", promptSummary: "请给出详细总结" }, + { traceId: "trc_final_long", seq: 2, label: "assistant:completed", type: "assistant_message", terminal: true, status: "completed", itemId: "assistant-final", createdAt: "2026-06-01T13:00:02.000Z", message: longSummary } + ] + }), { status: 200 }) + }); + + assert.equal(result.exitCode, 0); + const terminalRow = (result.payload.body.rows as any[]).find((row) => row.terminal === true); + assert.ok(terminalRow, "expected a terminal row to be rendered for the final response"); + assert.equal(terminalRow.body, longSummary); + assert.equal(terminalRow.body?.includes("..."), false); +}); + test("hwlab-cli client agent trace auto logs in when protected trace has no local session", async () => { const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-auth-")); const calls: any[] = []; diff --git a/tools/src/hwlab-cli-lib.ts b/tools/src/hwlab-cli-lib.ts index e69c038b..ed233215 100644 --- a/tools/src/hwlab-cli-lib.ts +++ b/tools/src/hwlab-cli-lib.ts @@ -2325,7 +2325,10 @@ function compactTraceRenderRow(row: any) { tone: row?.tone ?? null, header: row?.header ?? null, bodyFormat: row?.bodyFormat ?? null, - body: row?.body ? preview(String(row.body), 1200) : undefined + terminal: row?.terminal === true ? true : undefined, + body: row?.body + ? (row?.terminal === true ? String(row.body) : preview(String(row.body), 1200)) + : undefined }); } diff --git a/web/hwlab-cloud-web/app-trace.test.ts b/web/hwlab-cloud-web/app-trace.test.ts index c0ccaf7b..65d354ab 100644 --- a/web/hwlab-cloud-web/app-trace.test.ts +++ b/web/hwlab-cloud-web/app-trace.test.ts @@ -407,3 +407,49 @@ function matchCount(text, pattern) { const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`; return [...String(text ?? "").matchAll(new RegExp(pattern.source, flags))].length; } + +test("trace display rows keep full final-response text without truncating long agent summaries", () => { + const longSummary = "下面是详细总结。".repeat(2000); + const events = [ + event(1, "request:accepted", { promptSummary: "请给出详细总结" }), + event(2, "assistant:completed", { + type: "assistant_message", + terminal: true, + status: "completed", + itemId: "assistant-final", + message: longSummary + }) + ]; + const rows = traceDisplayRows({ traceId: "trc_final_long", events }, events); + const terminalRow = rows.find((row) => row.terminal === true); + assert.ok(terminalRow, "expected a terminal row to be rendered for the final response"); + assert.equal(terminalRow.tone, "ok"); + assert.equal(terminalRow.body?.length, longSummary.length); + assert.equal(terminalRow.body?.includes("内容已截断"), false); +}); + +test("trace display rows still cap non-terminal streaming assistant text at the 2200 char limit", () => { + const streamingText = "streaming ".repeat(2000); + const events = [ + event(1, "request:accepted", { promptSummary: "stream" }), + event(2, "assistant:item_completed", { + type: "assistant_message", + status: "completed", + itemId: "msg_streaming", + message: streamingText + }), + event(3, "assistant:completed", { + type: "assistant_message", + terminal: true, + status: "completed", + itemId: "assistant-final", + message: "done" + }) + ]; + const rows = traceDisplayRows({ traceId: "trc_streaming_long", events }, events); + const streamingRow = rows.find((row) => row.rowId === "event:2"); + assert.ok(streamingRow, "expected a non-terminal streaming row to be rendered"); + assert.equal(streamingRow.terminal, false); + assert.ok((streamingRow.body?.length ?? 0) <= 2200); + assert.match(streamingRow.body ?? "", /内容已截断/u); +}); diff --git a/web/hwlab-cloud-web/app-trace.ts b/web/hwlab-cloud-web/app-trace.ts index a956904a..83bff34f 100644 --- a/web/hwlab-cloud-web/app-trace.ts +++ b/web/hwlab-cloud-web/app-trace.ts @@ -743,7 +743,8 @@ function traceAssistantSummaryRow(trace, event, index = 0, textOverride = undefi header: terminal ? `${traceClock(event.createdAt)} 🤖 助手最后一条消息,轮次完成(总耗时 ${elapsed})` : `${traceClock(event.createdAt)} 🤖 助手消息${index === 0 ? "(第一条)" : ""}`, - body: text ? compactTraceAssistantMarkdown(text, terminal ? 5000 : 2200) : null, + terminal: terminal === true, + body: text ? compactTraceAssistantMarkdown(text, terminal ? Number.POSITIVE_INFINITY : 2200) : null, bodyFormat: "markdown" }; }