Merge pull request #455 from pikasTech/fix/hwlab-450-trace-preview

修复 Code Agent trace 预览计数展示
This commit is contained in:
Lyon
2026-05-24 18:24:02 +08:00
committed by GitHub
5 changed files with 248 additions and 17 deletions
+101 -8
View File
@@ -618,6 +618,11 @@ function runStaticSmoke() {
evidence: ["DEFAULT_API_TIMEOUT_MS=4500 for light probes", `DEFAULT_CODE_AGENT_TIMEOUT_MS=${codeAgentLongTimeoutMs}`, "sendAgentMessage passes timeoutMs"]
});
addCheck(checks, blockers, "code-agent-trace-replay-disclosure", hasCodeAgentTraceReplayDisclosure(files), "Trace replay panels disclose tail preview counts and provide full UI/JSON/download access for long traces.", {
blocker: "observability_blocker",
evidence: ["显示最近 14 / 总计 N", "展开全部", "复制 JSON", "下载 trace", "internal scroll for full trace"]
});
addCheck(checks, blockers, "code-agent-provider-readiness-visibility", hasCodeAgentReadinessVisibility(files), "Workbench shows provider/stdio blockers without exposing credential internals and only long-lived Codex stdio replies can become full Code Agent completion.", {
blocker: "observability_blocker",
evidence: ["服务受阻 or legacy BLOCKED 凭证缺口", "provider_unavailable classifier", "completed -> dev-live guard", "default workspace hides credential internals"]
@@ -2618,6 +2623,28 @@ function hasCodeAgentLongTimeoutContract(files) {
);
}
function hasCodeAgentTraceReplayDisclosure({ app, styles }) {
const tracePanelBody = functionBody(app, "messageTracePanel");
const traceToolbarBody = functionBody(app, "messageTraceToolbar");
return (
/CODE_AGENT_TRACE_PREVIEW_LIMIT\s*=\s*14/u.test(app) &&
/function\s+messageTraceCountText\s*\(/u.test(app) &&
/显示最近\s+\$\{CODE_AGENT_TRACE_PREVIEW_LIMIT\}\s+\/\s+总计\s+\$\{total\}/u.test(app) &&
/function\s+renderTraceEventList\s*\(/u.test(app) &&
/tracePreviewEvents\(events\)/u.test(tracePanelBody) &&
/messageTraceToolbar\(message,\s*trace,\s*events,\s*list\)/u.test(tracePanelBody) &&
/展开全部/u.test(traceToolbarBody) &&
/复制 JSON/u.test(traceToolbarBody) &&
/下载 trace/u.test(traceToolbarBody) &&
/function\s+messageTraceJson\s*\(/u.test(app) &&
/function\s+downloadTraceJson\s*\(/u.test(app) &&
/\.message-trace-toolbar\s*\{/u.test(styles) &&
/\.message-trace-count\s*\{/u.test(styles) &&
/\.message-trace-action\s*\{/u.test(styles) &&
/\.message-trace-events-full\s*\{[\s\S]*?max-height:\s*min\(360px,\s*42vh\)[\s\S]*?overflow:\s*auto/u.test(styles)
);
}
function hasCodeAgentStatusSummaryContract({ html, app, styles, codeAgentStatus }) {
return (
/id=["']code-agent-summary["']/u.test(html) &&
@@ -4289,6 +4316,23 @@ async function inspectJourneyUi(page) {
const runningActions = summarizeActions("status-running");
const failedActions = summarizeActions("status-failed");
const timeoutActions = summarizeActions("status-timeout");
const tracePanels = [...document.querySelectorAll(".message-card.message-agent .message-trace")];
const tracePanelMetrics = tracePanels.map((panel) => {
const countText = panel.querySelector(".message-trace-count")?.textContent?.replace(/\s+/gu, " ").trim() ?? "";
const actions = [...panel.querySelectorAll(".message-trace-action")].map((button) => button.textContent?.replace(/\s+/gu, " ").trim() ?? "");
const events = [...panel.querySelectorAll(".message-trace-events li")].map((item) => item.textContent?.replace(/\s+/gu, " ").trim() ?? "");
return {
countText,
actions,
eventCount: events.length,
traceMode: panel.querySelector(".message-trace-events")?.dataset.traceMode ?? "",
tailPreviewVisible: /显示最近\s+14\s*\/\s*总计\s+(?:1[5-9]|[2-9]\d+)/u.test(countText),
fullEntryVisible: actions.includes("展开全部"),
copyJsonVisible: actions.includes("复制 JSON"),
downloadTraceVisible: actions.includes("下载 trace"),
cappedTailCount: events.length <= 14
};
});
return {
title: document.title,
workspaceHidden: document.querySelector('[data-view="workspace"]')?.hidden ?? null,
@@ -4331,16 +4375,24 @@ async function inspectJourneyUi(page) {
[...document.querySelectorAll(".message-card.status-source .message-meta")]
.some((element) => /SOURCE|source-fixture|trc_/u.test(element.textContent ?? ""))
),
traceText: [...document.querySelectorAll(".message-card.message-agent .message-trace")]
traceText: tracePanels
.map((element) => element.textContent ?? "")
.join("\n"),
traceHasTraceId: [...document.querySelectorAll(".message-card.message-agent .message-trace")]
traceHasTraceId: tracePanels
.some((element) => /trc_/u.test(element.textContent ?? "")),
traceShowsEvents: [...document.querySelectorAll(".message-card.message-agent .message-trace-events li")]
.some((element) => /#\d+|session|prompt|tool|assistant|timeout|blocked/u.test(element.textContent ?? "")),
tracePanelMetrics,
traceLongPreviewDisclosureVisible: tracePanelMetrics.some((metric) => metric.tailPreviewVisible),
traceFullViewEntryVisible: tracePanelMetrics.some((metric) => metric.fullEntryVisible),
traceCopyJsonVisible: tracePanelMetrics.some((metric) => metric.copyJsonVisible),
traceDownloadVisible: tracePanelMetrics.some((metric) => metric.downloadTraceVisible),
tracePreviewTailCapped: tracePanelMetrics
.filter((metric) => metric.tailPreviewVisible)
.every((metric) => metric.cappedTailCount && metric.traceMode === "tail"),
noMainAttributionNoise: document.querySelectorAll(".message-card.message-agent .message-attribution, .message-card.message-agent .message-evidence, .message-card.message-agent .code-agent-facts").length === 0,
attributionFallbackNotRunnerControl: !/openai-responses-fallback[\s\S]{0,80}(真实 runner|Codex stdio 长会话|workspace-write)/u.test(
[...document.querySelectorAll(".message-card.message-agent .message-trace")]
tracePanels
.map((element) => element.textContent ?? "")
.join("\n")
),
@@ -4831,6 +4883,11 @@ async function runLocalAgentFixtureSmoke({
ui.sourceMessageHasNonSensitiveMeta &&
ui.traceHasTraceId &&
ui.traceShowsEvents &&
ui.traceLongPreviewDisclosureVisible &&
ui.tracePreviewTailCapped &&
ui.traceFullViewEntryVisible &&
ui.traceCopyJsonVisible &&
ui.traceDownloadVisible &&
ui.noMainAttributionNoise &&
ui.attributionFallbackNotRunnerControl &&
ui.conversationFactsVisible &&
@@ -5547,6 +5604,44 @@ async function inspectQuickPromptGeometry(page, viewport) {
}, viewport);
}
function longSourceFixtureTraceEvents(traceId, count = 18) {
const labels = [
"source-fixture:session",
"prompt:queued",
"prompt:sent",
"context:loaded",
"session:reused",
"assistant:started",
"tool:skills.inspect",
"tool:skills.inspect:completed",
"tool:workspace.read",
"tool:workspace.read:completed",
"assistant:delta",
"assistant:delta",
"assistant:delta",
"assistant:delta",
"assistant:finalizing",
"message:stored",
"conversation:facts-updated",
"assistant:completed"
];
return Array.from({ length: count }, (_, index) => {
const seq = index + 1;
const label = labels[index] ?? `assistant:event:${seq}`;
const toolName = label.startsWith("tool:") ? label.split(":").slice(1).join(":") : undefined;
return {
seq,
traceId,
type: label.split(":")[0] || "event",
stage: label.split(":")[1] || "observed",
status: seq === count ? "completed" : "observed",
label,
...(toolName ? { toolName } : {}),
elapsedMs: seq * 37
};
});
}
async function startStaticWebServer(options = {}) {
const rootDir = path.resolve(options.rootDir ?? webRoot);
const authFixtureSessions = new Set();
@@ -6609,6 +6704,7 @@ async function handleLocalAgentFixtureApi({ request, response, url, options = {}
items: [],
blockers: []
};
const fixtureTraceEvents = longSourceFixtureTraceEvents(traceId);
jsonResponse(response, 200, {
conversationId,
sessionId: conversationId,
@@ -6637,11 +6733,8 @@ async function handleLocalAgentFixtureApi({ request, response, url, options = {}
runnerTrace: {
traceId,
runnerKind: "openai-responses-fallback",
events: [
{ seq: 1, traceId, type: "session", stage: "fixture", status: "completed", label: "source-fixture:session" },
{ seq: 2, traceId, type: "assistant", stage: "completed", status: "completed", label: "assistant:completed" }
],
lastEvent: { seq: 2, traceId, type: "assistant", stage: "completed", status: "completed", label: "assistant:completed" }
events: fixtureTraceEvents,
lastEvent: fixtureTraceEvents.at(-1)
},
conversationFacts: {
conversationId,