fix: quiet successful Workbench runtime notices
Pipelines as Code CI / hwlab-nc01-v03-ci-poll- Success

This commit is contained in:
root
2026-07-20 12:00:25 +02:00
parent f4aaf55c48
commit e810679d9c
2 changed files with 62 additions and 15 deletions
@@ -5,6 +5,16 @@ import { describe, expect, it } from "vitest";
import WorkbenchMessageCard from "./WorkbenchMessageCard.vue";
function componentStubs() {
return {
TraceTimeline: { template: "<div />" },
LoadingState: { template: "<div />" },
MessageMarkdown: { template: "<div />" },
ApiErrorDiagnostic: { template: "<div />" },
StatusBadge: { template: "<div />" }
};
}
describe("WorkbenchMessageCard semantic retry visibility", () => {
it("shows Git mirror clone progress as runtime preparation", () => {
const wrapper = mount(WorkbenchMessageCard, {
@@ -41,11 +51,13 @@ describe("WorkbenchMessageCard semantic retry visibility", () => {
}
}
},
global: { stubs: { TraceTimeline: true, LoadingState: true, MessageMarkdown: true, ApiErrorDiagnostic: true, StatusBadge: true } }
global: { stubs: componentStubs() }
});
expect(wrapper.text()).toContain("运行环境准备:正在从 Git mirror 获取 Runner 源码");
expect(wrapper.text()).toContain("第 1/3 次源码获取进行中");
expect(wrapper.text()).not.toContain("基础设施故障");
expect(wrapper.get(".semantic-failure-banner").attributes("data-emphasis")).toBe("quiet");
expect(wrapper.find(".semantic-failure-banner code").exists()).toBe(false);
wrapper.unmount();
});
@@ -86,17 +98,12 @@ describe("WorkbenchMessageCard semantic retry visibility", () => {
}
},
global: {
stubs: {
TraceTimeline: true,
LoadingState: true,
MessageMarkdown: true,
ApiErrorDiagnostic: true,
StatusBadge: true
}
stubs: componentStubs()
}
});
const banner = wrapper.get(".semantic-failure-banner");
expect(banner.attributes("data-emphasis")).toBe("alert");
expect(banner.text()).toContain("基础设施故障:Runner 镜像仓库暂时不可达");
expect(banner.text()).toContain("第 2/5 次重试已安排");
expect(banner.text()).toMatch(/ \d+ /u);
@@ -123,7 +123,7 @@ function traceStorageKey(message: ChatMessage): string {
return `${props.storageKeyPrefix}.${message.traceId ?? message.runnerTrace?.traceId ?? message.id}`;
}
function semanticRuntimeStatusView(message: ChatMessage, currentTimeMs: number): { title: string; detail: string; tone: string; code: string | null } | null {
function semanticRuntimeStatusView(message: ChatMessage, currentTimeMs: number): { title: string; detail: string; tone: string; emphasis: "quiet" | "alert"; code: string | null } | null {
const events = Array.isArray(message.runnerTrace?.events) ? message.runnerTrace.events : [];
const event = [...events].reverse().find((item) => typeof item.failureDomain === "string" && typeof item.retryPhase === "string");
if (!event) return null;
@@ -135,10 +135,10 @@ function semanticRuntimeStatusView(message: ChatMessage, currentTimeMs: number):
const progress = attempt !== null && maxAttempts !== null ? `${attempt}/${maxAttempts}` : "本次";
const code = firstText(event.failureCode, event.code);
if (code === "git-mirror-fetch-in-progress") {
return { title: `运行环境准备:${summary}`, detail: `${progress}源码获取进行中`, tone: "info", code };
return { title: `运行环境准备:${summary}`, detail: `${progress}源码获取进行中`, tone: "info", emphasis: "quiet", code };
}
if (code === "git-mirror-fetch-completed") {
return { title: `运行环境准备:${summary}`, detail: "源码获取完成,正在启动 Runner", tone: "recovered", code };
return { title: `运行环境准备:${summary}`, detail: "源码获取完成,正在启动 Runner", tone: "recovered", emphasis: "quiet", code };
}
let detail = "正在判定有限重试";
let tone = "warning";
@@ -159,6 +159,7 @@ function semanticRuntimeStatusView(message: ChatMessage, currentTimeMs: number):
title: `${domain}${summary}`,
detail,
tone,
emphasis: "alert",
code
};
}
@@ -198,10 +199,11 @@ function finiteInteger(value: unknown): number | null {
<button v-if="detailEnabled && message.role === 'agent'" class="message-detail-button" type="button" aria-label="运行详情" title="运行详情" @click="emit('details', message)">!</button>
</div>
</header>
<section v-if="semanticRuntimeStatus" class="semantic-failure-banner" :data-tone="semanticRuntimeStatus.tone" role="status" aria-live="polite">
<section v-if="semanticRuntimeStatus" class="semantic-failure-banner" :data-tone="semanticRuntimeStatus.tone" :data-emphasis="semanticRuntimeStatus.emphasis" role="status" aria-live="polite" :title="semanticRuntimeStatus.code ? `${semanticRuntimeStatus.title} · ${semanticRuntimeStatus.detail} · ${semanticRuntimeStatus.code}` : undefined">
<span class="semantic-status-marker" aria-hidden="true" />
<strong>{{ semanticRuntimeStatus.title }}</strong>
<span>{{ semanticRuntimeStatus.detail }}</span>
<code v-if="semanticRuntimeStatus.code">{{ semanticRuntimeStatus.code }}</code>
<code v-if="semanticRuntimeStatus.code && semanticRuntimeStatus.emphasis === 'alert'">{{ semanticRuntimeStatus.code }}</code>
</section>
<TraceTimeline v-if="message.role === 'agent' && message.runnerTrace" :trace="message.runnerTrace" :auto-expanded="autoExpanded" :storage-key="traceStorageKey(message)" final-response-placement="outer" :outer-final-response-text="visibleText" />
<LoadingState v-if="isAwaitingAgentBody(message)" class="message-loading" label="思考中..." compact />
@@ -222,15 +224,53 @@ function finiteInteger(value: unknown): number | null {
padding: 0.55rem 0.7rem;
}
.semantic-status-marker {
display: none;
}
.semantic-failure-banner[data-emphasis="quiet"] {
display: flex;
align-items: center;
gap: 0.45rem;
min-width: 0;
margin: 0.25rem 0;
border-left-width: 2px;
border-radius: 0.2rem;
background: transparent;
padding: 0.2rem 0.45rem;
color: #475569;
font-size: 0.82rem;
line-height: 1.35;
}
.semantic-failure-banner[data-emphasis="quiet"] .semantic-status-marker {
display: block;
flex: 0 0 0.42rem;
width: 0.42rem;
height: 0.42rem;
border-radius: 50%;
background: currentColor;
}
.semantic-failure-banner[data-emphasis="quiet"] strong {
overflow: hidden;
min-width: 0;
text-overflow: ellipsis;
white-space: nowrap;
}
.semantic-failure-banner[data-emphasis="quiet"] span:not(.semantic-status-marker) {
flex: 0 0 auto;
color: #64748b;
}
.semantic-failure-banner[data-tone="recovered"] {
border-left-color: #15803d;
background: #f0fdf4;
color: #166534;
}
.semantic-failure-banner[data-tone="info"] {
border-left-color: #2563eb;
background: #eff6ff;
color: #1e40af;
}