test(agents): cover transcript render-decision helpers + sweep dead branches

The activity-feed UI components claimed coverage "for grouping,
presentation, and tool summaries" but the render-decision logic wiring
those helpers into the rendered components had none. The node test
harness only strips .ts (no JSX/DOM), so this extracts the pure
decision logic out of the .tsx components into sibling .ts modules and
tests it directly — matching the existing helper-test convention.

- AgentSessionToolItem.tsx: extract isInlineImageData (the data:image/
  passthrough guard), getToolDurationDisplay/formatDurationMs (duration
  fallback chain), and parseToolResultValue (JSON double-parse) into
  agentSessionUtils.ts. resolveImageSrc stays in the component (Tauri
  dep) but is now built on the testable isInlineImageData predicate.
- ManagedAgentSessionPanel.tsx: extract scopeByChannel,
  deriveLatestSessionId, and resolveRawRailLayout (the raw-ACP toggle
  decision) into agentSessionPanelLayout.ts.
- describeRawEvent (raw-view labels) gets direct coverage.

New tests: agentSessionToolItemHelpers.test.mjs, rawEventRail.test.mjs,
agentSessionPanelLayout.test.mjs (32 cases). Total suite 766 -> 798.

Also sweeps the two parked dead branches:
- agentSessionTranscriptGrouping.ts: the isSetupLifecycle guard in the
  activity loop can never fire (setup items are already in `consumed`
  and filtered out of `activity`).
- agentSessionToolSummary.ts: the `base === "shell"` ternary inside the
  DEVELOPER_TOOL_BASES branch is unreachable (shell returns earlier);
  simplified to return "dev_mcp".

