fix: polish caserun aggregate trace markdown
This commit is contained in:
@@ -684,14 +684,16 @@ test("case aggregate writes one registry markdown entry without grading or broad
|
||||
artifacts: [],
|
||||
decisions: { runnerPostAgentCompileCheck: "recorded" }
|
||||
});
|
||||
const longTraceCommand = "hwpod workspace cat projects/01_baseline/Middlewares/Arm-2D/Library/include/arm_2d_types.h --spec .hwlab/hwpod-spec.yaml 2>&1 | grep -A 20 \"typedef struct arm_2d_tile_t\" 2>/dev/null || hwpod workspace cat projects/01_baseline/Middlewares/Arm-2D/Library/include/arm_2d_types.h --spec .hwlab/hwpod-spec.yaml 2>&1 | grep";
|
||||
await writeJsonFile(path.join(registryRunDir, "agent-messages.json"), {
|
||||
renderer: "tools/src/hwlab-cli/trace-renderer:traceDisplayRows",
|
||||
sourceEventCount: 3,
|
||||
renderedRowCount: 3,
|
||||
sourceEventCount: 4,
|
||||
renderedRowCount: 4,
|
||||
rows: [
|
||||
{ rowId: "row-1", seq: 1, header: "请求接受", body: "CaseRun started" },
|
||||
{ rowId: "row-2", seq: 2, header: "ok commandExecution", body: "hwpod build --spec .hwlab/hwpod-spec.yaml\nexitCode=0" },
|
||||
{ rowId: "row-3", seq: 3, header: "助手最终消息", terminal: true, bodyFormat: "markdown", body: "Build completed" }
|
||||
{ rowId: "row-3", seq: 3, header: "ok commandExecution", body: `${longTraceCommand} stdout: first line\nsecond line exitCode=0` },
|
||||
{ rowId: "row-4", seq: 4, header: "助手最终消息", terminal: true, bodyFormat: "markdown", body: "Build completed" }
|
||||
]
|
||||
});
|
||||
await writeJsonFile(path.join(registryRunDir, "artifact-manifest.json"), {
|
||||
@@ -743,8 +745,12 @@ test("case aggregate writes one registry markdown entry without grading or broad
|
||||
assert.match(aggregate, /## 输入 Prompt/u);
|
||||
assert.match(aggregate, /## 低噪声 Trace/u);
|
||||
assert.doesNotMatch(aggregate, /• 2\. ok commandExecution/u);
|
||||
assert.match(aggregate, /<details>\n<summary>已运行 hwpod build --spec \.hwlab\/hwpod-spec\.yaml<\/summary>/u);
|
||||
assert.match(aggregate, /<summary>已运行 hwpod build --spec \.hwlab\/hwpod-spec\.yaml<\/summary>[\s\S]*```text\nhwpod build --spec \.hwlab\/hwpod-spec\.yaml\nexitCode=0[\s\S]*<\/details>\n\nBuild completed\n\n## Final Response/u);
|
||||
assert.match(aggregate, /\*\*CaseRun started\*\*/u);
|
||||
assert.match(aggregate, /- <details>\n <summary>已运行 hwpod build --spec \.hwlab\/hwpod-spec\.yaml<\/summary>/u);
|
||||
assert.match(aggregate, / ```text\n hwpod build --spec \.hwlab\/hwpod-spec\.yaml\n exitCode=0[\s\S]* <\/details>/u);
|
||||
assert.match(aggregate, /<summary>已运行 hwpod workspace cat projects\/01_baseline\/Middlewares\/Arm-2D\/Library\/include\/arm_2d_type\.\.\.<\/summary>/u);
|
||||
assert.match(aggregate, / ```text\n hwpod workspace cat [^\n]+\n stdout:\n first line\n second line\n exitCode=0[\s\S]* <\/details>\n\n\*\*Build completed\*\*\n\n## Final Response/u);
|
||||
for (const [, summary] of aggregate.matchAll(/<summary>([\s\S]*?)<\/summary>/gu)) assert.doesNotMatch(summary, /\n|stdout:|stderr:|exitCode=|grep -A 20/u);
|
||||
assert.doesNotMatch(aggregate, /<summary>.*助手最终消息/u);
|
||||
assert.match(aggregate, /## Final Response/u);
|
||||
assert.match(aggregate, /## 最后 Diff/u);
|
||||
|
||||
@@ -86,21 +86,21 @@ export function renderTraceRowsMarkdown(rows: TraceEventRow[] = []): string {
|
||||
|
||||
export function renderTraceRowMarkdown(row: TraceEventRow, index = 1): string {
|
||||
if (!isToolTraceRow(row)) return renderTraceMessageRowMarkdown(row);
|
||||
const body = renderTraceRowDetailBody(row) || row.header || `trace row ${index}`;
|
||||
const body = renderTraceToolDetailBody(row) || row.header || `trace row ${index}`;
|
||||
return [
|
||||
`<details>`,
|
||||
`<summary>${escapeHtml(`已运行 ${traceToolSummary(row)}`)}</summary>`,
|
||||
`- <details>`,
|
||||
` <summary>${escapeHtml(`已运行 ${traceToolSummary(row)}`)}</summary>`,
|
||||
``,
|
||||
traceMarkdownFence(row.bodyFormat === "markdown" ? "markdown" : "text", body),
|
||||
indentTraceMarkdownBlock(traceMarkdownFence(row.bodyFormat === "markdown" ? "markdown" : "text", body)),
|
||||
``,
|
||||
`</details>`
|
||||
` </details>`
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderTraceMessageRowMarkdown(row: TraceEventRow): string {
|
||||
const body = row.body?.trimEnd();
|
||||
if (body) return body;
|
||||
return row.header || `_No readable trace row body._`;
|
||||
if (body) return traceBoldMarkdown(body);
|
||||
return traceBoldMarkdown(row.header || `_No readable trace row body._`);
|
||||
}
|
||||
|
||||
function isToolTraceRow(row: TraceEventRow): boolean {
|
||||
@@ -109,7 +109,7 @@ function isToolTraceRow(row: TraceEventRow): boolean {
|
||||
|
||||
function traceToolSummary(row: TraceEventRow): string {
|
||||
const firstLine = traceMarkdownPreviewLines(row.body).find((line) => !/^(stdout|stderr|exitCode|rowId|terminal):/iu.test(line.trim()));
|
||||
return compactTraceOneLine(stripTraceToolOutputFromSummary(firstLine || row.header || "tool call"), 320);
|
||||
return compactTraceOneLine(stripTraceToolOutputFromSummary(firstLine || row.header || "tool call"), 90);
|
||||
}
|
||||
|
||||
function stripTraceToolOutputFromSummary(value: string): string {
|
||||
@@ -483,6 +483,37 @@ function renderTraceRowDetailBody(row: TraceEventRow): string {
|
||||
].filter(Boolean).join("\n");
|
||||
}
|
||||
|
||||
function renderTraceToolDetailBody(row: TraceEventRow): string {
|
||||
return normalizeTraceToolDetailBody(renderTraceRowDetailBody(row));
|
||||
}
|
||||
|
||||
function normalizeTraceToolDetailBody(value: string): string {
|
||||
return String(value ?? "")
|
||||
.replace(/\r\n|\r/gu, "\n")
|
||||
.replace(/[^\S\n]+(stdout:)[^\S\n]*/giu, "\n$1\n")
|
||||
.replace(/[^\S\n]+(stderr:)[^\S\n]*/giu, "\n$1\n")
|
||||
.replace(/[^\S\n]+(exitCode=)/giu, "\n$1")
|
||||
.replace(/\n{3,}/gu, "\n\n")
|
||||
.trimEnd();
|
||||
}
|
||||
|
||||
function traceBoldMarkdown(value: string): string {
|
||||
const body = String(value ?? "").trimEnd();
|
||||
if (!body) return "";
|
||||
return body.split("\n").map((line) => {
|
||||
if (!line.trim()) return "";
|
||||
return `**${escapeMarkdownStrongText(line)}**`;
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
function escapeMarkdownStrongText(value: string): string {
|
||||
return escapeHtml(value).replace(/\\/gu, "\\\\").replace(/\*/gu, "\\*");
|
||||
}
|
||||
|
||||
function indentTraceMarkdownBlock(value: string): string {
|
||||
return String(value).split("\n").map((line) => ` ${line}`).join("\n");
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return String(value).replace(/&/gu, "&").replace(/</gu, "<").replace(/>/gu, ">");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user