= {
queued: "default-outline",
delivered: "default-outline",
diff --git a/web/src/components/alerts/rca/RcaThreadStep.vue b/web/src/components/alerts/rca/RcaThreadStep.vue
index 66081b37d3..c5b87c0145 100644
--- a/web/src/components/alerts/rca/RcaThreadStep.vue
+++ b/web/src/components/alerts/rca/RcaThreadStep.vue
@@ -44,7 +44,7 @@ along with this program. If not, see .
@@ -151,10 +151,12 @@ const props = withDefaults(
runLive: boolean;
/** The live run is paused for an answer, so its unfinished step is not working. */
runWaiting?: boolean;
+ /** The step counts as finished; an ended run's step that Resume repeats does not. */
+ finished?: boolean;
/** Evidence id → entry number from the run's summary; null when no summary is loaded, so chips show the raw id. */
evidenceNumbers?: Map | null;
}>(),
- { runWaiting: false, evidenceNumbers: null },
+ { runWaiting: false, finished: true, evidenceNumbers: null },
);
defineEmits<{
@@ -166,13 +168,13 @@ const { t } = useI18nTyped();
const callsListId = useId();
-/** running: the live step; waiting: paused for an answer; stopped: the run's end cut it off; warn: a tool failed. */
+/** running: the live step; waiting: paused for an answer; stopped: the step did not finish; warn: a tool failed. */
const status = computed<"running" | "waiting" | "stopped" | "warn" | "ok">(() => {
if (props.step.ended_at === null) {
if (!props.runLive) return "stopped";
return props.runWaiting ? "waiting" : "running";
}
- if (isInterruptedStep(props.step)) return "stopped";
+ if (!props.finished || isInterruptedStep(props.step)) return "stopped";
return props.step.tool_calls.some((call) => call.status === "error") ? "warn" : "ok";
});
diff --git a/web/src/components/alerts/rca/RcaVerdictCard.spec.ts b/web/src/components/alerts/rca/RcaVerdictCard.spec.ts
index 7584a085c6..4509172fb9 100644
--- a/web/src/components/alerts/rca/RcaVerdictCard.spec.ts
+++ b/web/src/components/alerts/rca/RcaVerdictCard.spec.ts
@@ -592,6 +592,28 @@ describe("RcaVerdictCard", () => {
expect(byTest(single, "rca-verdict-causes").exists()).toBe(false);
});
+ it("agent inline code shows as code, never as backticks, in the headline, causes and impact", () => {
+ const base = rcaRunSummary().card;
+ const card = {
+ ...base,
+ outcome: "multiple_root_causes" as const,
+ headline: "The `adFailure` flag was toggled on",
+ causes: [
+ { id: "h1", statement: "frontend `GET /api/data` failed", band: "medium" as const },
+ ],
+ impact: { ...base.impact!, summary: "`frontend` 5xx" },
+ };
+ const wrapper = mountCard({ cardState: "analyzed", summary: rcaRunSummary({ card }) });
+
+ const headline = byTest(wrapper, "rca-verdict-headline");
+ expect(headline.text()).toBe("The adFailure flag was toggled on");
+ expect(headline.find("code").text()).toBe("adFailure");
+ const cause = byTest(wrapper, "rca-verdict-cause");
+ expect(cause.text()).not.toContain("`");
+ expect(cause.find("code").text()).toBe("GET /api/data");
+ expect(byTest(wrapper, "rca-verdict-impact").find("code").text()).toBe("frontend");
+ });
+
it("C: Inconclusive, 'Suspected, not confirmed' with the subject statement, band Low, '(diff not read)' (VE17, VE35)", () => {
const wrapper = mountCard({ cardState: "analyzed", summary: rcaRunSummaryInconclusive() });
diff --git a/web/src/components/alerts/rca/RcaVerdictCard.vue b/web/src/components/alerts/rca/RcaVerdictCard.vue
index 2d35bcdde9..bb36a6eea6 100644
--- a/web/src/components/alerts/rca/RcaVerdictCard.vue
+++ b/web/src/components/alerts/rca/RcaVerdictCard.vue
@@ -50,7 +50,7 @@ along with this program. If not, see .
data-test="rca-verdict-headline"
class="text-text-heading m-0 max-w-215 text-2xl leading-tight font-bold [text-wrap:pretty]"
>
- {{ headline }}
+
.
data-test="rca-verdict-below"
class="text-text-secondary text-compact m-0 font-semibold"
>
- {{ belowHeadline }}
+
@@ -69,7 +69,9 @@ along with this program. If not, see .
class="m-0 flex max-w-225 list-none flex-col gap-1 p-0"
>
- {{ cause.statement }}
+
+
+
· {{ causeMeta(cause) }}
@@ -182,7 +184,7 @@ along with this program. If not, see .
{{ t("alerts.incidents.rcaSummary.impact") }}
- {{ impactText }}
+
@@ -465,6 +467,7 @@ import type { RcaCardState } from "@/utils/incidents/rcaThread";
import type { RcaUnblockedLine } from "@/utils/incidents/rcaSteering";
import type { RcaRefusal } from "@/composables/useIncidentRca";
import RcaBlockedBanner from "./RcaBlockedBanner.vue";
+import RcaInlineText from "./RcaInlineText.vue";
import RcaPill from "./RcaPill.vue";
import RcaStageStepper from "./RcaStageStepper.vue";
diff --git a/web/src/components/alerts/rca/RcaWhatHappened.spec.ts b/web/src/components/alerts/rca/RcaWhatHappened.spec.ts
index 90cf5cc1a8..2b40e29067 100644
--- a/web/src/components/alerts/rca/RcaWhatHappened.spec.ts
+++ b/web/src/components/alerts/rca/RcaWhatHappened.spec.ts
@@ -73,6 +73,18 @@ describe("RcaWhatHappened (03 VE31, VE36, VE-D4)", () => {
).toEqual([["1"], ["2"], []]);
});
+ it("agent marks in an entry show as code and bold, never as raw markers", () => {
+ const [first] = rcaRunSummary().timeline;
+ const wrapper = mountTimeline(
+ rcaRunSummary({ timeline: [{ ...first, text: "The `adFailure` flag turns **on**" }] }),
+ );
+
+ const row = rows(wrapper)[0];
+ expect(row.text()).toContain("The adFailure flag turns on");
+ expect(row.find("code").text()).toBe("adFailure");
+ expect(row.find("strong").text()).toBe("on");
+ });
+
it("VE31 markers: Confirmed has no tag; likely reads Likely; Unverified and Refuted tag their rows", () => {
const wrapper = mountTimeline(
rcaRunSummary({
diff --git a/web/src/components/alerts/rca/RcaWhatHappened.vue b/web/src/components/alerts/rca/RcaWhatHappened.vue
index a1ea394b08..58a40ab069 100644
--- a/web/src/components/alerts/rca/RcaWhatHappened.vue
+++ b/web/src/components/alerts/rca/RcaWhatHappened.vue
@@ -74,7 +74,7 @@ along with this program. If not, see .
: 'text-text-heading'
"
>
- {{ entry.text }}
+
diff --git a/web/src/utils/incidents/rcaThread.ts b/web/src/utils/incidents/rcaThread.ts
index 856d2f6990..df6d5334b4 100644
--- a/web/src/utils/incidents/rcaThread.ts
+++ b/web/src/utils/incidents/rcaThread.ts
@@ -158,16 +158,17 @@ export const toolCallsDurationMs = (calls: RcaToolCall[]): number =>
export const isInterruptedStep = (step: RcaStep): boolean =>
step.tool_calls.some((call) => call.interrupted === true);
+/** A finished, uninterrupted step; an ended run finishes only the steps a Resume keeps. */
+export function isFinishedStep(thread: RcaThread, step: RcaStep, idx: number): boolean {
+ if (step.ended_at === null || isInterruptedStep(step)) return false;
+ if (thread.active) return true;
+ if (thread.reusable_steps !== undefined) return idx < thread.reusable_steps;
+ return thread.report_submitted || idx < thread.steps.length - 1;
+}
+
/** Finished, uninterrupted steps; an ended run counts the steps a Resume keeps. */
export function finishedStepCount(thread: RcaThread): number {
- if (!thread.active && thread.reusable_steps !== undefined) return thread.reusable_steps;
- const last = thread.steps.length - 1;
- return thread.steps.filter(
- (step, idx) =>
- step.ended_at !== null &&
- !isInterruptedStep(step) &&
- !(idx === last && !thread.active && !thread.report_submitted),
- ).length;
+ return thread.steps.filter((step, idx) => isFinishedStep(thread, step, idx)).length;
}
/** Status text of a tool row: "OK", "Error" or "Running". */