Gates: biome check, tsc, vite build, 798 unit tests all green.
This commit is contained in:
Taylor Ho
2026-06-15 20:12:15 -07:00
parent c0aa2564ad
commit ab29e2c086
9 changed files with 415 additions and 91 deletions
@@ -7,7 +7,11 @@ import { rewriteRelayUrl } from "@/shared/lib/mediaUrl";
import type { TranscriptItem } from "./agentSessionTypes";
import { getBuzzToolInfo } from "./agentSessionToolCatalog";
import { buildCompactToolSummary } from "./agentSessionToolSummary";
import { asRecord, formatCodeValue, formatDuration } from "./agentSessionUtils";
import {
formatCodeValue,
getToolDurationDisplay,
isInlineImageData,
} from "./agentSessionUtils";
export function ToolItem({
item,
@@ -20,7 +24,7 @@ export function ToolItem({
const canonicalToolName = item.buzzToolName ?? item.toolName;
const buzzTool = getBuzzToolInfo(canonicalToolName);
const compactSummary = buildCompactToolSummary(item);
const duration = getToolDuration(item);
const duration = getToolDurationDisplay(item);
const handleToggle = React.useCallback(
(event: React.SyntheticEvent<HTMLDetailsElement>) => {
setIsExpanded(event.currentTarget.open);
@@ -88,6 +92,10 @@ function compactSummaryTone() {
return "text-muted-foreground/60 group-open:text-muted-foreground";
}
function resolveImageSrc(source: string): string {
return isInlineImageData(source) ? source : rewriteRelayUrl(source);
}
function CompactToolSummaryRow({
duration,
label,
@@ -188,13 +196,6 @@ function CompactMessageSummary({
);
}
function resolveImageSrc(source: string): string {
if (source.startsWith("data:image/")) {
return source;
}
return rewriteRelayUrl(source);
}
function ViewImageToolPreview({
src,
title,
@@ -294,48 +295,6 @@ function ImageLightbox({
);
}
function getToolDuration(item: Extract<TranscriptItem, { type: "tool" }>) {
if (item.startedAt && item.completedAt) {
return formatDuration(item.startedAt, item.completedAt);
}
const resultRecord = asRecord(parseToolResultValue(item.result));
const durationMs =
getToolNumber(resultRecord, ["duration_ms", "durationMs"]) ??
getToolNumber(resultRecord, ["elapsed_ms", "elapsedMs"]);
return durationMs == null ? null : formatDurationMs(durationMs);
}
function getToolNumber(
record: Record<string, unknown>,
keys: string[],
): number | null {
for (const key of keys) {
const value = record[key];
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
}
return null;
}
function formatDurationMs(ms: number) {
if (ms < 0) return null;
const totalSeconds = ms / 1000;
if (totalSeconds < 60) {
return totalSeconds < 10
? `${totalSeconds.toFixed(1)}s`
: `${Math.round(totalSeconds)}s`;
}
let minutes = Math.floor(totalSeconds / 60);
let seconds = Math.round(totalSeconds % 60);
if (seconds === 60) {
minutes += 1;
seconds = 0;
}
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
}
function ToolDetailBlocks({
args,
description,
@@ -416,20 +375,3 @@ function ToolCodeBlock({
</div>
);
}
function parseToolResultValue(result: string): unknown {
const trimmed = result.trim();
if (!trimmed) return null;
try {
const parsed = JSON.parse(trimmed);
if (typeof parsed !== "string") return parsed;
try {
return JSON.parse(parsed);
} catch {
return parsed;
}
} catch {
return null;
}
}
@@ -21,6 +21,11 @@ import type {
ObserverEvent,
TranscriptItem,
} from "./agentSessionTypes";
import {
deriveLatestSessionId,
resolveRawRailLayout,
scopeByChannel,
} from "./agentSessionPanelLayout";
import { shorten } from "./agentSessionUtils";
import { useObserverEvents, useAgentTranscript } from "./useObserverEvents";
@@ -58,29 +63,21 @@ export function ManagedAgentSessionPanel({
// Filter transcript items by channelId (lightweight — items now carry channelId)
const scopedTranscript = React.useMemo(
() =>
channelId
? transcript.filter((item) => item.channelId === channelId)
: transcript,
() => scopeByChannel(transcript, channelId),
[channelId, transcript],
);
// Filter raw events by channelId for the RawEventRail
const scopedEvents = React.useMemo(
() =>
channelId
? events.filter((event) => event.channelId === channelId)
: events,
() => scopeByChannel(events, channelId),
[channelId, events],
);
// Derive latestSessionId from channel-scoped events
const latestSessionId = React.useMemo(() => {
for (let i = scopedEvents.length - 1; i >= 0; i--) {
if (scopedEvents[i].sessionId) return scopedEvents[i].sessionId;
}
return null;
}, [scopedEvents]);
const latestSessionId = React.useMemo(
() => deriveLatestSessionId(scopedEvents),
[scopedEvents],
);
return (
<section
@@ -181,7 +178,9 @@ function SessionBody({
showRaw: boolean;
transcript: TranscriptItem[];
}) {
if (showRaw && rawLayout === "exclusive") {
const rawRail = resolveRawRailLayout(showRaw, rawLayout);
if (rawRail.mode === "exclusive") {
return (
<>
<RawEventRail events={events} />
@@ -205,7 +204,7 @@ function SessionBody({
) : (
<div
className={cn(
showRaw && rawLayout === "responsive"
rawRail.mode === "side"
? "mt-4 grid gap-4 xl:grid-cols-[minmax(0,1fr)_20rem]"
: "mt-0",
)}
@@ -219,9 +218,7 @@ function SessionBody({
items={transcript}
profiles={profiles}
/>
{showRaw && rawLayout === "responsive" ? (
<RawEventRail events={events} />
) : null}
{rawRail.mode === "side" ? <RawEventRail events={events} /> : null}
</div>
)}
@@ -0,0 +1,85 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
deriveLatestSessionId,
resolveRawRailLayout,
scopeByChannel,
} from "./agentSessionPanelLayout.ts";
// ---- scopeByChannel ----
const items = [
{ id: "a", channelId: "channel-1" },
{ id: "b", channelId: "channel-2" },
{ id: "c", channelId: "channel-1" },
];
test("scopeByChannel returns the input unchanged when channelId is null", () => {
assert.equal(scopeByChannel(items, null), items);
});
test("scopeByChannel returns the input unchanged when channelId is undefined", () => {
assert.equal(scopeByChannel(items, undefined), items);
});
test("scopeByChannel filters items down to the requested channel", () => {
const scoped = scopeByChannel(items, "channel-1");
assert.deepEqual(
scoped.map((item) => item.id),
["a", "c"],
);
});
test("scopeByChannel returns an empty array when no item matches", () => {
assert.deepEqual(scopeByChannel(items, "channel-99"), []);
});
// ---- deriveLatestSessionId ----
test("deriveLatestSessionId returns null for an empty list", () => {
assert.equal(deriveLatestSessionId([]), null);
});
test("deriveLatestSessionId returns the last event's sessionId", () => {
const events = [
{ seq: 1, sessionId: "sess-1" },
{ seq: 2, sessionId: "sess-2" },
];
assert.equal(deriveLatestSessionId(events), "sess-2");
});
test("deriveLatestSessionId skips trailing events without a sessionId", () => {
const events = [
{ seq: 1, sessionId: "sess-1" },
{ seq: 2, sessionId: null },
{ seq: 3, sessionId: undefined },
];
assert.equal(deriveLatestSessionId(events), "sess-1");
});
test("deriveLatestSessionId returns null when no event carries a sessionId", () => {
const events = [{ seq: 1, sessionId: null }, { seq: 2 }];
assert.equal(deriveLatestSessionId(events), null);
});
// ---- resolveRawRailLayout (raw-ACP view toggle) ----
test("resolveRawRailLayout hides the rail when showRaw is off", () => {
assert.deepEqual(resolveRawRailLayout(false, "responsive"), {
mode: "hidden",
});
assert.deepEqual(resolveRawRailLayout(false, "exclusive"), {
mode: "hidden",
});
});
test("resolveRawRailLayout renders the rail exclusively when toggled on in exclusive layout", () => {
assert.deepEqual(resolveRawRailLayout(true, "exclusive"), {
mode: "exclusive",
});
});
test("resolveRawRailLayout renders the rail beside the transcript in responsive layout", () => {
assert.deepEqual(resolveRawRailLayout(true, "responsive"), { mode: "side" });
});
@@ -0,0 +1,48 @@
import type { ObserverEvent } from "./agentSessionTypes";
/**
* Filter transcript items or raw observer events down to a single channel.
* A null `channelId` means "no scoping" — the input is returned as-is.
*/
export function scopeByChannel<T extends { channelId?: string | null }>(
items: readonly T[],
channelId: string | null | undefined,
): T[] {
if (!channelId) return items as T[];
return items.filter((item) => item.channelId === channelId);
}
/**
* Derive the most recent session id from a list of observer events by
* scanning from the end. Returns null when no event carries a sessionId.
*/
export function deriveLatestSessionId(
events: readonly ObserverEvent[],
): string | null {
for (let i = events.length - 1; i >= 0; i--) {
const sessionId = events[i]?.sessionId;
if (sessionId) return sessionId;
}
return null;
}
export type RawRailLayout =
| { mode: "hidden" }
| { mode: "exclusive" }
| { mode: "side" };
/**
* Decide how the raw-ACP event rail should be rendered relative to the
* transcript:
* - `hidden` — raw view is off
* - `exclusive` — raw rail replaces the transcript entirely
* - `side` — raw rail renders alongside the transcript (responsive)
*/
export function resolveRawRailLayout(
showRaw: boolean,
rawLayout: "responsive" | "exclusive",
): RawRailLayout {
if (!showRaw) return { mode: "hidden" };
if (rawLayout === "exclusive") return { mode: "exclusive" };
return { mode: "side" };
}
@@ -0,0 +1,128 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
formatDurationMs,
getToolDurationDisplay,
isInlineImageData,
parseToolResultValue,
} from "./agentSessionUtils.ts";
// ---- isInlineImageData (dual-layer image-scheme security guard) ----
test("isInlineImageData accepts data:image/ URIs", () => {
const dataUri =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGNgYGAAAAAEAAH2FzhVAAAAAElFTkSuQmCC";
assert.equal(isInlineImageData(dataUri), true);
});
test("isInlineImageData rejects non-image data: schemes (no passthrough widening)", () => {
// A non-image data: URI must NOT be treated as a safe inline image —
// it has to fall through to the relay rewrite path.
assert.equal(
isInlineImageData(
"data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==",
),
false,
);
assert.equal(isInlineImageData("data:application/json;base64,e30="), false);
});
test("isInlineImageData rejects relay-relative and absolute media URLs", () => {
assert.equal(isInlineImageData("/media/abc123.png"), false);
assert.equal(isInlineImageData("https://relay.example/media/abc.png"), false);
});
// ---- formatDurationMs ----
test("formatDurationMs returns null for negative input", () => {
assert.equal(formatDurationMs(-1), null);
});
test("formatDurationMs renders sub-10s with one decimal", () => {
assert.equal(formatDurationMs(400), "0.4s");
assert.equal(formatDurationMs(9900), "9.9s");
});
test("formatDurationMs rounds 10s..60s to whole seconds", () => {
assert.equal(formatDurationMs(12300), "12s");
assert.equal(formatDurationMs(59400), "59s");
});
test("formatDurationMs renders minutes and seconds", () => {
assert.equal(formatDurationMs(90000), "1m 30s");
assert.equal(formatDurationMs(120000), "2m");
});
test("formatDurationMs carries a rounded 60s into the next minute", () => {
// 89.7s rounds the seconds component to 60, which must carry to 1m 30s
assert.equal(formatDurationMs(89700), "1m 30s");
});
// ---- parseToolResultValue (JSON double-parse) ----
test("parseToolResultValue returns null for empty/whitespace", () => {
assert.equal(parseToolResultValue(""), null);
assert.equal(parseToolResultValue(" "), null);
});
test("parseToolResultValue parses a JSON object", () => {
assert.deepEqual(parseToolResultValue('{"duration_ms":123}'), {
duration_ms: 123,
});
});
test("parseToolResultValue unwraps a double-encoded JSON string", () => {
// The result is a JSON string that itself contains JSON.
const doubleEncoded = JSON.stringify(JSON.stringify({ ok: true }));
assert.deepEqual(parseToolResultValue(doubleEncoded), { ok: true });
});
test("parseToolResultValue returns the inner string when it is not JSON", () => {
const wrapped = JSON.stringify("plain text");
assert.equal(parseToolResultValue(wrapped), "plain text");
});
test("parseToolResultValue returns null for invalid JSON", () => {
assert.equal(parseToolResultValue("not json {"), null);
});
// ---- getToolDurationDisplay (fallback chain) ----
const startedAt = "2026-06-14T19:00:00.000Z";
const completedAt = "2026-06-14T19:00:02.000Z";
test("getToolDurationDisplay prefers start/complete timestamps", () => {
assert.equal(
getToolDurationDisplay({ startedAt, completedAt, result: "" }),
"2.0s",
);
});
test("getToolDurationDisplay falls back to duration_ms in the result payload", () => {
assert.equal(
getToolDurationDisplay({
startedAt: null,
completedAt: null,
result: JSON.stringify({ duration_ms: 3500 }),
}),
"3.5s",
);
});
test("getToolDurationDisplay falls back to elapsed_ms when duration_ms absent", () => {
assert.equal(
getToolDurationDisplay({
result: JSON.stringify({ elapsed_ms: 65000 }),
}),
"1m 5s",
);
});
test("getToolDurationDisplay returns null when no duration is available", () => {
assert.equal(getToolDurationDisplay({ result: "" }), null);
assert.equal(
getToolDurationDisplay({ result: JSON.stringify({ other: 1 }) }),
null,
);
});
@@ -110,7 +110,7 @@ function classifyDeveloperToolName(
if (base === "postcompact") return "post_compact_hook";
if (DEVELOPER_TOOL_BASES.has(base)) {
return base === "shell" ? "shell" : "dev_mcp";
return "dev_mcp";
}
if (normalized.includes("buzz_dev_mcp")) {
@@ -84,9 +84,6 @@ function classifyTurnItems(items: TranscriptItem[]): TranscriptTurnSegment[] {
segments.push({ kind: "item", item });
continue;
}
if (isSetupLifecycle(item)) {
continue;
}
segments.push({ kind: "item", item });
}
@@ -64,6 +64,91 @@ export function asRecord(value: unknown): Record<string, unknown> {
: {};
}
/**
* True when a tool image source is an inline `data:image/` URI that should be
* rendered as-is. This is the dual-layer image-scheme guard: only the
* `data:image/` prefix is treated as a safe passthrough every other scheme
* (including other `data:` subtypes) must be routed through the relay rewriter.
* Never widen this beyond `data:image/`.
*/
export function isInlineImageData(source: string): boolean {
return source.startsWith("data:image/");
}
function getToolNumber(
record: Record<string, unknown>,
keys: string[],
): number | null {
for (const key of keys) {
const value = record[key];
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
}
return null;
}
/** Format a millisecond duration; negative input yields null. */
export function formatDurationMs(ms: number): string | null {
if (ms < 0) return null;
const totalSeconds = ms / 1000;
if (totalSeconds < 60) {
return totalSeconds < 10
? `${totalSeconds.toFixed(1)}s`
: `${Math.round(totalSeconds)}s`;
}
let minutes = Math.floor(totalSeconds / 60);
let seconds = Math.round(totalSeconds % 60);
if (seconds === 60) {
minutes += 1;
seconds = 0;
}
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
}
/**
* Parse a tool result string into a value. Handles the double-encoding case
* where a JSON string itself contains JSON. Returns null on empty or invalid
* input.
*/
export function parseToolResultValue(result: string): unknown {
const trimmed = result.trim();
if (!trimmed) return null;
try {
const parsed = JSON.parse(trimmed);
if (typeof parsed !== "string") return parsed;
try {
return JSON.parse(parsed);
} catch {
return parsed;
}
} catch {
return null;
}
}
/**
* Resolve a tool's display duration. Prefers the start/complete timestamps,
* then falls back to `duration_ms`/`elapsed_ms` fields inside the parsed
* result payload.
*/
export function getToolDurationDisplay(item: {
startedAt?: string | null;
completedAt?: string | null;
result: string;
}): string | null {
if (item.startedAt && item.completedAt) {
return formatDuration(item.startedAt, item.completedAt);
}
const resultRecord = asRecord(parseToolResultValue(item.result));
const durationMs =
getToolNumber(resultRecord, ["duration_ms", "durationMs"]) ??
getToolNumber(resultRecord, ["elapsed_ms", "elapsedMs"]);
return durationMs == null ? null : formatDurationMs(durationMs);
}
export function asString(value: unknown): string | null {
return typeof value === "string" ? value : null;
}
@@ -0,0 +1,42 @@
import assert from "node:assert/strict";
import test from "node:test";
import { describeRawEvent } from "./agentSessionTranscriptHelpers.ts";
function rawEvent(overrides = {}) {
return {
seq: 1,
kind: "acp",
sessionId: "sess-1",
channelId: "channel-1",
payload: {},
...overrides,
};
}
test("describeRawEvent surfaces the session/update sessionUpdate label", () => {
const event = rawEvent({
payload: {
method: "session/update",
params: { update: { sessionUpdate: "agent_message_chunk" } },
},
});
assert.equal(describeRawEvent(event), "agent_message_chunk");
});
test("describeRawEvent falls back to the method when session/update lacks an update label", () => {
const event = rawEvent({
payload: { method: "session/update", params: {} },
});
assert.equal(describeRawEvent(event), "session/update");
});
test("describeRawEvent uses the method for non-session/update payloads", () => {
const event = rawEvent({ payload: { method: "session/prompt" } });
assert.equal(describeRawEvent(event), "session/prompt");
});
test("describeRawEvent falls back to the event kind when no method is present", () => {
const event = rawEvent({ kind: "acp_parse_error", payload: {} });
assert.equal(describeRawEvent(event), "acp_parse_error");
});