diff --git a/tools/capture-issue-803-noise.mjs b/tools/capture-issue-803-noise.mjs
new file mode 100644
index 00000000..ac7b5c0e
--- /dev/null
+++ b/tools/capture-issue-803-noise.mjs
@@ -0,0 +1,120 @@
+#!/usr/bin/env node
+// HWLAB #803 noise-capture CLI.
+//
+// Captures the rendered Agent workspace (#code-agent-summary, per-message
+// meta/pending/runtime/session/trace cards) from a live or local Cloud Web
+// build and prints a structured JSON dump that can be diffed before/after a
+// Web UI refactor.
+//
+// Usage:
+// CHROMIUM_PATH=/usr/bin/chromium-browser \
+// node tools/capture-issue-803-noise.mjs \
+// --url http://74.48.78.17:17666/ --user admin --pass hwlab2026 \
+// --png /tmp/hwlab-noise.png
+// CHROMIUM_PATH=/usr/bin/chromium-browser \
+// NOISE_USER=admin NOISE_PASS=hwlab2026 \
+// node tools/capture-issue-803-noise.mjs
+
+import { chromium } from "playwright";
+import { setTimeout as sleep } from "node:timers/promises";
+
+function parseArgs(argv) {
+ const args = {
+ url: process.env.NOISE_URL ?? "http://74.48.78.17:17666/",
+ user: process.env.NOISE_USER ?? "admin",
+ pass: process.env.NOISE_PASS ?? "hwlab2026",
+ message: process.env.NOISE_MESSAGE ?? null,
+ viewport: process.env.NOISE_VIEWPORT ?? "1440x900",
+ png: process.env.NOISE_PNG ?? "/tmp/hwlab-noise.png"
+ };
+ for (let index = 0; index < argv.length; index += 1) {
+ const arg = argv[index];
+ if (arg === "--url") args.url = argv[++index];
+ else if (arg === "--user") args.user = argv[++index];
+ else if (arg === "--pass") args.pass = argv[++index];
+ else if (arg === "--message") args.message = argv[++index];
+ else if (arg === "--png") args.png = argv[++index];
+ else if (arg === "--viewport") args.viewport = argv[++index];
+ }
+ const [w, h] = args.viewport.split("x").map(Number);
+ args.viewportWidth = Number.isFinite(w) ? w : 1440;
+ args.viewportHeight = Number.isFinite(h) ? h : 900;
+ return args;
+}
+
+const args = parseArgs(process.argv.slice(2));
+const launchOptions = { headless: true };
+if (process.env.CHROMIUM_PATH) launchOptions.executablePath = process.env.CHROMIUM_PATH;
+
+const browser = await chromium.launch(launchOptions);
+const ctx = await browser.newContext({ viewport: { width: args.viewportWidth, height: args.viewportHeight } });
+const page = await ctx.newPage();
+await page.goto(args.url, { waitUntil: "networkidle", timeout: 30000 });
+try {
+ await page.locator("#login-username").fill(args.user, { timeout: 5000 });
+ await page.locator("#login-password").fill(args.pass);
+ await page.locator("#login-submit").click();
+ await page.locator("#command-input").waitFor({ state: "visible", timeout: 12000 });
+} catch (error) {
+ console.error("[noise] login failed:", error instanceof Error ? error.message : String(error));
+}
+await page.waitForTimeout(1500);
+if (args.message) {
+ try {
+ await page.locator("#command-input").fill(args.message, { timeout: 5000 });
+ await page.locator("#command-send").click();
+ await sleep(8000);
+ } catch (error) {
+ console.error("[noise] submit failed:", error instanceof Error ? error.message : String(error));
+ }
+}
+
+const data = await page.evaluate(() => {
+ const summary = document.querySelector("#code-agent-summary");
+ const summaryRect = summary?.getBoundingClientRect();
+ const summaryText = summary?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 800) ?? null;
+ const summaryDetail = document.querySelector("#code-agent-summary-detail");
+ const summaryDetailRect = summaryDetail?.getBoundingClientRect();
+ const summaryDetailText = summaryDetail?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 1500) ?? null;
+ const summaryIsOpen = summary ? summary.open === true : null;
+ const summaryDebugBtn = document.querySelector("#code-agent-debug-trigger");
+ const convList = document.querySelector("#conversation-list");
+ const convListRect = convList?.getBoundingClientRect();
+ const messages = [...document.querySelectorAll(".message-card")].map((card) => {
+ const rect = card.getBoundingClientRect();
+ const meta = card.querySelector(".message-meta")?.textContent?.replace(/\s+/gu, " ").trim() ?? null;
+ const pending = card.querySelector(".message-pending-context")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 600) ?? null;
+ const session = card.querySelector(".message-session-context")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 600) ?? null;
+ const runtime = card.querySelector(".message-runtime-path")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 600) ?? null;
+ const trace = card.querySelector(".message-trace")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 400) ?? null;
+ const traceEventsRect = card.querySelector(".message-trace-events")?.getBoundingClientRect();
+ const debugTrigger = card.querySelector(".debug-dialog-trigger");
+ const cardWidth = rect.width;
+ const tracePct = cardWidth > 0 && traceEventsRect ? Math.round((traceEventsRect.width / cardWidth) * 100) : null;
+ return {
+ meta,
+ pending,
+ session,
+ runtime,
+ trace,
+ debugTriggerText: debugTrigger?.textContent ?? null,
+ cardWidth: Math.round(cardWidth),
+ traceEventsWidth: traceEventsRect ? Math.round(traceEventsRect.width) : null,
+ traceEventsPct: tracePct
+ };
+ });
+ return {
+ summaryText,
+ summaryDetailText,
+ summaryIsOpen,
+ summaryDebugBtnExists: !!summaryDebugBtn,
+ summaryRect: summaryRect ? { width: Math.round(summaryRect.width), height: Math.round(summaryRect.height) } : null,
+ summaryDetailRect: summaryDetailRect ? { width: Math.round(summaryDetailRect.width), height: Math.round(summaryDetailRect.height) } : null,
+ convListRect: convListRect ? { width: Math.round(convListRect.width), height: Math.round(convListRect.height) } : null,
+ messageCount: messages.length,
+ messages
+ };
+});
+console.log(JSON.stringify(data, null, 2));
+await page.screenshot({ path: args.png, fullPage: false });
+await browser.close();
diff --git a/web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx b/web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx
index 768cda0f..2e8165df 100644
--- a/web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx
+++ b/web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx
@@ -1,17 +1,15 @@
import type { ReactElement } from "react";
-import { useMemo } from "react";
+import { useMemo, useState } from "react";
import { renderMessageMarkdown } from "../../services/markdown/render";
import type { ChatMessage, CodeAgentAvailability, LiveSurface } from "../../types/domain";
import { formatBeijingTime, jsonPreview, shortToken, toneClass } from "../../utils";
-import { codeAgentSummaryRows, classifyCodeAgentStatusSummary } from "../../state/code-agent-status";
+import { codeAgentSummaryRows, classifyCodeAgentStatusSummary, codeAgentRuntimePathFromMessage } from "../../state/code-agent-status";
import { agentStatusLabel } from "../../state/agent-status-labels";
import { StateTag } from "../shared/StateTag";
+import { DebugDialog, type DebugRow, type DebugSection } from "../shared/DebugDialog";
import { MessageTracePanel } from "./MessageTracePanel";
import { MessageActions } from "./MessageActions";
-import { MessagePendingContext } from "./MessagePendingContext";
-import { MessageRuntimePath } from "./MessageRuntimePath";
-import { MessageSessionContinuity } from "./MessageSessionContinuity";
interface ConversationPanelProps {
messages: ChatMessage[];
@@ -60,7 +58,16 @@ export function ConversationPanel({ messages, availability, live, chatPending, c
- {[...intro, ...messages].map((message) => onCancelMessage(message.id)} onRetry={() => onRetryMessage(message.id)} onReplayTrace={() => onReplayTrace(message.id)} />)}
+ {[...intro, ...messages].map((message) => (
+ onCancelMessage(message.id)}
+ onRetry={() => onRetryMessage(message.id)}
+ onReplayTrace={() => onReplayTrace(message.id)}
+ />
+ ))}
@@ -78,27 +85,49 @@ function latestAgentMessage(messages: ChatMessage[]): Record |
function CodeAgentSummary({ availability, live, latestMessage }: { availability: CodeAgentAvailability | null; live: LiveSurface | null; latestMessage: Record | null }): ReactElement {
const summary = useMemo(() => classifyCodeAgentStatusSummary({ availability, live, latestMessage }), [availability, live, latestMessage]);
- const rows = useMemo(() => codeAgentSummaryRows(summary), [summary]);
+ const rows = useMemo(() => codeAgentSummaryRows(summary).map((row) => ({ label: row.key, value: row.value, tone: row.tone as DebugRow["tone"] })), [summary]);
+ const [open, setOpen] = useState(false);
+ const sections: DebugSection[] = [
+ { title: "Code Agent 详情", rows },
+ { title: "readiness blockers", rows: summary.readinessBlockers.length > 0
+ ? summary.readinessBlockers.map((blocker) => ({ label: blocker, value: blocker, tone: "blocked" as const }))
+ : [] , empty: summary.readinessBlockers.length === 0 ? "无" : undefined }
+ ];
return (
-
-
- {summary.icon}
- {summary.label}
- {summary.capabilityLevel}
- trace {summary.lastTraceId}
-
-
-
- {rows.map((row) => (
-
- {row.key}
- {row.value}
-
- ))}
+
+
setOpen((event.currentTarget as HTMLDetailsElement).open)}
+ >
+
+ {summary.icon}
+ {summary.label}
+ {summary.capabilityLevel}
+
+
+
+ {rows.map((row, index) => (
+
+ {row.label}
+ {row.value}
+
+ ))}
+
- {summary.readinessBlockers.length > 0 ? 展开 readiness 原始 JSON
{jsonPreview(availability ?? { status: "unverified" }, 1200)} : null}
-
-
+
+
+
);
}
@@ -115,20 +144,96 @@ function MessageCard({ message, codeAgentTimeoutMs, onCancel, onRetry, onReplayT
{statusLabel(message).label}
- {message.role === "agent" && message.status === "running" ?
: null}
- {message.role === "agent" && message.status !== "running" ?
: null}
- {message.role === "agent" ?
: null}
-
+
{message.runnerTrace ?
: null}
-
);
}
+function MessageDebug({ message, codeAgentTimeoutMs, onCancel, onRetry, onReplayTrace }: { message: ChatMessage; codeAgentTimeoutMs: number; onCancel(): void; onRetry(): void; onReplayTrace(): void }): ReactElement | null {
+ if (message.role === "user" || message.role === "system") {
+ if (!message.traceId) return null;
+ return (
+
+ );
+ }
+ return (
+ <>
+
+ {message.status === "running" ? (
+
+ 处理中
+ {message.runnerTrace?.waitingFor ? `等待 ${message.runnerTrace.waitingFor}` : "等待后端事件"}
+
+ ) : null}
+ >
+ );
+}
+
+function systemDebugSections(message: ChatMessage): DebugSection[] {
+ const rows: DebugRow[] = [];
+ if (message.traceId) rows.push({ label: "traceId", value: message.traceId, copyable: true });
+ return [{ title: "会话标识", rows }];
+}
+
+function agentDebugSections(message: ChatMessage, codeAgentTimeoutMs: number): DebugSection[] {
+ const sessionRows: DebugRow[] = [];
+ if (message.traceId) sessionRows.push({ label: "traceId", value: message.traceId, copyable: true });
+ if (message.conversationId) sessionRows.push({ label: "conversation", value: message.conversationId, copyable: true });
+ if (message.sessionId) sessionRows.push({ label: "session", value: message.sessionId, copyable: true });
+ else if (message.runnerTrace?.sessionId) sessionRows.push({ label: "session", value: String(message.runnerTrace.sessionId), copyable: true });
+ if (message.threadId) sessionRows.push({ label: "thread", value: message.threadId, copyable: true });
+ else if (message.runnerTrace?.threadId) sessionRows.push({ label: "thread", value: String(message.runnerTrace.threadId), copyable: true });
+ const sessionStatus = typeof message.runnerTrace?.sessionStatus === "string" ? message.runnerTrace.sessionStatus : message.status;
+ if (sessionStatus) sessionRows.push({ label: "sessionStatus", value: String(sessionStatus) });
+ const lifecycle = typeof message.runnerTrace?.sessionLifecycleStatus === "string" ? message.runnerTrace.sessionLifecycleStatus : null;
+ if (lifecycle) sessionRows.push({ label: "lifecycle", value: lifecycle });
+ if (message.runnerTrace?.lastEventLabel) sessionRows.push({ label: "lastEvent", value: String(message.runnerTrace.lastEventLabel) });
+ sessionRows.push({ label: "activityTimeout", value: `${Math.round(codeAgentTimeoutMs / 60000)}m 无新事件` });
+
+ const runtime = codeAgentRuntimePathFromMessage({ role: "agent", ...(message as unknown as Record
) });
+ const runtimeRows: DebugRow[] = [
+ { label: "provider", value: runtime.rows[0]?.value ?? "未观测" },
+ { label: "runnerKind", value: runtime.rows[1]?.value ?? "未观测" },
+ { label: "protocol", value: runtime.rows[2]?.value ?? "未观测" },
+ { label: "implementationType", value: runtime.rows[3]?.value ?? "未观测" },
+ { label: "providerTrace.command", value: runtime.rows[4]?.value ?? "未观测" },
+ { label: "providerTrace.terminalStatus", value: runtime.rows[5]?.value ?? "未观测" },
+ { label: "providerTrace.failureKind", value: runtime.rows[6]?.value ?? "未观测" }
+ ].filter((row) => row.value !== "未观测" && row.value !== "字段缺失:证据不足");
+ const runtimeSection: DebugSection = { title: "Runtime Path", rows: runtimeRows, empty: runtime.missingFields.length > 0 ? "未观测到 providerTrace 字段,跳过该 section。" : "运行路径不可用。" };
+
+ return [
+ { title: "会话标识", rows: sessionRows },
+ runtimeSection
+ ];
+}
+
function statusLabel(message: ChatMessage): { label: string; tone: ReturnType } {
const label = agentStatusLabel(message);
return { label: label.label, tone: label.tone };
diff --git a/web/hwlab-cloud-web/src/components/shared/DebugDialog.tsx b/web/hwlab-cloud-web/src/components/shared/DebugDialog.tsx
new file mode 100644
index 00000000..4bd43ca4
--- /dev/null
+++ b/web/hwlab-cloud-web/src/components/shared/DebugDialog.tsx
@@ -0,0 +1,123 @@
+import type { ReactElement } from "react";
+import { useEffect, useId, useState } from "react";
+
+export interface DebugRow {
+ label: string;
+ value: string;
+ tone?: "ok" | "warn" | "blocked" | "source" | "pending" | "dev-live" | "error";
+ copyable?: boolean;
+}
+
+export interface DebugSection {
+ title: string;
+ rows: DebugRow[];
+ empty?: string;
+}
+
+interface DebugDialogProps {
+ triggerLabel?: string;
+ triggerTitle?: string;
+ dialogId: string;
+ title: string;
+ sections: DebugSection[];
+ rawJson?: unknown;
+ empty?: string;
+ className?: string;
+}
+
+function toneClass(tone: DebugRow["tone"]): string {
+ if (tone === "ok" || tone === "warn" || tone === "blocked" || tone === "source" || tone === "pending" || tone === "dev-live" || tone === "error") return tone;
+ return "source";
+}
+
+export function DebugDialog({ triggerLabel, triggerTitle, dialogId, title, sections, rawJson, empty, className }: DebugDialogProps): ReactElement {
+ const fallbackId = useId();
+ const id = dialogId || fallbackId;
+ const triggerId = `${id}-trigger`;
+ const [open, setOpen] = useState(false);
+
+ useEffect(() => {
+ if (!open) return;
+ const handleKey = (event: KeyboardEvent) => {
+ if (event.key === "Escape") setOpen(false);
+ };
+ window.addEventListener("keydown", handleKey);
+ return () => window.removeEventListener("keydown", handleKey);
+ }, [open]);
+
+ const totalRows = sections.reduce((acc, section) => acc + section.rows.length, 0);
+ const allEmpty = totalRows === 0 && !rawJson;
+
+ return (
+ <>
+
+ {open ? (
+ {
+ if (event.target === event.currentTarget) setOpen(false);
+ }}
+ >
+
+
+ ) : null}
+ >
+ );
+}
diff --git a/web/hwlab-cloud-web/src/state/code-agent-status.ts b/web/hwlab-cloud-web/src/state/code-agent-status.ts
index 7c4e9221..f7c6175d 100644
--- a/web/hwlab-cloud-web/src/state/code-agent-status.ts
+++ b/web/hwlab-cloud-web/src/state/code-agent-status.ts
@@ -353,7 +353,9 @@ function compactRunnerTraceSummary(runnerTrace: unknown): string {
}
export function codeAgentSummaryRows(summary: CodeAgentStatusSummary): { key: string; value: string; tone: CodeAgentStatusSummary["tone"] | "source" }[] {
- return [
+ // 顶层 summary 仅保留有意义条目;providerTrace.* / facts / runnerTrace echo
+ // 等未观测噪音移出,调试用户走"调试信息" dialog 读取 summary.fields。
+ const allRows: Array<{ key: string; value: string; tone: CodeAgentStatusSummary["tone"] | "source" }> = [
{ key: "codeAgent.status", value: summary.codeAgentStatus, tone: summary.tone },
{ key: "provider/mode/backend", value: summary.providerModeBackend, tone: "source" },
{ key: "capabilityLevel", value: summary.capabilityLevel, tone: summary.tone },
@@ -363,20 +365,20 @@ export function codeAgentSummaryRows(summary: CodeAgentStatusSummary): { key: st
{ key: "workspace", value: summary.workspace, tone: "source" },
{ key: "sandbox", value: summary.sandbox, tone: "source" },
{ key: "runnerKind", value: summary.runnerKind, tone: summary.kind === "long-lived-session" ? "ok" : "source" },
- { key: "protocol", value: summary.protocol, tone: summary.runtimePathTone },
- { key: "implementationType", value: summary.implementationType, tone: summary.runtimePathTone },
- { key: "providerTrace.command", value: summary.providerTraceCommand, tone: summary.runtimePathTone },
- { key: "providerTrace.terminalStatus", value: summary.providerTraceTerminalStatus, tone: summary.runtimePathTone },
- { key: "providerTrace.failureKind", value: summary.providerTraceFailureKind, tone: summary.runtimePathTone },
{ key: "运行路径语义", value: summary.runtimePathLabel, tone: summary.runtimePathTone },
{ key: "toolCalls", value: summary.toolCalls, tone: summary.toolCalls === "none" ? "source" : summary.tone },
{ key: "skills", value: summary.skills, tone: summary.skills === "none" ? "source" : summary.tone },
- { key: "conversation facts", value: summary.fields.runnerTrace ?? "未观测", tone: "source" },
- { key: "runnerTrace", value: summary.runnerTrace, tone: "source" },
{ key: "readiness blockers", value: summary.blockersLabel, tone: summary.readinessBlockers.length > 0 ? "blocked" : "source" },
- { key: "last traceId", value: summary.lastTraceId, tone: "source" },
{ key: "当前部署 revision", value: summary.deploymentRevision, tone: "source" }
];
+ return allRows.filter((row) => {
+ if (!row.value) return false;
+ if (row.value === "未观测") return false;
+ if (row.value === "字段缺失:证据不足") return false;
+ if (row.value === "DEGRADED:运行路径字段不完整") return false;
+ if (row.value === "无" && row.key === "readiness blockers") return false;
+ return true;
+ });
}
function sessionSummaryTone(summary: CodeAgentStatusSummary): CodeAgentStatusSummary["tone"] {
@@ -395,4 +397,4 @@ export function summarizeLastEvent(trace: RunnerTrace | null | undefined): { lab
const status = String(last?.status ?? "");
const tone: CodeAgentStatusSummary["tone"] = status === "completed" ? "ok" : status === "running" || status === "pending" ? "warn" : status === "failed" ? "blocked" : "source";
return { label: lastLabel, ts, tone };
-}
+}
\ No newline at end of file
diff --git a/web/hwlab-cloud-web/src/styles/workbench.css b/web/hwlab-cloud-web/src/styles/workbench.css
index 59252d9d..e16f763d 100644
--- a/web/hwlab-cloud-web/src/styles/workbench.css
+++ b/web/hwlab-cloud-web/src/styles/workbench.css
@@ -101,7 +101,7 @@ button:disabled { cursor: not-allowed; opacity: 0.55; }
.message-trace-label { font-weight: 700; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; overflow-wrap: anywhere; }
.message-trace-time { color: var(--muted); font-size: 12px; }
.message-trace-body { margin: 6px 0 0; max-height: 220px; overflow: auto; background: #f3f5f1; padding: 6px 8px; border-radius: 6px; font-size: 12px; }
-.message-trace-events { min-height: 0; max-height: 360px; overflow: auto; }
+.message-trace-events { min-height: 0; max-height: 360px; overflow: auto; width: 100%; }
.message-trace-empty { color: var(--muted); font-size: 12px; padding: 10px; }
.message-trace-last { color: var(--muted); font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.message-actions { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 6px 8px; border-top: 1px dashed var(--line); flex-wrap: wrap; }
@@ -163,8 +163,35 @@ button:disabled { cursor: not-allowed; opacity: 0.55; }
.message-body { line-height: 1.58; }
.message-body pre { overflow: auto; background: #f3f5f1; padding: 10px; border-radius: 6px; }
.message-meta { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
-.message-trace { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
-.message-trace-events { max-height: 260px; overflow: auto; display: grid; gap: 8px; }
+.message-trace { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; width: 100%; }
+.message-trace[data-trace-mode="all"], .message-trace[open] { width: 100%; }
+.message-trace-events { max-height: min(540px, 60dvh); overflow: auto; display: grid; gap: 8px; width: 100%; }
+
+.code-agent-summary-wrapper { display: flex; align-items: stretch; gap: 8px; min-width: 0; }
+.code-agent-summary-wrapper > .code-agent-summary { flex: 1 1 auto; min-width: 0; }
+.code-agent-summary-debug { flex: 0 0 auto; align-self: center; }
+.debug-dialog-trigger { border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); padding: 4px 10px; font-size: 12px; font-weight: 700; cursor: pointer; }
+.debug-dialog-trigger:hover, .debug-dialog-trigger:focus-visible { border-color: var(--accent); outline: 0; }
+.debug-dialog-scrim { position: fixed; inset: 0; background: rgba(15, 22, 20, 0.45); display: grid; place-items: center; z-index: 60; padding: 16px; }
+.debug-dialog { max-width: min(720px, 92vw); width: 100%; max-height: 86vh; border: 1px solid var(--line); border-radius: 10px; padding: 0; background: var(--surface); color: var(--ink); box-shadow: 0 24px 60px rgba(15, 22, 20, 0.4); overflow: hidden; }
+.debug-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--line); }
+.debug-dialog-head h3 { margin: 0; font-size: 16px; }
+.debug-dialog-close { border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); padding: 4px 10px; font-size: 12px; font-weight: 700; cursor: pointer; }
+.debug-dialog-body { display: grid; gap: 12px; padding: 12px 16px; overflow: auto; max-height: 70vh; }
+.debug-dialog-section { display: grid; gap: 6px; }
+.debug-dialog-section h4 { margin: 0; font-size: 13px; color: var(--muted); }
+.debug-dialog-empty, .debug-dialog-section-empty { margin: 0; color: var(--muted); font-size: 12px; }
+.debug-dialog-rows { display: grid; gap: 4px 12px; grid-template-columns: minmax(140px, max-content) 1fr; align-items: baseline; }
+.debug-dialog-row { display: contents; }
+.debug-dialog-key { color: var(--muted); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
+.debug-dialog-value { font-size: 12px; overflow-wrap: anywhere; }
+.debug-dialog-raw { margin: 0; max-height: 240px; overflow: auto; background: #f3f5f1; padding: 8px; border-radius: 6px; font-size: 12px; }
+.message-meta-compact { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 6px; }
+.message-meta-compact .debug-dialog-trigger { margin-left: auto; }
+.message-pending-inline { display: flex; align-items: center; gap: 8px; margin: 8px 0 0; padding: 4px 8px; background: #fbfcf8; border-radius: 6px; }
+.message-pending-inline-text { color: var(--muted); font-size: 12px; }
+.message-trace { width: 100%; }
+.message-trace-events { width: 100%; }
.command-bar { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "input send clear" "profile agent gateway"; gap: 8px; align-items: stretch; padding: 10px 14px; border-top: 1px solid var(--line); background: #f7f8f4; }
.command-bar .input-shell { grid-area: input; }
@@ -242,7 +269,8 @@ button:disabled { cursor: not-allowed; opacity: 0.55; }
.right-sidebar { grid-column: 2; grid-row: 2; padding: 10px; }
.view { padding: 8px; }
.conversation-panel { padding: 10px; }
- .command-bar { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "input send clear" "profile profile profile" "agent agent agent" "gateway gateway gateway"; align-items: stretch; }
+
+.command-bar { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "input send clear" "profile profile profile" "agent agent agent" "gateway gateway gateway"; align-items: stretch; }
.agent-timeout-control { display: none; }
.input-shell { grid-column: 1 / span 3; }
.command-button { min-height: 40px; }