mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
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:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user