Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ import {
AuxiliaryPanelHeader,
AuxiliaryPanelHeaderActions,
AuxiliaryPanelHeaderGroup,
AuxiliaryPanelHeaderTitleBlock,
} from "@/shared/layout/AuxiliaryPanel";
import { Button } from "@/shared/ui/button";
import type { UserProfileLookup } from "@/features/profile/lib/identity";
import { resolveUserLabel } from "@/features/profile/lib/identity";
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
import { normalizePubkey } from "@/shared/lib/pubkey";
import {
DropdownMenu,
DropdownMenuContent,
Expand Down Expand Up @@ -237,6 +239,15 @@ export function AgentSessionThreadPanel({
? `#${scopeChannelName}`
: "1 channel"
: "All channels";
const agentProfile = profiles?.[normalizePubkey(agent.pubkey)] ?? null;
const agentLabel = resolveUserLabel({
pubkey: agent.pubkey,
fallbackName: agent.name,
profiles,
preferResolvedSelfLabel: true,
});
const viewLabel = showRawFeed ? "Raw ACP activity" : "Activity";
const headerScopeLabel = `${viewLabel} · ${scopeLabel}`;
const animateActivity = useTranscriptAnimationEnabled();
const showTimestamps = useTranscriptTimestampsEnabled();
async function handleInterruptTurn() {
Expand Down Expand Up @@ -417,19 +428,39 @@ export function AgentSessionThreadPanel({
backButtonTestId="agent-session-back"
onBack={onBack}
>
<AuxiliaryPanelHeaderTitleBlock
subtitle={lastUpdatedLabel}
subtitleTitle={lastUpdatedTitle}
title={showRawFeed ? "Raw ACP Activity" : "Activity"}
<ProfileAvatar
avatarUrl={agentProfile?.avatarUrl ?? null}
className="size-9"
label={agentLabel}
testId="agent-session-agent-avatar"
/>
{/* Scope label: makes channel-targeted vs all-channels state obvious
(an all-channels pane can look "wrong" without it). */}
<span
className="min-w-0 shrink truncate text-xs text-muted-foreground"
data-testid="agent-session-scope-label"
>
{scopeLabel}
</span>
<div className="min-w-0 flex-1">
<h2
className="truncate text-sm font-semibold leading-5"
data-testid="agent-session-agent-name"
title={agentLabel}
>
{agentLabel}
</h2>
<div className="flex min-w-0 items-center gap-1 text-xs text-muted-foreground">
<p
className="min-w-0 flex-1 truncate"
data-testid="agent-session-scope-label"
>
{headerScopeLabel}
</p>
<span aria-hidden="true" className="shrink-0">
·
</span>
<span
className="shrink-0"
data-testid="agent-session-recency-label"
title={lastUpdatedTitle}
>
{lastUpdatedLabel}
</span>
</div>
</div>
</AuxiliaryPanelHeaderGroup>
{agentHeaderActions}
</>
Expand Down
73 changes: 68 additions & 5 deletions desktop/tests/e2e/activity-scope-label-screenshots.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
const SHOTS = "test-results/activity-scope-label";

const AGENT_PUBKEY = TEST_IDENTITIES.tyler.pubkey;
const LONG_AGENT_NAME =
"Observer Agent With An Exceptionally Long Display Name";
const AGENTS_CHANNEL_ID = "94a444a4-c0a3-5966-ab05-530c6ddc2301"; // #agents

// Open the activity pane via profile → "View activity" (same ingress the
Expand Down Expand Up @@ -57,9 +59,36 @@ test.describe("activity panel scope label", () => {
"channel-agents",
"agents",
);
await expect(page.getByTestId("agent-session-scope-label")).toHaveText(
"#agents",
await expect(page.getByTestId("agent-session-agent-name")).toHaveText(
"Observer Agent",
);
await expect(page.getByTestId("agent-session-agent-avatar")).toBeVisible();
const scope = page.getByTestId("agent-session-scope-label");
await expect(scope).toHaveText("Activity · #agents");
const recency = page.getByTestId("agent-session-recency-label");
await expect(recency).toHaveText("No updates yet");
await expect(recency).toBeVisible();

// Simulate long-scope pressure deterministically: scope owns truncation
// while the independently shrinking recency stays visible.
await scope.evaluate((element) => {
element.style.width = "5rem";
element.style.flex = "none";
});
expect(
await scope.evaluate((element) => element.scrollWidth),
).toBeGreaterThan(await scope.evaluate((element) => element.clientWidth));
await expect(recency).toBeVisible();

await page.getByTestId("agent-session-settings-menu-trigger").click();
await page.getByTestId("agent-session-toggle-raw-feed").click();
await expect(scope).toHaveText("Raw ACP activity · #agents");
expect(
await scope.evaluate((element) => element.scrollWidth),
).toBeGreaterThan(await scope.evaluate((element) => element.clientWidth));
await expect(recency).toHaveText("No updates yet");
await expect(recency).toBeVisible();
await page.keyboard.press("Escape");

await waitForAnimations(page);
await panel.screenshot({ path: `${SHOTS}/01-channel-scoped.png` });
Expand All @@ -75,11 +104,18 @@ test.describe("activity panel scope label", () => {
managedAgents: [
{
pubkey: AGENT_PUBKEY,
name: "Observer Agent",
name: LONG_AGENT_NAME,
status: "running" as const,
channelNames: ["random"],
},
],
searchProfiles: [
{
pubkey: AGENT_PUBKEY,
displayName: LONG_AGENT_NAME,
isAgent: true,
},
],
});

await page.goto(
Expand All @@ -90,9 +126,36 @@ test.describe("activity panel scope label", () => {

const panel = page.getByTestId("agent-session-thread-panel");
await expect(panel).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("agent-session-scope-label")).toHaveText(
"All channels",
await expect(page.getByTestId("agent-session-agent-name")).toHaveText(
LONG_AGENT_NAME,
);
const agentName = page.getByTestId("agent-session-agent-name");
expect(
await agentName.evaluate((element) => element.scrollWidth),
).toBeGreaterThan(
await agentName.evaluate((element) => element.clientWidth),
);
const scope = page.getByTestId("agent-session-scope-label");
await expect(scope).toHaveText("Activity · All channels");
const recency = page.getByTestId("agent-session-recency-label");
await expect(recency).toHaveText("No updates yet");
await expect(recency).toBeVisible();

await page.setViewportSize({ width: 720, height: 700 });
await expect(scope).toBeVisible();
await expect(recency).toBeVisible();
const recencyWidth = await recency.evaluate(
(element) => element.clientWidth,
);

await page.getByTestId("agent-session-settings-menu-trigger").click();
await page.getByTestId("agent-session-toggle-raw-feed").click();
await expect(scope).toHaveText("Raw ACP activity · All channels");
await expect(recency).toBeVisible();
expect(await recency.evaluate((element) => element.clientWidth)).toBe(
recencyWidth,
);
await page.keyboard.press("Escape");

await waitForAnimations(page);
await panel.screenshot({ path: `${SHOTS}/02-all-channels.png` });
Expand Down