mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Restore agent card status and logs
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user