mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Remove additional agents gallery (#2098)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -99,18 +99,6 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
|
||||
() => buildUnifiedGroups(personas, agents),
|
||||
[personas, agents],
|
||||
);
|
||||
const additionalPersonaAgents = React.useMemo(() => {
|
||||
const additional: ManagedAgent[] = [];
|
||||
for (const group of groups) {
|
||||
const primary = pickProfileAgent(group.agents);
|
||||
for (const agent of group.agents) {
|
||||
if (primary?.pubkey !== agent.pubkey) {
|
||||
additional.push(agent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return additional;
|
||||
}, [groups]);
|
||||
const [collapsed, setCollapsed] = React.useState<Set<string>>(new Set());
|
||||
const {
|
||||
fileInputRef,
|
||||
@@ -199,19 +187,6 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{additionalPersonaAgents.length > 0 ? (
|
||||
<CollapsibleAgentGroup
|
||||
agents={additionalPersonaAgents}
|
||||
collapsed={collapsed}
|
||||
defaultModel={defaultModel}
|
||||
groupKey="__additional_persona_agents__"
|
||||
label="Additional running agents"
|
||||
startingAgentPubkey={startingAgentPubkey}
|
||||
onToggle={toggle}
|
||||
onOpenAgentProfile={onOpenAgentProfile}
|
||||
onStartAgent={onStartAgent}
|
||||
/>
|
||||
) : null}
|
||||
{unknown.length > 0 ? (
|
||||
<CollapsibleAgentGroup
|
||||
agents={unknown}
|
||||
|
||||
@@ -199,6 +199,12 @@ export function UserProfilePanel({
|
||||
}
|
||||
return undefined;
|
||||
}, [managedAgentsQuery.data, persona, pubkey]);
|
||||
const personaInstances = React.useMemo(() => {
|
||||
if (!managedAgent?.personaId) return managedAgent ? [managedAgent] : [];
|
||||
return (managedAgentsQuery.data ?? []).filter(
|
||||
(agent) => agent.personaId === managedAgent.personaId,
|
||||
);
|
||||
}, [managedAgent, managedAgentsQuery.data]);
|
||||
const resolvedPersonaFromSource = React.useMemo(() => {
|
||||
const personaId = persona?.id ?? managedAgent?.personaId;
|
||||
if (personaId) {
|
||||
@@ -833,6 +839,7 @@ export function UserProfilePanel({
|
||||
isFollowing={isFollowing}
|
||||
isOwner={viewerIsOwner}
|
||||
isSelf={isSelf}
|
||||
instances={personaInstances}
|
||||
activityAgent={activityAgent}
|
||||
managedAgent={managedAgent}
|
||||
memoriesLoading={memoryQuery.isLoading}
|
||||
@@ -841,6 +848,7 @@ export function UserProfilePanel({
|
||||
agentSettingsFields={agentSettingsFields}
|
||||
diagnosticsFields={diagnosticsFields}
|
||||
onAddToChannel={() => setAddToChannelOpen(true)}
|
||||
onOpenInstance={(instancePubkey) => onOpenProfile?.(instancePubkey)}
|
||||
onOpenActivity={handleOpenActivity}
|
||||
onOpenChannel={handleOpenChannel}
|
||||
onOpenDiagnostics={() => setView("diagnostics")}
|
||||
|
||||
@@ -88,6 +88,7 @@ export type ProfileSummaryViewProps = {
|
||||
isFollowing: boolean;
|
||||
isOwner: boolean | undefined;
|
||||
isSelf: boolean;
|
||||
instances: ManagedAgent[];
|
||||
managedAgent: ManagedAgent | undefined;
|
||||
memoriesLoading: boolean;
|
||||
memoryCount: number | undefined;
|
||||
@@ -95,6 +96,7 @@ export type ProfileSummaryViewProps = {
|
||||
agentSettingsFields: ProfileField[];
|
||||
diagnosticsFields: ProfileField[];
|
||||
onAddToChannel: () => void;
|
||||
onOpenInstance: (pubkey: string) => void;
|
||||
onOpenActivity: (channelId?: string | null) => void;
|
||||
onOpenChannel: (channelId: string) => void;
|
||||
onOpenDiagnostics: () => void;
|
||||
@@ -198,6 +200,7 @@ export function ProfileSummaryView({
|
||||
isFollowing,
|
||||
isOwner,
|
||||
isSelf,
|
||||
instances,
|
||||
managedAgent,
|
||||
memoriesLoading,
|
||||
memoryCount,
|
||||
@@ -205,6 +208,7 @@ export function ProfileSummaryView({
|
||||
agentSettingsFields,
|
||||
diagnosticsFields,
|
||||
onAddToChannel,
|
||||
onOpenInstance,
|
||||
onOpenActivity,
|
||||
onOpenChannel,
|
||||
onOpenDiagnostics,
|
||||
@@ -248,6 +252,7 @@ export function ProfileSummaryView({
|
||||
const showActivityIngress = canViewActivity;
|
||||
const showInfoTab =
|
||||
agentInfoFields.length > 0 ||
|
||||
instances.length > 1 ||
|
||||
isArchived ||
|
||||
showActivityIngress ||
|
||||
!showRuntimeTab;
|
||||
@@ -388,8 +393,10 @@ export function ProfileSummaryView({
|
||||
agentInfoFields={agentInfoFields}
|
||||
callerChannelId={callerChannelId}
|
||||
channelIdToName={channelIdToName}
|
||||
instances={instances}
|
||||
isArchived={isArchived}
|
||||
onOpenActivity={onOpenActivity}
|
||||
onOpenInstance={onOpenInstance}
|
||||
pubkey={pubkey}
|
||||
showActivityIngress={showActivityIngress}
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
|
||||
import type { ManagedAgent } from "@/shared/api/types";
|
||||
import type { ActiveTurnSummary } from "@/features/agents/activeAgentTurnsStore";
|
||||
import { ManagedAgentSessionPanel } from "@/features/agents/ui/ManagedAgentSessionPanel";
|
||||
import {
|
||||
@@ -282,8 +283,10 @@ export function ProfileInfoTabContent({
|
||||
agentInfoFields,
|
||||
callerChannelId,
|
||||
channelIdToName,
|
||||
instances,
|
||||
isArchived,
|
||||
onOpenActivity,
|
||||
onOpenInstance,
|
||||
pubkey,
|
||||
showActivityIngress,
|
||||
}: {
|
||||
@@ -292,8 +295,10 @@ export function ProfileInfoTabContent({
|
||||
agentInfoFields: ProfileField[];
|
||||
callerChannelId: string | null;
|
||||
channelIdToName: Record<string, string>;
|
||||
instances: ManagedAgent[];
|
||||
isArchived: boolean;
|
||||
onOpenActivity: (channelId?: string | null) => void;
|
||||
onOpenInstance: (pubkey: string) => void;
|
||||
pubkey: string | null;
|
||||
showActivityIngress: boolean;
|
||||
}) {
|
||||
@@ -310,11 +315,12 @@ export function ProfileInfoTabContent({
|
||||
]
|
||||
: agentInfoFields;
|
||||
const hasInfoFields = infoFields.length > 0;
|
||||
const hasInstances = instances.length > 1;
|
||||
const feedScope = useProfileActivityFeedScope(activityAgent, activeTurns);
|
||||
const showLiveActivityEmbed =
|
||||
showActivityIngress && (feedScope.isLive || feedScope.hasFeedContent);
|
||||
|
||||
if (!hasInfoFields && !showActivityIngress) {
|
||||
if (!hasInfoFields && !showActivityIngress && !hasInstances) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -341,6 +347,72 @@ export function ProfileInfoTabContent({
|
||||
)
|
||||
) : null}
|
||||
{hasInfoFields ? <ProfileFieldGroup fields={infoFields} /> : null}
|
||||
{hasInstances ? (
|
||||
<ProfileInstancesSection
|
||||
currentPubkey={pubkey}
|
||||
instances={instances}
|
||||
onOpenInstance={onOpenInstance}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileInstancesSection({
|
||||
currentPubkey,
|
||||
instances,
|
||||
onOpenInstance,
|
||||
}: {
|
||||
currentPubkey: string | null;
|
||||
instances: ManagedAgent[];
|
||||
onOpenInstance: (pubkey: string) => void;
|
||||
}) {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-2xl bg-muted/20">
|
||||
<button
|
||||
aria-expanded={expanded}
|
||||
className="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/40"
|
||||
data-testid="user-profile-instances"
|
||||
onClick={() => setExpanded((value) => !value)}
|
||||
type="button"
|
||||
>
|
||||
<span className="min-w-0 flex-1 text-sm font-medium">Instances</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{instances.length}
|
||||
</span>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
"h-4 w-4 shrink-0 text-muted-foreground transition-transform",
|
||||
expanded && "rotate-90",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
{expanded ? (
|
||||
<div className="border-t border-border/60 px-2 py-2">
|
||||
{instances.map((instance) => {
|
||||
const isCurrent = instance.pubkey === currentPubkey;
|
||||
return (
|
||||
<button
|
||||
className="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left transition-colors hover:bg-muted/40"
|
||||
data-testid={`user-profile-instance-${instance.pubkey}`}
|
||||
key={instance.pubkey}
|
||||
onClick={() => onOpenInstance(instance.pubkey)}
|
||||
type="button"
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate text-sm font-medium">
|
||||
{instance.name}
|
||||
</span>
|
||||
<span className="text-xs capitalize text-muted-foreground">
|
||||
{isCurrent ? "Current" : instance.status.replace("_", " ")}
|
||||
</span>
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1627,3 +1627,51 @@ test("personas referenced by teams cannot be deleted", async ({ page }) => {
|
||||
"Analyst is still referenced by a team. Remove it from those teams first.",
|
||||
);
|
||||
});
|
||||
|
||||
test("duplicate instances move from the agents gallery into the agent profile", async ({
|
||||
page,
|
||||
}) => {
|
||||
const personaId = "custom:duplicate-auditor";
|
||||
const primaryPubkey = TEST_IDENTITIES.alice.pubkey;
|
||||
const additionalPubkey = TEST_IDENTITIES.charlie.pubkey;
|
||||
await installMockBridge(page, {
|
||||
personas: [
|
||||
{
|
||||
id: personaId,
|
||||
displayName: "Duplicate Auditor",
|
||||
systemPrompt: "You audit duplicate instances.",
|
||||
},
|
||||
],
|
||||
managedAgents: [
|
||||
{
|
||||
pubkey: primaryPubkey,
|
||||
name: "Duplicate Auditor",
|
||||
personaId,
|
||||
status: "running",
|
||||
},
|
||||
{
|
||||
pubkey: additionalPubkey,
|
||||
name: "Duplicate Auditor",
|
||||
personaId,
|
||||
status: "stopped",
|
||||
},
|
||||
],
|
||||
});
|
||||
await gotoApp(page);
|
||||
await page.getByTestId("open-agents-view").click();
|
||||
|
||||
await expect(page.getByText("Additional running agents")).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByTestId(`managed-agent-${additionalPubkey}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
await page.getByTestId(`persona-agent-row-${personaId}`).click();
|
||||
await page.getByTestId("user-profile-instances").click();
|
||||
await page.getByTestId(`user-profile-instance-${additionalPubkey}`).click();
|
||||
|
||||
await expect(page.getByTestId("user-profile-panel")).toBeVisible();
|
||||
await page.getByTestId("user-profile-settings-menu-trigger").click();
|
||||
await expect(
|
||||
page.getByTestId(`user-profile-agent-delete-${additionalPubkey}`),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user