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" }; }