mirror of
https://github.com/block/buzz.git
synced 2026-11-08 18:31:56 +00:00
feat(desktop): rebuild agent activity feed around a classifier registry and twelve render classes (#1381)
Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
buildTranscriptDisplayBlocks,
|
||||
flattenDisplayBlocks,
|
||||
formatTurnSetupLabel,
|
||||
} from "./agentSessionTranscriptGrouping.ts";
|
||||
|
||||
const baseTimestamp = "2026-06-14T22:20:23.000Z";
|
||||
|
||||
function lifecycle(id, title, acpSource, turnId, text = "") {
|
||||
return {
|
||||
id,
|
||||
type: "lifecycle",
|
||||
title,
|
||||
text,
|
||||
timestamp: baseTimestamp,
|
||||
acpSource,
|
||||
turnId,
|
||||
sessionId: "sess-1",
|
||||
channelId: "channel-1",
|
||||
};
|
||||
}
|
||||
|
||||
function userPrompt(id, text, turnId) {
|
||||
return {
|
||||
id,
|
||||
type: "message",
|
||||
role: "user",
|
||||
title: "Buzz event",
|
||||
text,
|
||||
timestamp: baseTimestamp,
|
||||
acpSource: "session/prompt:user",
|
||||
turnId,
|
||||
sessionId: "sess-1",
|
||||
channelId: "channel-1",
|
||||
};
|
||||
}
|
||||
|
||||
function promptContext(id, turnId) {
|
||||
return {
|
||||
id,
|
||||
type: "metadata",
|
||||
title: "Prompt context",
|
||||
sections: [{ title: "Channel", body: "general" }],
|
||||
timestamp: baseTimestamp,
|
||||
acpSource: "session/prompt:context",
|
||||
turnId,
|
||||
sessionId: "sess-1",
|
||||
channelId: "channel-1",
|
||||
};
|
||||
}
|
||||
|
||||
function assistantMessage(id, text, turnId) {
|
||||
return {
|
||||
id,
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
title: "Assistant",
|
||||
text,
|
||||
timestamp: "2026-06-14T22:20:47.000Z",
|
||||
acpSource: "agent_message_chunk",
|
||||
turnId,
|
||||
sessionId: "sess-1",
|
||||
channelId: "channel-1",
|
||||
};
|
||||
}
|
||||
|
||||
function toolCall(id, turnId) {
|
||||
return {
|
||||
id,
|
||||
type: "tool",
|
||||
title: "Shell",
|
||||
toolName: "buzz-dev-mcp__shell",
|
||||
buzzToolName: null,
|
||||
status: "completed",
|
||||
args: {},
|
||||
result: "ok",
|
||||
isError: false,
|
||||
timestamp: "2026-06-14T22:20:47.000Z",
|
||||
startedAt: "2026-06-14T22:20:47.000Z",
|
||||
completedAt: "2026-06-14T22:20:47.400Z",
|
||||
acpSource: "tool_call_update",
|
||||
turnId,
|
||||
sessionId: "sess-1",
|
||||
channelId: "channel-1",
|
||||
};
|
||||
}
|
||||
|
||||
test("buildTranscriptDisplayBlocks bundles user prompt, setup, and context together", () => {
|
||||
const rawItems = [
|
||||
lifecycle(
|
||||
"turn",
|
||||
"Turn started",
|
||||
"turn_started",
|
||||
"turn-1",
|
||||
"Triggered by 1 event.",
|
||||
),
|
||||
lifecycle("session", "Session ready", "session_resolved", "turn-1"),
|
||||
userPrompt("prompt", "@Ned deliberate, wider pass", "turn-1"),
|
||||
promptContext("context", "turn-1"),
|
||||
assistantMessage("assistant", "Thinking out loud.", "turn-1"),
|
||||
toolCall("tool", "turn-1"),
|
||||
];
|
||||
|
||||
const blocks = buildTranscriptDisplayBlocks(rawItems);
|
||||
const displayOrder = flattenDisplayBlocks(blocks).map((item) => item.id);
|
||||
|
||||
assert.deepEqual(displayOrder, [
|
||||
"prompt",
|
||||
"turn",
|
||||
"session",
|
||||
"context",
|
||||
"assistant",
|
||||
"tool",
|
||||
]);
|
||||
|
||||
const turnBlock = blocks[0];
|
||||
assert.equal(turnBlock?.kind, "turn");
|
||||
assert.equal(turnBlock.segments[0]?.kind, "prompt");
|
||||
const promptSegment = turnBlock.segments[0];
|
||||
assert.equal(promptSegment.user.id, "prompt");
|
||||
assert.equal(promptSegment.context?.id, "context");
|
||||
assert.equal(promptSegment.setup.length, 2);
|
||||
assert.equal(turnBlock.segments[1]?.kind, "item");
|
||||
assert.equal(turnBlock.segments[2]?.kind, "item");
|
||||
});
|
||||
|
||||
test("buildTranscriptDisplayBlocks collapses setup lifecycle inside prompt bundle", () => {
|
||||
const rawItems = [
|
||||
lifecycle("turn", "Turn started", "turn_started", "turn-1"),
|
||||
lifecycle("session", "Session ready", "session_resolved", "turn-1"),
|
||||
userPrompt("prompt", "hello", "turn-1"),
|
||||
];
|
||||
|
||||
const blocks = buildTranscriptDisplayBlocks(rawItems);
|
||||
assert.equal(blocks.length, 1);
|
||||
assert.equal(blocks[0]?.kind, "turn");
|
||||
|
||||
const turnBlock = blocks[0];
|
||||
assert.equal(turnBlock.segments.length, 1);
|
||||
assert.equal(turnBlock.segments[0]?.kind, "prompt");
|
||||
assert.equal(
|
||||
formatTurnSetupLabel(turnBlock.segments[0].setup),
|
||||
"Turn started · Session ready",
|
||||
);
|
||||
});
|
||||
|
||||
test("buildTranscriptDisplayBlocks hides setup and context when prompt is missing", () => {
|
||||
const rawItems = [
|
||||
lifecycle("turn", "Turn started", "turn_started", "turn-1"),
|
||||
lifecycle("session", "Session ready", "session_resolved", "turn-1"),
|
||||
promptContext("context", "turn-1"),
|
||||
toolCall("tool", "turn-1"),
|
||||
];
|
||||
|
||||
const blocks = buildTranscriptDisplayBlocks(rawItems);
|
||||
const displayOrder = flattenDisplayBlocks(blocks).map((item) => item.id);
|
||||
|
||||
assert.deepEqual(displayOrder, ["tool"]);
|
||||
});
|
||||
|
||||
test("buildTranscriptDisplayBlocks drops setup-and-context-only turns", () => {
|
||||
const rawItems = [
|
||||
lifecycle("turn", "Turn started", "turn_started", "turn-1"),
|
||||
lifecycle("session", "Session ready", "session_resolved", "turn-1"),
|
||||
promptContext("context", "turn-1"),
|
||||
];
|
||||
|
||||
const blocks = buildTranscriptDisplayBlocks(rawItems);
|
||||
|
||||
assert.deepEqual(blocks, []);
|
||||
});
|
||||
|
||||
test("buildTranscriptDisplayBlocks leaves error lifecycle prominent outside prompt bundle", () => {
|
||||
const rawItems = [
|
||||
lifecycle("turn", "Turn started", "turn_started", "turn-1"),
|
||||
userPrompt("prompt", "hello", "turn-1"),
|
||||
lifecycle(
|
||||
"error",
|
||||
"Turn error",
|
||||
"turn_error",
|
||||
"turn-1",
|
||||
"timeout: agent hung",
|
||||
),
|
||||
];
|
||||
|
||||
const blocks = buildTranscriptDisplayBlocks(rawItems);
|
||||
const displayOrder = flattenDisplayBlocks(blocks).map((item) => item.id);
|
||||
|
||||
assert.deepEqual(displayOrder, ["prompt", "turn", "error"]);
|
||||
assert.equal(blocks[0]?.segments[0]?.kind, "prompt");
|
||||
assert.equal(blocks[0]?.segments[1]?.kind, "item");
|
||||
assert.equal(blocks[0]?.segments[1]?.item.id, "error");
|
||||
});
|
||||
|
||||
test("buildTranscriptDisplayBlocks passes through items without turnId", () => {
|
||||
const orphan = {
|
||||
id: "orphan",
|
||||
type: "lifecycle",
|
||||
title: "Wire parse error",
|
||||
text: "bad json",
|
||||
timestamp: baseTimestamp,
|
||||
acpSource: "acp_parse_error",
|
||||
channelId: "channel-1",
|
||||
};
|
||||
|
||||
const blocks = buildTranscriptDisplayBlocks([orphan]);
|
||||
assert.equal(blocks.length, 1);
|
||||
assert.equal(blocks[0]?.kind, "single");
|
||||
assert.equal(blocks[0]?.item.id, "orphan");
|
||||
});
|
||||
|
||||
test("buildTranscriptDisplayBlocks groups same-kind tool runs within a turn", () => {
|
||||
const items = [1, 2, 3].map((index) => ({
|
||||
id: `tool:${index}`,
|
||||
type: "tool",
|
||||
renderClass: "generic",
|
||||
descriptor: {
|
||||
renderClass: "generic",
|
||||
label: "Read file",
|
||||
preview: `file-${index}.ts`,
|
||||
groupKey: "read_file",
|
||||
},
|
||||
title: "read_file",
|
||||
toolName: "read_file",
|
||||
buzzToolName: null,
|
||||
status: "completed",
|
||||
args: { path: `file-${index}.ts` },
|
||||
result: "",
|
||||
isError: false,
|
||||
timestamp: "2026-06-18T00:00:00Z",
|
||||
startedAt: "2026-06-18T00:00:00Z",
|
||||
completedAt: "2026-06-18T00:00:01Z",
|
||||
turnId: "turn-1",
|
||||
sessionId: "sess-1",
|
||||
channelId: "chan-1",
|
||||
}));
|
||||
|
||||
const [block] = buildTranscriptDisplayBlocks(items);
|
||||
|
||||
assert.equal(block.kind, "turn");
|
||||
assert.equal(block.segments.length, 1);
|
||||
assert.equal(block.segments[0].kind, "summary");
|
||||
assert.equal(block.segments[0].summary.label, "Read 3 files");
|
||||
});
|
||||
|
||||
test("buildTranscriptDisplayBlocks groups consecutive file edit tool runs", () => {
|
||||
const items = [1, 2].map((index) => ({
|
||||
id: `edit:${index}`,
|
||||
type: "tool",
|
||||
renderClass: "file-edit",
|
||||
descriptor: {
|
||||
renderClass: "file-edit",
|
||||
label: "Edited file",
|
||||
preview: `src/file-${index}.ts`,
|
||||
groupKey: "file-edit:str_replace",
|
||||
},
|
||||
title: "str_replace",
|
||||
toolName: "str_replace",
|
||||
buzzToolName: null,
|
||||
status: "completed",
|
||||
args: { path: `src/file-${index}.ts` },
|
||||
result: "",
|
||||
isError: false,
|
||||
timestamp: "2026-06-18T00:00:00Z",
|
||||
startedAt: "2026-06-18T00:00:00Z",
|
||||
completedAt: "2026-06-18T00:00:01Z",
|
||||
turnId: "turn-1",
|
||||
sessionId: "sess-1",
|
||||
channelId: "chan-1",
|
||||
}));
|
||||
|
||||
const [block] = buildTranscriptDisplayBlocks(items);
|
||||
|
||||
assert.equal(block.kind, "turn");
|
||||
assert.equal(block.segments.length, 1);
|
||||
assert.equal(block.segments[0].kind, "summary");
|
||||
assert.equal(block.segments[0].summary.label, "Edited 2 files");
|
||||
assert.equal(block.segments[0].summary.renderClass, "file-edit");
|
||||
assert.deepEqual(
|
||||
block.segments[0].summary.items.map((item) => item.id),
|
||||
["edit:1", "edit:2"],
|
||||
);
|
||||
});
|
||||
|
||||
test("buildTranscriptDisplayBlocks keeps non-contiguous same-kind runs expanded", () => {
|
||||
const mkTool = (id, label, renderClass = "generic", groupKey = label) => ({
|
||||
id,
|
||||
type: "tool",
|
||||
renderClass,
|
||||
descriptor: {
|
||||
renderClass,
|
||||
label,
|
||||
preview: id,
|
||||
source: "harness",
|
||||
groupKey,
|
||||
},
|
||||
title: label,
|
||||
toolName: label,
|
||||
buzzToolName: null,
|
||||
status: "completed",
|
||||
args: {},
|
||||
result: "",
|
||||
isError: false,
|
||||
timestamp: "2026-06-18T00:00:00Z",
|
||||
startedAt: "2026-06-18T00:00:00Z",
|
||||
completedAt: "2026-06-18T00:00:01Z",
|
||||
turnId: "turn-1",
|
||||
sessionId: "sess-1",
|
||||
channelId: "chan-1",
|
||||
});
|
||||
|
||||
const [block] = buildTranscriptDisplayBlocks([
|
||||
mkTool("read-1", "Read file", "generic", "read_file"),
|
||||
mkTool("shell-1", "Ran command", "shell", "shell:command"),
|
||||
mkTool("read-2", "Read file", "generic", "read_file"),
|
||||
mkTool("read-3", "Read file", "generic", "read_file"),
|
||||
]);
|
||||
|
||||
assert.equal(block.kind, "turn");
|
||||
assert.deepEqual(
|
||||
block.segments.map((segment) => segment.kind),
|
||||
["item", "item", "item", "item"],
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user