From 96b4557b0a85783f2b6761c2f6ea2d50c61da1bc Mon Sep 17 00:00:00 2001 From: lyon Date: Mon, 15 Jun 2026 20:50:27 +0800 Subject: [PATCH] fix(web): show tool command preview in trace rows --- tools/src/hwlab-cli/trace-renderer.ts | 3 +++ web/hwlab-cloud-web/scripts/check.ts | 4 ++++ .../src/components/agent/TraceTimeline.vue | 18 +++++++++++++++++- web/hwlab-cloud-web/src/styles/workbench.css | 16 ++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tools/src/hwlab-cli/trace-renderer.ts b/tools/src/hwlab-cli/trace-renderer.ts index 01d30c90..deb1c6e0 100644 --- a/tools/src/hwlab-cli/trace-renderer.ts +++ b/tools/src/hwlab-cli/trace-renderer.ts @@ -4,6 +4,7 @@ export interface TraceEventRow { tone: "ok" | "blocked" | "warn" | "source"; header: string; body: string | null; + preview?: string | null; terminal?: boolean; bodyFormat?: "markdown" | "text"; } @@ -101,6 +102,7 @@ function isToolTraceRow(row: TraceEventRow): boolean { } function traceToolSummary(row: TraceEventRow): string { + if (row.preview) return compactTraceOneLine(row.preview, 90); const firstLine = traceMarkdownPreviewLines(row.body).find((line) => !/^(stdout|stderr|exitCode|rowId|terminal):/iu.test(line.trim())); return compactTraceOneLine(stripTraceToolOutputFromSummary(firstLine || row.header || "tool call"), 90); } @@ -132,6 +134,7 @@ function traceToolCallRow(trace: Record, event: TraceEvent): Tr tone: traceEventTone(event), header: `${traceEventClock(event)} total=${formatTraceDuration(traceRelativeMs(trace, event))} ${status} ${toolName}`.trim(), body, + preview: command ? compactTraceOneLine(command, 2000) : null, bodyFormat: "text" }; } diff --git a/web/hwlab-cloud-web/scripts/check.ts b/web/hwlab-cloud-web/scripts/check.ts index 88ad399e..f7273348 100644 --- a/web/hwlab-cloud-web/scripts/check.ts +++ b/web/hwlab-cloud-web/scripts/check.ts @@ -107,6 +107,10 @@ assertIncludes(messageTraceDebugSource, "trace-meta-panel", "Trace identity and assertIncludes(appSource, "scrollbar-width: none;", "Platform sidebar must scroll without a visible scrollbar"); assertIncludes(appSource, "border-collapse: collapse;", "Markdown tables must use GitHub-like bordered table styling"); assertIncludes(appSource, "grid-template-columns: minmax(0, 1fr);", "Trace rows must give the readable body the full content width"); +assertIncludes(traceTimelineSource, "toolCommandPreview", "Trace tool summaries must expose command previews in the collapsed single-line row"); +assertIncludes(traceTimelineSource, "trace-tool-command-preview", "Trace tool command preview must render inside the collapsed summary"); +assertIncludes(traceTimelineSource, "row.preview", "Trace tool command preview must use request-side row.preview instead of response body text"); +assertIncludes(appSource, "text-overflow: ellipsis;", "Trace tool command preview must keep long commands visually clipped on one line"); assertIncludes(appSource, "const primaryAction = computed", "Workbench composer must derive a single primary send/cancel/steer action"); assertIncludes(appSource, "primaryAction.value === \"cancel\"", "Composer primary button must switch to cancel while a turn is running and the draft is empty"); assertIncludes(appSource, "primaryAction.value === \"steer\"", "Composer primary button must switch to steer while a turn is running and the draft has text"); diff --git a/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue b/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue index 367f4bfb..d01b03c7 100644 --- a/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue +++ b/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue @@ -13,6 +13,7 @@ const expanded = ref(false); const events = computed(() => Array.isArray(props.trace?.events) ? props.trace.events : []); const eventCount = computed(() => props.trace?.eventCount ?? events.value.length); const readableRows = computed(() => traceDisplayRows(traceRecord(props.trace), events.value as Record[])); +const toolPreviewLimit = 140; watch(() => [eventCount.value, props.trace?.status, following.value, expanded.value], async () => { if (!following.value || !expanded.value) return; @@ -70,6 +71,18 @@ function rowIsTool(row: TraceEventRow): boolean { return row.rowId.startsWith("tool:") || /commandExecution/u.test(row.header); } +function toolCommandPreview(row: TraceEventRow): string | null { + if (!rowIsTool(row)) return null; + const preview = singleLinePreview(row.preview); + if (!preview) return null; + return preview.length > toolPreviewLimit ? `${preview.slice(0, toolPreviewLimit - 3).trimEnd()}...` : preview; +} + +function singleLinePreview(value: unknown): string | null { + const preview = String(value ?? "").replace(/\u0000/gu, "").replace(/\r\n|\r|\n/gu, " ").replace(/\s+/gu, " ").trim(); + return preview || null; +} + function traceRecord(trace: RunnerTrace | null | undefined): Record { return trace && typeof trace === "object" ? trace as Record : {}; } @@ -87,7 +100,10 @@ function traceRecord(trace: RunnerTrace | null | undefined): Record
  • - {{ row.header }} + + {{ row.header }} + {{ toolCommandPreview(row) }} +
    {{ row.body }}
    已记录 diff --git a/web/hwlab-cloud-web/src/styles/workbench.css b/web/hwlab-cloud-web/src/styles/workbench.css index 0d27b704..80fa5589 100644 --- a/web/hwlab-cloud-web/src/styles/workbench.css +++ b/web/hwlab-cloud-web/src/styles/workbench.css @@ -1073,8 +1073,10 @@ min-width: 0; align-items: center; gap: 6px; + overflow: hidden; cursor: pointer; list-style: none; + white-space: nowrap; } .trace-tool-details > summary::before { @@ -1094,11 +1096,25 @@ .trace-tool-details > summary code { min-width: 0; + max-width: 42%; + flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.trace-tool-command-preview { + min-width: 0; + flex: 1 1 auto; + overflow: hidden; + color: #334155; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 11px; + font-weight: 650; + text-overflow: ellipsis; + white-space: nowrap; +} + .trace-timeline li { display: grid; grid-template-columns: minmax(0, 1fr);