mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
Preserve transcript tool-run view choices
Co-authored-by: ss-quality-00 <75789fffd704a5265cd65462cfc263f44561872fbebcea3a131a2a5f511465d2@buzz.block.builderlab.xyz> Signed-off-by: ss-quality-00 <75789fffd704a5265cd65462cfc263f44561872fbebcea3a131a2a5f511465d2@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -49,6 +49,7 @@ export default defineConfig({
|
||||
"**/profile-active-turn.spec.ts",
|
||||
"**/config-bridge-screenshots.spec.ts",
|
||||
"**/observer-feed-screenshots.spec.ts",
|
||||
"**/transcript-tool-run-quality.smoke.spec.ts",
|
||||
"**/core-memory-screenshots.spec.ts",
|
||||
"**/activity-scope-label-screenshots.spec.ts",
|
||||
"**/welcome-agent-modal-screenshots.spec.ts",
|
||||
|
||||
@@ -29,11 +29,16 @@ export function ToolItem({
|
||||
agentPubkey,
|
||||
item,
|
||||
profiles,
|
||||
expanded,
|
||||
onExpansionChange,
|
||||
}: AgentTranscriptIdentityProps & {
|
||||
item: Extract<TranscriptItem, { type: "tool" }>;
|
||||
profiles?: UserProfileLookup;
|
||||
expanded?: boolean;
|
||||
onExpansionChange?: (expanded: boolean) => void;
|
||||
}) {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const [localExpanded, setLocalExpanded] = React.useState(false);
|
||||
const isExpanded = expanded ?? localExpanded;
|
||||
const hasArgs = Object.keys(item.args).length > 0;
|
||||
const hasResult = item.result.trim().length > 0;
|
||||
const canonicalToolName = item.buzzToolName ?? item.toolName;
|
||||
@@ -52,9 +57,14 @@ export function ToolItem({
|
||||
const agentResolvedAvatarUrl = agentProfile?.avatarUrl ?? agentAvatarUrl;
|
||||
const handleToggle = React.useCallback(
|
||||
(event: React.SyntheticEvent<HTMLDetailsElement>) => {
|
||||
setIsExpanded(event.currentTarget.open);
|
||||
const nextExpanded = event.currentTarget.open;
|
||||
if (onExpansionChange) {
|
||||
onExpansionChange(nextExpanded);
|
||||
} else {
|
||||
setLocalExpanded(nextExpanded);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[onExpansionChange],
|
||||
);
|
||||
|
||||
if (compactSummary.presentation === "message") {
|
||||
|
||||
@@ -533,7 +533,13 @@ function SameKindSummaryItem({
|
||||
// the group card never has to import this module back. Not memoized: the
|
||||
// group card calls it during render and never uses its identity as a
|
||||
// dependency, and memoizing would need this component as its own dep.
|
||||
const renderChild = (child: TranscriptToolRunChildSegment) =>
|
||||
const renderChild = (
|
||||
child: TranscriptToolRunChildSegment,
|
||||
expansion?: {
|
||||
expanded: boolean;
|
||||
onExpansionChange: (expanded: boolean) => void;
|
||||
},
|
||||
) =>
|
||||
child.kind === "summary" ? (
|
||||
<SameKindSummaryItem
|
||||
agentAvatarUrl={agentAvatarUrl}
|
||||
@@ -550,7 +556,9 @@ function SameKindSummaryItem({
|
||||
agentPubkey={agentPubkey}
|
||||
item={child.item}
|
||||
key={child.item.id}
|
||||
onExpansionChange={expansion?.onExpansionChange}
|
||||
profiles={profiles}
|
||||
expanded={expansion?.expanded}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -948,17 +956,23 @@ const TranscriptItemView = React.memo(function TranscriptItemView({
|
||||
agentName,
|
||||
agentPubkey,
|
||||
item,
|
||||
onExpansionChange,
|
||||
profiles,
|
||||
expanded,
|
||||
}: AgentTranscriptIdentityProps & {
|
||||
item: TranscriptItem;
|
||||
profiles?: UserProfileLookup;
|
||||
expanded?: boolean;
|
||||
onExpansionChange?: (expanded: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<TranscriptActivityItem
|
||||
agentAvatarUrl={agentAvatarUrl}
|
||||
agentName={agentName}
|
||||
agentPubkey={agentPubkey}
|
||||
expanded={expanded}
|
||||
item={item}
|
||||
onExpansionChange={onExpansionChange}
|
||||
profiles={profiles}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import {
|
||||
setToolRunGroupExpanded,
|
||||
setToolRunGroupInternalStepsShown,
|
||||
setToolRunGroupItemExpanded,
|
||||
} from "./agentSessionToolRunViewState";
|
||||
import { useToolRunGroupViewState } from "./useToolRunGroupViewState";
|
||||
import { formatTranscriptTimestampTitle } from "./agentSessionUtils";
|
||||
@@ -121,7 +122,13 @@ export type TranscriptToolRunGroupProps = {
|
||||
label: React.ReactNode;
|
||||
showTimestamp: boolean;
|
||||
/** Renders one child segment. Passed in to avoid a list↔group import cycle. */
|
||||
renderChild: (child: TranscriptToolRunChildSegment) => React.ReactNode;
|
||||
renderChild: (
|
||||
child: TranscriptToolRunChildSegment,
|
||||
expansion?: {
|
||||
expanded: boolean;
|
||||
onExpansionChange: (expanded: boolean) => void;
|
||||
},
|
||||
) => React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -200,7 +207,24 @@ export function TranscriptToolRunGroup({
|
||||
{label}
|
||||
<ToolRunGroupStatusBadge failureCount={failureCount} status={status} />
|
||||
<ActivityRowContent className="flex flex-col gap-0.5">
|
||||
{visibleChildren.map((child) => renderChild(child))}
|
||||
{visibleChildren.map((child) =>
|
||||
renderChild(
|
||||
child,
|
||||
child.kind === "item" && child.item.type === "tool"
|
||||
? {
|
||||
expanded: state.expandedItemIds.includes(child.item.id),
|
||||
onExpansionChange: (expanded) =>
|
||||
update((previous) =>
|
||||
setToolRunGroupItemExpanded(
|
||||
previous,
|
||||
child.item.id,
|
||||
expanded,
|
||||
),
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
),
|
||||
)}
|
||||
{hiddenItems.length > 0 ? (
|
||||
<button
|
||||
className="mt-0.5 self-start text-xs text-muted-foreground underline decoration-dotted hover:text-foreground"
|
||||
|
||||
@@ -12,6 +12,9 @@ export type AgentTranscriptIdentityProps = {
|
||||
export type ActivityRenderClassItemProps = AgentTranscriptIdentityProps & {
|
||||
item: TranscriptItem;
|
||||
profiles?: UserProfileLookup;
|
||||
/** Controlled detail expansion for rows whose state must survive regrouping. */
|
||||
expanded?: boolean;
|
||||
onExpansionChange?: (expanded: boolean) => void;
|
||||
};
|
||||
|
||||
export type ActivityRenderClassPresenter =
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
restoreActiveAgentTurnsForCommunity,
|
||||
} from "@/features/agents/activeAgentTurnsStore";
|
||||
import { resetAgentWorkingSignal } from "@/features/agents/agentWorkingSignal";
|
||||
import { resetAgentSessionToolRunViewState } from "@/features/agents/ui/agentSessionToolRunViewStore";
|
||||
import { resetAgentObserverStore } from "@/features/agents/observerRelayStore";
|
||||
import { resetAvatarPresentations } from "@/features/profile/avatarPresentationStore";
|
||||
import { resetAvatarProfileSync } from "@/features/profile/avatarProfileSync";
|
||||
@@ -60,6 +61,7 @@ async function resetCommunityState({
|
||||
resetRateLimitGate();
|
||||
clearAllDrafts();
|
||||
resetAgentObserverStore();
|
||||
resetAgentSessionToolRunViewState();
|
||||
resetActiveAgentTurnsStore();
|
||||
resetAgentWorkingSignal();
|
||||
if (isTauri() && isMacPlatform()) {
|
||||
|
||||
148
desktop/tests/e2e/transcript-tool-run-quality.smoke.spec.ts
Normal file
148
desktop/tests/e2e/transcript-tool-run-quality.smoke.spec.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
||||
|
||||
const AGENT_PUBKEY = TEST_IDENTITIES.tyler.pubkey;
|
||||
const CHANNEL_ID = "94a444a4-c0a3-5966-ab05-530c6ddc2301";
|
||||
const NOW = new Date("2025-06-15T12:00:00Z").toISOString();
|
||||
|
||||
const MANAGED_AGENTS = [
|
||||
{
|
||||
pubkey: AGENT_PUBKEY,
|
||||
name: "Observer Agent",
|
||||
status: "running" as const,
|
||||
channelNames: ["agents"],
|
||||
},
|
||||
];
|
||||
|
||||
async function openFeed(page: import("@playwright/test").Page) {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.waitForFunction(
|
||||
() => typeof window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__ === "function",
|
||||
);
|
||||
await page.getByTestId("channel-agents").click();
|
||||
const row = page
|
||||
.getByTestId("message-row")
|
||||
.filter({ has: page.getByText("Observer Agent", { exact: false }) })
|
||||
.first();
|
||||
await row.getByRole("button").first().click();
|
||||
await page.getByTestId(`user-profile-view-activity-${AGENT_PUBKEY}`).click();
|
||||
return page.getByTestId("agent-session-thread-panel");
|
||||
}
|
||||
|
||||
function event(
|
||||
seq: number,
|
||||
toolCallId: string,
|
||||
update: Record<string, unknown>,
|
||||
) {
|
||||
return {
|
||||
seq,
|
||||
timestamp: NOW,
|
||||
kind: "acp_read",
|
||||
agentIndex: 0,
|
||||
channelId: CHANNEL_ID,
|
||||
sessionId: "session-quality",
|
||||
turnId: "turn-quality",
|
||||
payload: {
|
||||
method: "session/update",
|
||||
params: {
|
||||
sessionId: "session-quality",
|
||||
update: { toolCallId, ...update },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function seed(
|
||||
page: import("@playwright/test").Page,
|
||||
events: ReturnType<typeof event>[],
|
||||
) {
|
||||
await page.evaluate(
|
||||
({ pubkey, events }) =>
|
||||
window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__?.({
|
||||
agentPubkey: pubkey,
|
||||
events,
|
||||
}),
|
||||
{ pubkey: AGENT_PUBKEY, events },
|
||||
);
|
||||
}
|
||||
|
||||
test("tool-run group stays readable through live completion and user expansion", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
||||
const panel = await openFeed(page);
|
||||
|
||||
await seed(page, [
|
||||
event(1, "read-a", {
|
||||
sessionUpdate: "tool_call",
|
||||
status: "executing",
|
||||
title: "read_file",
|
||||
kind: "read_file",
|
||||
rawInput: { path: "src/a.ts" },
|
||||
}),
|
||||
event(2, "read-b", {
|
||||
sessionUpdate: "tool_call",
|
||||
status: "executing",
|
||||
title: "read_file",
|
||||
kind: "read_file",
|
||||
rawInput: { path: "src/b.ts" },
|
||||
}),
|
||||
event(3, "read-c", {
|
||||
sessionUpdate: "tool_call",
|
||||
status: "executing",
|
||||
title: "read_file",
|
||||
kind: "read_file",
|
||||
rawInput: { path: "src/c.ts" },
|
||||
}),
|
||||
]);
|
||||
|
||||
const group = panel.getByTestId("transcript-same-kind-summary");
|
||||
await expect(group).toHaveAttribute("open", "");
|
||||
await expect(
|
||||
group.getByTestId("tool-run-group-status-running"),
|
||||
).toBeVisible();
|
||||
|
||||
await group.locator("summary").first().click();
|
||||
await expect(group).not.toHaveAttribute("open", "");
|
||||
await group.locator("summary").first().click();
|
||||
await expect(group).toHaveAttribute("open", "");
|
||||
|
||||
await seed(page, [
|
||||
event(4, "read-a", {
|
||||
sessionUpdate: "tool_call_update",
|
||||
status: "completed",
|
||||
title: "read_file",
|
||||
kind: "read_file",
|
||||
rawInput: { path: "src/a.ts" },
|
||||
rawOutput: "a",
|
||||
}),
|
||||
event(5, "read-b", {
|
||||
sessionUpdate: "tool_call_update",
|
||||
status: "completed",
|
||||
title: "read_file",
|
||||
kind: "read_file",
|
||||
rawInput: { path: "src/b.ts" },
|
||||
rawOutput: "b",
|
||||
}),
|
||||
event(6, "read-c", {
|
||||
sessionUpdate: "tool_call_update",
|
||||
status: "completed",
|
||||
title: "read_file",
|
||||
kind: "read_file",
|
||||
rawInput: { path: "src/c.ts" },
|
||||
rawOutput: "c",
|
||||
}),
|
||||
]);
|
||||
|
||||
await expect(group).toHaveAttribute("open", "");
|
||||
const child = group
|
||||
.getByTestId("transcript-tool-item")
|
||||
.first()
|
||||
.locator("details");
|
||||
await child.locator("summary").click();
|
||||
await expect(child).toHaveAttribute("open", "");
|
||||
await group.locator("summary").first().click();
|
||||
await group.locator("summary").first().click();
|
||||
await expect(child).toHaveAttribute("open", "");
|
||||
});
|
||||
Reference in New Issue
Block a user