diff --git a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
index 08bc730a3..019b4d7d9 100644
--- a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
+++ b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
@@ -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 ? (
+
+ ) : null}
+ {source}
+
+ );
+}
+
function TranscriptNowSummary({
agentName,
compact,
diff --git a/desktop/src/features/agents/ui/agentSessionTranscript.test.mjs b/desktop/src/features/agents/ui/agentSessionTranscript.test.mjs
new file mode 100644
index 000000000..dda17b174
--- /dev/null
+++ b/desktop/src/features/agents/ui/agentSessionTranscript.test.mjs
@@ -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");
+});
diff --git a/desktop/src/features/agents/ui/agentSessionTranscript.ts b/desktop/src/features/agents/ui/agentSessionTranscript.ts
index 60cf7f62b..62d642f59 100644
--- a/desktop/src/features/agents/ui/agentSessionTranscript.ts
+++ b/desktop/src/features/agents/ui/agentSessionTranscript.ts
@@ -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,
);
}
}
diff --git a/desktop/src/features/agents/ui/agentSessionTypes.ts b/desktop/src/features/agents/ui/agentSessionTypes.ts
index 2ff4ea305..90fb2500e 100644
--- a/desktop/src/features/agents/ui/agentSessionTypes.ts
+++ b/desktop/src/features/agents/ui/agentSessionTypes.ts
@@ -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;
};