chore(agents): add dev-only ACP source labels on transcript rows

- Stamp each transcript item with its observer/ACP wire source during build
- Show amber dev-only badges in the activity transcript for local debugging
- Add unit tests covering agent_message_chunk vs agent_thought_chunk tagging
This commit is contained in:
Taylor Ho
2026-06-15 15:59:27 -07:00
parent cb684ad315
commit b98df61359
4 changed files with 130 additions and 4 deletions
@@ -26,6 +26,9 @@ import { buildTranscriptPresentation } from "./agentSessionTranscriptPresentatio
import { formatTranscriptTime } from "./agentSessionUtils";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
/** Dev-only: surface the observer wire label that produced each transcript row. */
const SHOW_TRANSCRIPT_ACP_SOURCE = import.meta.env.DEV;
export function AgentSessionTranscriptList({
agentName,
compact = false,
@@ -87,6 +90,9 @@ export function AgentSessionTranscriptList({
)}
key={item.id}
>
{SHOW_TRANSCRIPT_ACP_SOURCE && item.acpSource ? (
<TranscriptAcpSourceBadge source={item.acpSource} />
) : null}
<TranscriptItemView
agentName={agentName}
compact={compact}
@@ -101,6 +107,18 @@ export function AgentSessionTranscriptList({
);
}
function TranscriptAcpSourceBadge({ source }: { source: string }) {
return (
<span
className="mb-1 inline-flex max-w-full rounded border border-amber-500/25 bg-amber-500/10 px-1.5 py-0.5 font-mono text-[10px] leading-none text-amber-800 dark:text-amber-200"
data-testid="transcript-acp-source"
title={`ACP wire source: ${source}`}
>
{source}
</span>
);
}
function TranscriptNowSummary({
agentName,
compact,
@@ -0,0 +1,60 @@
import assert from "node:assert/strict";
import test from "node:test";
import { buildTranscript } from "./agentSessionTranscript.ts";
test("buildTranscript tags assistant chunks with agent_message_chunk", () => {
const items = buildTranscript([
{
seq: 1,
timestamp: "2026-06-14T20:47:14.000Z",
kind: "acp_read",
agentIndex: 0,
channelId: "channel-1",
sessionId: "sess-1",
turnId: "turn-1",
payload: {
method: "session/update",
params: {
update: {
sessionUpdate: "agent_message_chunk",
messageId: "msg-1",
content: [{ type: "text", text: "Marge is summoned." }],
},
},
},
},
]);
assert.equal(items.length, 1);
assert.equal(items[0]?.type, "message");
assert.equal(items[0]?.acpSource, "agent_message_chunk");
});
test("buildTranscript tags thought chunks with agent_thought_chunk", () => {
const items = buildTranscript([
{
seq: 2,
timestamp: "2026-06-14T20:47:15.000Z",
kind: "acp_read",
agentIndex: 0,
channelId: "channel-1",
sessionId: "sess-1",
turnId: "turn-1",
payload: {
method: "session/update",
params: {
update: {
sessionUpdate: "agent_thought_chunk",
messageId: "thought-1",
content: [{ type: "text", text: "Considering next step." }],
},
},
},
},
]);
assert.equal(items.length, 1);
assert.equal(items[0]?.type, "thought");
assert.equal(items[0]?.acpSource, "agent_thought_chunk");
});
@@ -117,6 +117,7 @@ function upsertMessage(
timestamp: string,
channelId: string | null,
authorPubkey: string | null = null,
acpSource?: string,
) {
const currentKey = d.activeMessageKey.get(id);
@@ -128,6 +129,7 @@ function upsertMessage(
text: existing.text + text,
channelId,
authorPubkey: authorPubkey ?? existing.authorPubkey,
acpSource: acpSource ?? existing.acpSource,
});
return;
}
@@ -144,6 +146,7 @@ function upsertMessage(
timestamp,
channelId,
authorPubkey,
acpSource,
});
d.activeMessageKey = new Map(d.activeMessageKey);
d.activeMessageKey.set(id, newKey);
@@ -157,14 +160,20 @@ function upsertTextItem(
text: string,
timestamp: string,
channelId: string | null,
acpSource?: string,
) {
const existing = d.itemsById.get(id);
if (existing && existing.type === type) {
replaceItem(d, id, { ...existing, text: existing.text + text, channelId });
replaceItem(d, id, {
...existing,
text: existing.text + text,
channelId,
acpSource: acpSource ?? existing.acpSource,
});
return;
}
sealOpenMessages(d);
pushItem(d, { id, type, title, text, timestamp, channelId });
pushItem(d, { id, type, title, text, timestamp, channelId, acpSource });
}
function upsertMetadata(
@@ -174,14 +183,28 @@ function upsertMetadata(
sections: PromptSection[],
timestamp: string,
channelId: string | null,
acpSource?: string,
) {
const existing = d.itemsById.get(id);
if (existing?.type === "metadata") {
replaceItem(d, id, { ...existing, sections, channelId });
replaceItem(d, id, {
...existing,
sections,
channelId,
acpSource: acpSource ?? existing.acpSource,
});
return;
}
sealOpenMessages(d);
pushItem(d, { id, type: "metadata", title, sections, timestamp, channelId });
pushItem(d, {
id,
type: "metadata",
title,
sections,
timestamp,
channelId,
acpSource,
});
}
function upsertTool(
@@ -196,6 +219,7 @@ function upsertTool(
isError: boolean,
timestamp: string,
channelId: string | null,
acpSource?: string,
) {
const existing = d.itemsById.get(id);
const canonicalBuzzToolName =
@@ -225,6 +249,7 @@ function upsertTool(
? timestamp
: existing.completedAt,
channelId,
acpSource: acpSource ?? existing.acpSource,
});
return;
}
@@ -243,6 +268,7 @@ function upsertTool(
startedAt: timestamp,
completedAt: null,
channelId,
acpSource,
});
}
@@ -268,6 +294,7 @@ export function processTranscriptEvent(
describeTurnStarted(event.payload),
event.timestamp,
channelId,
event.kind,
);
} else if (event.kind === "session_resolved") {
upsertTextItem(
@@ -278,6 +305,7 @@ export function processTranscriptEvent(
describeSessionResolved(event.payload),
event.timestamp,
channelId,
event.kind,
);
} else if (event.kind === "acp_parse_error") {
upsertTextItem(
@@ -288,6 +316,7 @@ export function processTranscriptEvent(
extractBlockText(event.payload),
event.timestamp,
channelId,
event.kind,
);
} else if (event.kind === "turn_error" || event.kind === "agent_panic") {
const payload = asRecord(event.payload);
@@ -303,6 +332,7 @@ export function processTranscriptEvent(
`${outcome}: ${error}`,
event.timestamp,
channelId,
event.kind,
);
} else if (event.kind === "acp_read" || event.kind === "acp_write") {
const payload = asRecord(event.payload);
@@ -322,6 +352,7 @@ export function processTranscriptEvent(
event.timestamp,
channelId,
parsedPrompt.userPubkey,
"session/prompt:user",
);
}
if (parsedPrompt.sections.length > 0) {
@@ -332,6 +363,7 @@ export function processTranscriptEvent(
parsedPrompt.sections,
event.timestamp,
channelId,
"session/prompt:context",
);
}
}
@@ -351,6 +383,8 @@ export function processTranscriptEvent(
extractContentText(update.content),
event.timestamp,
channelId,
null,
updateType,
);
} else if (updateType === "user_message_chunk") {
upsertMessage(
@@ -361,6 +395,8 @@ export function processTranscriptEvent(
extractContentText(update.content),
event.timestamp,
channelId,
null,
updateType,
);
} else if (updateType === "agent_thought_chunk") {
upsertTextItem(
@@ -371,6 +407,7 @@ export function processTranscriptEvent(
extractContentText(update.content),
event.timestamp,
channelId,
updateType,
);
} else if (updateType === "tool_call") {
const toolId = asString(update.toolCallId) ?? `tool:${event.seq}`;
@@ -387,6 +424,7 @@ export function processTranscriptEvent(
false,
event.timestamp,
channelId,
updateType,
);
} else if (updateType === "tool_call_update") {
const toolId = asString(update.toolCallId) ?? `tool:${event.seq}`;
@@ -406,6 +444,7 @@ export function processTranscriptEvent(
status === "failed",
event.timestamp,
channelId,
updateType,
);
} else if (updateType === "plan") {
upsertTextItem(
@@ -416,6 +455,7 @@ export function processTranscriptEvent(
extractContentText(update.content) || JSON.stringify(update, null, 2),
event.timestamp,
channelId,
updateType,
);
}
}
@@ -20,6 +20,9 @@ export type ConnectionState =
export type ToolStatus = "executing" | "completed" | "failed" | "pending";
/** Observer/ACP wire label for dev-only transcript debugging. */
export type TranscriptAcpSource = string;
export type TranscriptItem =
| {
id: string;
@@ -28,6 +31,7 @@ export type TranscriptItem =
title: string;
text: string;
timestamp: string;
acpSource?: TranscriptAcpSource;
authorPubkey?: string | null;
channelId?: string | null;
}
@@ -37,6 +41,7 @@ export type TranscriptItem =
title: string;
text: string;
timestamp: string;
acpSource?: TranscriptAcpSource;
channelId?: string | null;
}
| {
@@ -45,6 +50,7 @@ export type TranscriptItem =
title: string;
text: string;
timestamp: string;
acpSource?: TranscriptAcpSource;
channelId?: string | null;
}
| {
@@ -53,6 +59,7 @@ export type TranscriptItem =
title: string;
sections: PromptSection[];
timestamp: string;
acpSource?: TranscriptAcpSource;
channelId?: string | null;
}
| {
@@ -68,6 +75,7 @@ export type TranscriptItem =
timestamp: string;
startedAt: string;
completedAt: string | null;
acpSource?: TranscriptAcpSource;
channelId?: string | null;
};