fix(runner): polish live browser diagnostics

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-07 21:31:46 +00:00
1 parent 7e0c8632da
commit 90daf4ff00
12 files changed
+192 -20

No files matched your search

@@ -84,6 +84,27 @@ function terminalTone(snapshot: SessionSnapshot) {
return "neutral" as const;
}
function humanizeProtocolLabel(value: string): string {
const words = value.replaceAll("_", " ");
return words.length === 0 ? words : `${words[0]?.toUpperCase()}${words.slice(1)}`;
}
function liveStatusTone(status: LiveStatus, snapshot: SessionSnapshot | null) {
if (status === "error") {
return "danger" as const;
}
if (status !== "terminal") {
return "neutral" as const;
}
if (snapshot?.terminal?.runTerminalState === "succeeded") {
return "success" as const;
}
if (snapshot?.terminal?.runTerminalState === "failed") {
return "danger" as const;
}
return "neutral" as const;
}
function SnapshotSummary({
snapshot,
label = "Session snapshot",
@@ -99,9 +120,11 @@ function SnapshotSummary({
<h2>{snapshot.fixtureName}</h2>
</div>
<Badge tone={terminalTone(snapshot)} data-testid="terminal-badge">
{snapshot.integrity === "gap_detected"
? "Gap detected"
: (snapshot.terminal?.runTerminalState ?? snapshot.runPhase)}
{humanizeProtocolLabel(
snapshot.integrity === "gap_detected"
? "Gap detected"
: (snapshot.terminal?.runTerminalState ?? snapshot.runPhase),
)}
</Badge>
</div>
@@ -126,7 +149,9 @@ function SnapshotSummary({
{snapshot.proposedResult ? (
<div className="result-summary" data-testid="result-summary">
<span>Reported {snapshot.proposedResult.reportedWorkDisposition}</span>
<span>
Reported {humanizeProtocolLabel(snapshot.proposedResult.reportedWorkDisposition)}
</span>
<p>{snapshot.proposedResult.summary}</p>
</div>
) : null}
@@ -366,10 +391,10 @@ function LiveRunner() {
<div className="live-status" aria-live="polite">
<span>Runner status</span>
<Badge
tone={status === "error" ? "danger" : status === "terminal" ? "success" : "neutral"}
tone={liveStatusTone(status, snapshot)}
data-testid="live-status"
>
{status}
{humanizeProtocolLabel(status)}
</Badge>
</div>
@@ -8,8 +8,9 @@ test("validates and renders the shared Phase 1 fixture", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: "Static replay" }).click();
await expect(page.getByRole("heading", { name: "Live local runner" })).toBeVisible();
await expect(page.getByTestId("terminal-badge")).toHaveText("succeeded");
await expect(page.getByTestId("terminal-badge")).toHaveText("Succeeded");
await expect(page.getByTestId("timeline").getByRole("listitem")).toHaveCount(9);
await expect(page.getByTestId("timeline")).not.toContainText("workspace_preparing");
await expect(page.getByTestId("result-summary")).toContainText(
"The scripted run completed successfully.",
);
@@ -34,8 +35,8 @@ test("shows duplicate and unsupported-version replay states", async ({ page }) =
test("streams a live run and proves replay parity", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: "Start local run" }).click();
await expect(page.getByTestId("live-status")).toHaveText("terminal");
await expect(page.getByTestId("terminal-badge")).toHaveText("succeeded");
await expect(page.getByTestId("live-status")).toHaveText("Terminal");
await expect(page.getByTestId("terminal-badge")).toHaveText("Succeeded");
await expect(page.getByTestId("process-facts")).toContainText("Harness process exit");
await expect(page.getByTestId("process-facts")).toContainText("done");
await expect(page.getByTestId("parity-result")).toContainText("Match");
@@ -50,15 +51,26 @@ test("resolves live permission and input requests", async ({ page }) => {
await page.getByLabel("Scenario").selectOption("permission-input");
await page.getByRole("button", { name: "Start local run" }).click();
await expect(page.getByTestId("runtime-request")).toContainText("permission request");
await expect(page.getByTestId("terminal-badge")).toHaveText("Executing");
await expect(
page.getByTestId("timeline").getByRole("listitem").filter({
hasText: "runtime_request.created",
}).first(),
).toContainText("permission: Allow the fake driver to write its local fixture?");
await page.screenshot({
path: resolve(packageRoot, "knowledge/evidence/phase-02-live-permission.png"),
fullPage: true,
});
await page.getByRole("button", { name: "Allow" }).click();
await expect(page.getByTestId("runtime-request")).toContainText("input request");
await expect(
page.getByTestId("timeline").getByRole("listitem").filter({
hasText: "runtime_request.resolved",
}).first(),
).toContainText("Resolved permission: Allow the fake driver to write its local fixture?");
await page.getByLabel("Response").fill("phase-02-browser-trace");
await page.getByRole("button", { name: "Send input" }).click();
await expect(page.getByTestId("live-status")).toHaveText("terminal");
await expect(page.getByTestId("live-status")).toHaveText("Terminal");
await expect(page.getByTestId("parity-result")).toContainText("Match");
});
@@ -69,11 +81,34 @@ test("interrupts a live turn without duplicating terminal state", async ({ page
const interrupt = page.getByRole("button", { name: "Interrupt turn" });
await expect(interrupt).toBeEnabled();
await interrupt.click();
await expect(page.getByTestId("live-status")).toHaveText("terminal");
await expect(page.getByTestId("terminal-badge")).toHaveText("cancelled");
await expect(page.getByTestId("live-status")).toHaveText("Terminal");
await expect(page.getByTestId("terminal-badge")).toHaveText("Cancelled");
await expect(page.getByTestId("live-status")).toHaveAttribute("data-tone", "neutral");
await expect(page.getByTestId("terminal-badge")).toHaveAttribute("data-tone", "neutral");
await expect(page.getByTestId("timeline").getByText("run.terminal")).toHaveCount(1);
await page.screenshot({
path: resolve(packageRoot, "knowledge/evidence/phase-02-live-interrupted.png"),
fullPage: true,
});
});
test("shows failed outcomes as danger and names the duplicate-terminal guard", async ({ page }) => {
await page.goto("/");
await page.getByLabel("Scenario").selectOption("error");
await page.getByRole("button", { name: "Start local run" }).click();
await expect(page.getByTestId("live-status")).toHaveText("Terminal");
await expect(page.getByTestId("live-status")).toHaveAttribute("data-tone", "danger");
await expect(page.getByTestId("terminal-badge")).toHaveText("Failed");
await expect(page.getByTestId("terminal-badge")).toHaveAttribute("data-tone", "danger");
await page.getByLabel("Scenario").selectOption("duplicate-terminal");
await page.getByRole("button", { name: "Start local run" }).click();
await expect(page.getByTestId("live-status")).toHaveText("Terminal");
await expect(
page.getByTestId("timeline").getByRole("listitem").filter({
hasText: "harness.diagnostic",
}),
).toContainText(
"Duplicate terminal event ignored; the first terminal event remains authoritative.",
);
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 120 KiB

@@ -75,7 +75,7 @@
"sourceSeq": 1,
"eventType": "runtime.phase.changed",
"emittedAt": "2026-08-07T12:00:00.030Z",
"summary": "Phase changed to workspace_preparing"
"summary": "Phase changed to workspace preparing"
},
{
"position": 2,
@@ -34,6 +34,7 @@
{
"requestId": "request_interrupted_permission",
"requestKind": "runtime",
"type": "permission",
"status": "pending",
"prompt": "Allow the fake command?"
}
@@ -92,7 +93,7 @@
"sourceSeq": 3,
"eventType": "runtime_request.created",
"emittedAt": "2026-08-07T12:20:00.040Z",
"summary": "runtime_request created"
"summary": "permission: Allow the fake command?"
},
{
"position": 4,
@@ -775,7 +775,10 @@ impl RunnerState {
} else {
self.emit_event(
"harness.diagnostic",
json!({ "code": "duplicate_semantic_result_ignored" }),
json!({
"code": "duplicate_semantic_result_ignored",
"message": "Duplicate semantic result ignored; the first result remains authoritative."
}),
None,
None,
)?;
@@ -791,7 +794,10 @@ impl RunnerState {
} else {
self.emit_event(
"harness.diagnostic",
json!({ "code": "duplicate_terminal_ignored" }),
json!({
"code": "duplicate_terminal_ignored",
"message": "Duplicate terminal event ignored; the first terminal event remains authoritative."
}),
None,
None,
)?;
@@ -1020,6 +1026,12 @@ pub fn run_fake_harness(
None,
json!({ "driverSessionId": "driver_phase2_fake" }),
)?;
output.send_event(
"runtime.phase.changed",
None,
None,
json!({ "phase": "executing" }),
)?;
let turn = receive_harness_command(&command_receiver, "turn.start", None)?;
let turn_id = turn
.payload
@@ -20,6 +20,14 @@ describe.sequential("Phase 2 local runner and fake harness", () => {
expect(eventTypes).toContain("harness.ready");
expect(eventTypes).toContain("item.completed");
const sessionStartedIndex = eventTypes.indexOf("session.started");
const executingIndex = trace.events.findIndex(
(event) =>
event.eventType === "runtime.phase.changed" &&
(event.payload as Record<string, unknown>).phase === "executing",
);
expect(executingIndex).toBeGreaterThan(sessionStartedIndex);
expect(executingIndex).toBeLessThan(eventTypes.indexOf("turn.started"));
expect(trace.events.some((event) => event.itemId === "item_phase2_file")).toBe(true);
expect(trace.result?.reportedWorkDisposition).toBe("done");
expect(trace.harnessProcessExit).toEqual({ exitCode: 0, success: true, signal: null });
@@ -99,6 +107,13 @@ describe.sequential("Phase 2 local runner and fake harness", () => {
"duplicate_terminal_ignored",
),
).toBe(true);
expect(
replayPhase2Events(trace, trace.events).timeline.find(
(event) => event.eventType === "harness.diagnostic",
)?.summary,
).toBe(
"Duplicate terminal event ignored; the first terminal event remains authoritative.",
);
});
it("keeps an error process exit distinct from its semantic result", async () => {
@@ -2,8 +2,13 @@ import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
import { parsePrpFixtureText, type PrpFixture } from "../protocol/phase1-contract.js";
import {
parsePrpFixtureText,
type PrpEvent,
type PrpFixture,
} from "../protocol/phase1-contract.js";
import {
createSessionSnapshot,
reducePrpFixture,
reduceSessionEvents,
type SessionSnapshot,
@@ -70,4 +75,34 @@ describe("deterministic PRP session reducer", () => {
},
]);
});
it("summarizes runtime request creation and resolution with type and prompt", async () => {
const fixture = await loadFixture("interrupted-run");
const fixtureRequest = fixture.events.find(
(event) => event.eventType === "runtime_request.created",
);
if (fixtureRequest === undefined) {
throw new Error("interrupted-run fixture must create a runtime request");
}
const created: PrpEvent = {
...fixtureRequest,
sourceEventId: "event_request_summary_01",
sourceSeq: 1,
};
const resolved: PrpEvent = {
...fixtureRequest,
sourceEventId: "event_request_summary_02",
sourceSeq: 2,
eventType: "runtime_request.resolved",
payload: { requestId: "request_interrupted_permission" },
};
const snapshot = reduceSessionEvents(createSessionSnapshot(fixture), [created, resolved]);
expect(snapshot.requests[0]).toMatchObject({ type: "permission" });
expect(snapshot.timeline.map((entry) => entry.summary)).toEqual([
"permission: Allow the fake command?",
"Resolved permission: Allow the fake command?",
]);
});
});
@@ -27,6 +27,7 @@ export interface SessionItemSnapshot {
export interface SessionRequestSnapshot {
requestId: string;
requestKind: string;
type: string;
status: string;
prompt: string;
}
@@ -80,10 +81,23 @@ function sourceKey(event: PrpEvent): string {
return `${event.sourceKind}:${event.sourceInstanceId}`;
}
function timelineSummary(event: PrpEvent): string {
function humanizeProtocolValue(value: string): string {
return value.replaceAll("_", " ");
}
function sentenceCase(value: string): string {
return value.length === 0 ? value : `${value[0]?.toUpperCase()}${value.slice(1)}`;
}
function requestSummary(type: string, prompt: string, action?: string): string {
const detail = prompt.length > 0 ? `${type}: ${prompt}` : `${type} request`;
return action === undefined ? detail : `${action} ${detail}`;
}
function timelineSummary(snapshot: SessionSnapshot, event: PrpEvent): string {
const payload = record(event.payload);
if (event.eventType === "runtime.phase.changed") {
return `Phase changed to ${stringValue(payload.phase, "unknown")}`;
return `Phase changed to ${humanizeProtocolValue(stringValue(payload.phase, "unknown"))}`;
}
if (event.eventType === "item.started") {
return `Started ${stringValue(payload.kind, "item")}`;
@@ -103,6 +117,40 @@ function timelineSummary(event: PrpEvent): string {
if (event.eventType === "run.terminal") {
return `Run ${stringValue(payload.runTerminalState, "terminal")}`;
}
if (
event.eventType === "runner.diagnostic" ||
event.eventType === "harness.diagnostic"
) {
const message = stringValue(payload.message);
if (message.length > 0) {
return message;
}
const code = stringValue(payload.code);
if (code.length > 0) {
return sentenceCase(humanizeProtocolValue(code));
}
}
if (event.eventType === "runtime_request.created") {
const request = record(payload.request ?? payload);
return requestSummary(
stringValue(request.type, "runtime"),
stringValue(request.prompt),
);
}
if (
event.eventType === "runtime_request.resolved" ||
event.eventType === "runtime_request.expired" ||
event.eventType === "runtime_request.cancelled"
) {
const requestId = stringValue(payload.requestId);
const request = snapshot.requests.find((candidate) => candidate.requestId === requestId);
const action = sentenceCase(
humanizeProtocolValue(event.eventType.split(".").at(-1) ?? "updated"),
);
return request === undefined
? `${action} runtime request ${requestId}`.trim()
: requestSummary(request.type, request.prompt, action);
}
return event.eventType.replaceAll(".", " ");
}
@@ -191,6 +239,7 @@ function applyProjection(snapshot: SessionSnapshot, event: PrpEvent): void {
snapshot.requests.push({
requestId,
requestKind: stringValue(request.requestKind, "runtime"),
type: stringValue(request.type, "runtime"),
status: stringValue(request.status, "pending"),
prompt: stringValue(request.prompt),
});
@@ -307,7 +356,7 @@ export function applyPrpEvent(
eventType: event.eventType,
emittedAt: event.emittedAt,
...(event.itemId === undefined ? {} : { itemId: event.itemId }),
summary: timelineSummary(event),
summary: timelineSummary(snapshot, event),
});
applyProjection(snapshot, event);
snapshot.integrity =