Restore agent card status and logs

This commit is contained in:
klopez4212
2026-06-25 19:07:19 +01:00
parent a9e808375a
commit ae2f3a2e3b
7 changed files with 125 additions and 17 deletions
@@ -12,6 +12,7 @@ type AgentIdentityCardProps = {
label: string;
modelLabel: string;
onClick: () => void;
status?: ReactNode;
};
export function AgentIdentityCard({
@@ -22,6 +23,7 @@ export function AgentIdentityCard({
label,
modelLabel,
onClick,
status,
}: AgentIdentityCardProps) {
const trimmedAvatarUrl = avatarUrl?.trim() || null;
@@ -56,6 +58,12 @@ export function AgentIdentityCard({
<div className="absolute top-3 right-3 z-40">{actions}</div>
) : null}
{status ? (
<div className="absolute top-3 left-3 z-30 flex max-w-[calc(100%-4rem)] flex-wrap items-center gap-1.5">
{status}
</div>
) : null}
<div className="absolute right-3 bottom-3 left-3 z-30 flex min-w-0 flex-col gap-0.5 text-left text-sm leading-5">
<span className="min-w-0 truncate font-semibold text-foreground tracking-normal">
{label}
@@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import { Link, Users } from "lucide-react";
import { Info, Link, Users } from "lucide-react";
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
import type { AgentPersona } from "@/shared/api/types";
@@ -28,6 +28,7 @@ export function TeamIdentityCard({
actions,
children,
dataTestId,
description,
isSymlink = false,
memberCount,
personas,
@@ -37,6 +38,7 @@ export function TeamIdentityCard({
version,
}: TeamIdentityCardProps) {
const footerModelLabel = getTeamFooterModelLabel(personas);
const trimmedDescription = description?.trim();
return (
<Card
@@ -62,6 +64,22 @@ export function TeamIdentityCard({
v{version}
</span>
) : null}
{trimmedDescription ? (
<Tooltip>
<TooltipTrigger asChild>
<button
aria-label={`${teamName} description`}
className="flex h-6 w-6 items-center justify-center rounded-full border border-border/65 bg-background/90 text-muted-foreground shadow-xs"
type="button"
>
<Info className="h-3.5 w-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
<p>{trimmedDescription}</p>
</TooltipContent>
</Tooltip>
) : null}
</div>
<div className="absolute top-3 right-3 z-40">{actions}</div>
@@ -15,7 +15,9 @@ import {
} from "lucide-react";
import { toast } from "sonner";
import { useActiveAgentTurns } from "@/features/agents/activeAgentTurnsStore";
import { isManagedAgentActive } from "@/features/agents/lib/managedAgentControlActions";
import { AgentStatusBadge } from "@/features/agents/ui/AgentStatusBadge";
import { useUserProfileQuery } from "@/features/profile/hooks";
import type {
AgentPersona,
@@ -24,6 +26,7 @@ import type {
} from "@/shared/api/types";
import { useFeedbackToasts } from "@/shared/hooks/useToastEffect";
import { useFileImportZone } from "@/shared/hooks/useFileImportZone";
import { normalizePubkey } from "@/shared/lib/pubkey";
import { Button } from "@/shared/ui/button";
import {
DropdownMenu,
@@ -36,6 +39,7 @@ import { IdentityCardSkeleton } from "@/shared/ui/identity-card-skeleton";
import { AgentIdentityCard } from "./AgentIdentityCard";
import { CreateIdentityCard } from "./CreateIdentityCard";
import { EditAgentDialog } from "./EditAgentDialog";
import { ManagedAgentLogPanel } from "./ManagedAgentLogPanel";
type UnifiedAgentsSectionProps = {
actionErrorMessage: string | null;
@@ -121,6 +125,12 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
agentsError,
isActionPending,
isAgentsLoading,
logContent,
logError,
logLoading,
presenceLoaded,
presenceLookup,
selectedLogAgentPubkey,
onAddToChannel,
onBulkRemoveStopped,
onBulkStopRunning,
@@ -170,6 +180,14 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
}
return additional;
}, [groups]);
const selectedLogAgent = React.useMemo(
() =>
selectedLogAgentPubkey
? (agents.find((agent) => agent.pubkey === selectedLogAgentPubkey) ??
null)
: null,
[agents, selectedLogAgentPubkey],
);
const [collapsed, setCollapsed] = React.useState<Set<string>>(new Set());
const {
fileInputRef,
@@ -249,6 +267,8 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
key={group.persona.id}
persona={group.persona}
personaMenuProps={personaMenuProps}
presenceLoaded={presenceLoaded}
presenceLookup={presenceLookup}
onOpenAgentProfile={onOpenAgentProfile}
/>
);
@@ -270,6 +290,8 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
collapsed={collapsed}
groupKey="__additional_persona_agents__"
label="Additional agent instances"
presenceLoaded={presenceLoaded}
presenceLookup={presenceLookup}
onToggle={toggle}
onOpenAgentProfile={onOpenAgentProfile}
/>
@@ -281,6 +303,8 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
collapsed={collapsed}
groupKey="__unknown__"
label="Unknown Persona"
presenceLoaded={presenceLoaded}
presenceLookup={presenceLookup}
onToggle={toggle}
onOpenAgentProfile={onOpenAgentProfile}
/>
@@ -292,10 +316,25 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
collapsed={collapsed}
groupKey="__ungrouped__"
label="Custom Agents"
presenceLoaded={presenceLoaded}
presenceLookup={presenceLookup}
onToggle={toggle}
onOpenAgentProfile={onOpenAgentProfile}
/>
) : null}
{selectedLogAgent ? (
<div
className={AGENT_CARD_COLUMN_CLASS}
data-testid="managed-agent-log-row"
>
<ManagedAgentLogPanel
error={logError}
isLoading={logLoading}
logContent={logContent}
selectedAgent={selectedLogAgent}
/>
</div>
) : null}
</div>
) : null}
@@ -371,12 +410,16 @@ function AgentPersonaCard({
agentMenuProps,
persona,
personaMenuProps,
presenceLoaded,
presenceLookup,
onOpenAgentProfile,
}: {
agent: ManagedAgent | undefined;
agentMenuProps: AgentMenuProps;
persona: AgentPersona;
personaMenuProps: PersonaMenuProps;
presenceLoaded: boolean;
presenceLookup: PresenceLookup;
onOpenAgentProfile?: (pubkey: string) => void;
}) {
const title = persona.displayName;
@@ -408,6 +451,15 @@ function AgentPersonaCard({
}
personaMenuProps.onEditPersona(persona);
}}
status={
agent ? (
<AgentCardStatus
agent={agent}
presenceLoaded={presenceLoaded}
presenceLookup={presenceLookup}
/>
) : null
}
/>
);
}
@@ -415,10 +467,14 @@ function AgentPersonaCard({
function StandaloneAgentCard({
agent,
agentMenuProps,
presenceLoaded,
presenceLookup,
onOpenAgentProfile,
}: {
agent: ManagedAgent;
agentMenuProps: AgentMenuProps;
presenceLoaded: boolean;
presenceLookup: PresenceLookup;
onOpenAgentProfile?: (pubkey: string) => void;
}) {
const title = agent.name;
@@ -439,6 +495,35 @@ function StandaloneAgentCard({
agentMenuProps.onOpenLogs(agent.pubkey);
}
}}
status={
<AgentCardStatus
agent={agent}
presenceLoaded={presenceLoaded}
presenceLookup={presenceLookup}
/>
}
/>
);
}
function AgentCardStatus({
agent,
presenceLoaded,
presenceLookup,
}: {
agent: ManagedAgent;
presenceLoaded: boolean;
presenceLookup: PresenceLookup;
}) {
const activeTurns = useActiveAgentTurns(agent.pubkey);
const presenceStatus = presenceLookup[normalizePubkey(agent.pubkey)];
return (
<AgentStatusBadge
isWorking={activeTurns.length > 0}
presenceLoaded={presenceLoaded}
presenceStatus={presenceStatus}
status={agent.status}
/>
);
}
@@ -465,6 +550,9 @@ function AgentPersonaActionsMenu({
<button
aria-label={`Open actions for ${persona.displayName}`}
className="flex h-7 w-7 items-center justify-center rounded-md bg-background/70 text-muted-foreground transition-colors hover:bg-background hover:text-foreground data-[state=open]:bg-background data-[state=open]:text-foreground"
data-testid={
agent ? `managed-agent-actions-${agent.pubkey}` : undefined
}
type="button"
>
<Ellipsis className="h-4 w-4" />
@@ -874,6 +962,8 @@ function CollapsibleAgentGroup({
agents,
agentMenuProps,
collapsed,
presenceLoaded,
presenceLookup,
onToggle,
onOpenAgentProfile,
}: {
@@ -882,6 +972,8 @@ function CollapsibleAgentGroup({
agents: ManagedAgent[];
agentMenuProps: AgentMenuProps;
collapsed: ReadonlySet<string>;
presenceLoaded: boolean;
presenceLookup: PresenceLookup;
onToggle: (key: string) => void;
onOpenAgentProfile?: (pubkey: string) => void;
}) {
@@ -908,6 +1000,8 @@ function CollapsibleAgentGroup({
agent={agent}
agentMenuProps={agentMenuProps}
key={agent.pubkey}
presenceLoaded={presenceLoaded}
presenceLookup={presenceLookup}
onOpenAgentProfile={onOpenAgentProfile}
/>
))}
+1 -4
View File
@@ -30,10 +30,7 @@ async function gotoApp(page: import("@playwright/test").Page) {
}
async function openPersonaCatalog(page: import("@playwright/test").Page) {
await page
.getByTestId("agents-library-personas")
.getByRole("button", { name: "New", exact: true })
.click();
await page.getByTestId("new-agent-card").click();
await page.getByText("Choose from Catalog...").click();
}
+1 -4
View File
@@ -76,10 +76,7 @@ async function openManagedAgentActions(
}
async function openNewAgentMenu(page: import("@playwright/test").Page) {
await page
.getByTestId("agents-library-personas")
.getByRole("button", { name: "New", exact: true })
.click();
await page.getByTestId("new-agent-card").click();
}
test.beforeEach(async ({ page }) => {
+1 -4
View File
@@ -223,10 +223,7 @@ test("env vars editor renders in PersonaDialog new-persona form", async ({
// Open the Agents view, click New > Persona to open the persona dialog.
await page.getByTestId("open-agents-view").click();
await page
.getByTestId("agents-library-personas")
.getByRole("button", { name: "New", exact: true })
.click();
await page.getByTestId("new-agent-card").click();
await page.getByRole("menuitem", { name: /^Persona$/ }).click();
// The env vars editor should be present.
+1 -4
View File
@@ -115,10 +115,7 @@ test("create agent supports parallelism and system prompt overrides", async ({
await page.goto("/");
await page.getByTestId("open-agents-view").click();
await page
.getByTestId("agents-library-personas")
.getByRole("button", { name: "New", exact: true })
.click();
await page.getByTestId("new-agent-card").click();
await page.getByText("Custom Agent").click();
await page.getByTestId("agent-name-input").fill(agentName);