Show agent avatars in activity log

Use the selected agent profile avatar in the live activity panel header and assistant transcript rows for clearer session context.

Made-with: Cursor
This commit is contained in:
Thomas Petersen
2026-04-30 11:22:23 -04:00
parent a7953ff80a
commit 3d1e618384
4 changed files with 47 additions and 8 deletions
@@ -1,15 +1,18 @@
import { Bot, Brain, ChevronDown, Radio, TerminalSquare } from "lucide-react";
import { Brain, ChevronDown, Radio, TerminalSquare } from "lucide-react";
import { cn } from "@/shared/lib/cn";
import { Markdown } from "@/shared/ui/markdown";
import { UserAvatar } from "@/shared/ui/UserAvatar";
import type { TranscriptItem } from "./agentSessionTypes";
import { ToolItem } from "./AgentSessionToolItem";
export function AgentSessionTranscriptList({
agentAvatarUrl,
agentName,
emptyDescription,
items,
}: {
agentAvatarUrl: string | null;
agentName: string;
emptyDescription: string;
items: TranscriptItem[];
@@ -33,7 +36,11 @@ export function AgentSessionTranscriptList({
>
{items.map((item) => (
<div key={item.id}>
<TranscriptItemView agentName={agentName} item={item} />
<TranscriptItemView
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
item={item}
/>
</div>
))}
</div>
@@ -41,14 +48,22 @@ export function AgentSessionTranscriptList({
}
function TranscriptItemView({
agentAvatarUrl,
agentName,
item,
}: {
agentAvatarUrl: string | null;
agentName: string;
item: TranscriptItem;
}) {
if (item.type === "message") {
return <MessageItem agentName={agentName} item={item} />;
return (
<MessageItem
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
item={item}
/>
);
}
if (item.type === "tool") {
return <ToolItem item={item} />;
@@ -63,9 +78,11 @@ function TranscriptItemView({
}
function MessageItem({
agentAvatarUrl,
agentName,
item,
}: {
agentAvatarUrl: string | null;
agentName: string;
item: Extract<TranscriptItem, { type: "message" }>;
}) {
@@ -80,9 +97,12 @@ function MessageItem({
<div className="group relative flex min-w-0 flex-1 flex-col items-start gap-1">
<div className="flex items-center gap-1.5 text-xs">
{isAssistant ? (
<span className="flex h-5 w-5 items-center justify-center">
<Bot className="h-3.5 w-3.5 text-muted-foreground" />
</span>
<UserAvatar
avatarUrl={agentAvatarUrl}
className="rounded-full shadow-none"
displayName={agentName}
size="xs"
/>
) : null}
<span className="font-medium text-foreground">{label}</span>
</div>
@@ -25,6 +25,7 @@ import { useObserverEvents } from "./useObserverEvents";
type ManagedAgentSessionPanelProps = {
agent: ManagedAgent;
agentAvatarUrl?: string | null;
channelId?: string | null;
className?: string;
emptyDescription?: string;
@@ -34,6 +35,7 @@ type ManagedAgentSessionPanelProps = {
export function ManagedAgentSessionPanel({
agent,
agentAvatarUrl = null,
channelId = null,
className,
emptyDescription = "Mention this agent in a channel to watch the next turn.",
@@ -78,6 +80,7 @@ export function ManagedAgentSessionPanel({
) : null}
<SessionBody
agentAvatarUrl={agentAvatarUrl}
agentName={agent.name}
connectionState={connectionState}
emptyDescription={emptyDescription}
@@ -127,6 +130,7 @@ function SessionHeader({
}
function SessionBody({
agentAvatarUrl,
agentName,
connectionState,
emptyDescription,
@@ -136,6 +140,7 @@ function SessionBody({
showRaw,
transcript,
}: {
agentAvatarUrl: string | null;
agentName: string;
connectionState: ConnectionState;
emptyDescription: string;
@@ -160,6 +165,7 @@ function SessionBody({
)}
>
<AgentSessionTranscriptList
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
emptyDescription={emptyDescription}
items={transcript}
@@ -1,14 +1,16 @@
import type * as React from "react";
import { Activity, Bot, CircleDot, Octagon, X } from "lucide-react";
import { Activity, CircleDot, Octagon, X } from "lucide-react";
import { toast } from "sonner";
import { ManagedAgentSessionPanel } from "@/features/agents/ui/ManagedAgentSessionPanel";
import type { UserProfileLookup } from "@/features/profile/lib/identity";
import { cancelManagedAgentTurn } from "@/shared/api/agentControl";
import type { Channel, ManagedAgent } from "@/shared/api/types";
import { useStickToBottom } from "@/shared/hooks/useStickToBottom";
import { Badge } from "@/shared/ui/badge";
import { Button } from "@/shared/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
import { UserAvatar } from "@/shared/ui/UserAvatar";
type AgentSessionThreadPanelProps = {
agent: ManagedAgent;
@@ -18,6 +20,7 @@ type AgentSessionThreadPanelProps = {
onClose: () => void;
onResetWidth: () => void;
onResizeStart: (event: React.PointerEvent<HTMLButtonElement>) => void;
profiles?: UserProfileLookup;
widthPx: number;
};
@@ -29,10 +32,13 @@ export function AgentSessionThreadPanel({
onClose,
onResetWidth,
onResizeStart,
profiles,
widthPx,
}: AgentSessionThreadPanelProps) {
const isLive = agent.status === "running";
const { ref: scrollRef, onScroll } = useStickToBottom<HTMLDivElement>();
const agentAvatarUrl =
profiles?.[agent.pubkey.toLowerCase()]?.avatarUrl ?? null;
async function handleInterruptTurn() {
try {
@@ -74,7 +80,12 @@ export function AgentSessionThreadPanel({
</button>
<div className="flex items-center gap-3 border-b border-border/70 px-4 py-2.5">
<Bot className="h-4 w-4 shrink-0 text-muted-foreground" />
<UserAvatar
avatarUrl={agentAvatarUrl}
className="shrink-0 rounded-full shadow-none"
displayName={agent.name}
size="sm"
/>
<div className="min-w-0 flex-1">
<h2 className="truncate text-sm font-semibold tracking-tight">
{agent.name}
@@ -141,6 +152,7 @@ export function AgentSessionThreadPanel({
channelId={channel.id}
className="border-0 bg-transparent p-0 shadow-none"
emptyDescription={`Mention ${agent.name} in the channel to see its work here.`}
agentAvatarUrl={agentAvatarUrl}
showHeader={false}
showRaw={false}
/>
@@ -355,6 +355,7 @@ export const ChannelPane = React.memo(function ChannelPane({
onClose={onCloseAgentSession}
onResetWidth={handleThreadPanelWidthReset}
onResizeStart={handleThreadPanelResizeStart}
profiles={profiles}
widthPx={threadPanelWidthPx}
/>
) : null}