Make external agent activity window optional

Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
Taylor Ho
2026-08-23 10:18:02 -07:00
committed by Rizz
parent 96bb026c64
commit 2ab62fd4e8
7 changed files with 364 additions and 255 deletions

View File

@@ -4,6 +4,7 @@ import {
Octagon,
Settings,
Sparkles,
SquareArrowOutUpRight,
TerminalSquare,
} from "lucide-react";
import { toast } from "sonner";
@@ -29,6 +30,7 @@ import {
import { useAnchoredScroll } from "@/features/messages/ui/useAnchoredScroll";
import { useStableArrayShallow } from "@/shared/hooks/useStableReference";
import { cancelManagedAgentTurn } from "@/shared/api/agentControl";
import { openAgentActivityWindow } from "@/shared/api/agentActivityWindow";
import type { Channel } from "@/shared/api/types";
import { useEscapeKey } from "@/shared/hooks/useEscapeKey";
import { useIsThreadPanelOverlay } from "@/shared/hooks/use-mobile";
@@ -257,6 +259,27 @@ export function AgentSessionThreadPanel({
const headerScopeLabel = `${viewLabel} · ${scopeLabel}`;
const animateActivity = useTranscriptAnimationEnabled();
const showTimestamps = useTranscriptTimestampsEnabled();
async function handleOpenExternalWindow() {
if (!sessionChannelId || isDedicatedActivityWindow) {
return;
}
try {
const openedNativeWindow = await openAgentActivityWindow(
sessionChannelId,
agent.pubkey,
);
if (!openedNativeWindow) {
toast.error(
"External activity windows are only available in the desktop app.",
);
}
} catch (error) {
console.error("Failed to open agent activity window:", error);
toast.error("Couldn't open the agent activity window.");
}
}
async function handleInterruptTurn() {
if (!channel) {
return;
@@ -278,152 +301,164 @@ export function AgentSessionThreadPanel({
const agentHeaderActions = (
<AuxiliaryPanelHeaderActions>
{isLive ? (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button
aria-label="Open activity settings"
className="relative"
data-testid="agent-session-settings-menu-trigger"
size="icon"
title="Activity settings"
type="button"
variant="ghost"
>
<Settings />
{canStopCurrentTurn ? (
<span
aria-hidden="true"
className="absolute right-1 bottom-1 h-2 w-2 rounded-full bg-primary ring-2 ring-background"
data-testid="agent-session-settings-live-badge"
/>
) : null}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="min-w-56"
onCloseAutoFocus={(event) => event.preventDefault()}
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button
aria-label="Open activity settings"
className="relative"
data-testid="agent-session-settings-menu-trigger"
size="icon"
title="Activity settings"
type="button"
variant="ghost"
>
<DropdownMenuItem
className="items-start gap-3"
data-testid="agent-session-toggle-raw-feed"
onSelect={(event) => {
event.preventDefault();
handleRawFeedChange(!showRawFeed);
}}
title={
showRawFeed
? "Hide raw JSON-RPC payloads."
: channel
? "Show raw JSON-RPC payloads for this channel."
: "Show raw JSON-RPC payloads for this agent."
}
>
<span className="min-w-0 flex-1">
<span className="flex items-center gap-2 text-sm font-medium">
<TerminalSquare className="h-4 w-4 text-muted-foreground" />
Raw
</span>
<Settings />
{canStopCurrentTurn ? (
<span
aria-hidden="true"
className="absolute right-1 bottom-1 h-2 w-2 rounded-full bg-primary ring-2 ring-background"
data-testid="agent-session-settings-live-badge"
/>
) : null}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="min-w-56"
onCloseAutoFocus={(event) => event.preventDefault()}
>
{!isDedicatedActivityWindow && sessionChannelId ? (
<>
<DropdownMenuItem
data-testid="agent-session-open-external-window"
onSelect={() => {
void handleOpenExternalWindow();
}}
>
<SquareArrowOutUpRight className="text-muted-foreground" />
<span>Open in external window</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
) : null}
<DropdownMenuItem
className="items-start gap-3"
data-testid="agent-session-toggle-raw-feed"
onSelect={(event) => {
event.preventDefault();
handleRawFeedChange(!showRawFeed);
}}
title={
showRawFeed
? "Hide raw JSON-RPC payloads."
: channel
? "Show raw JSON-RPC payloads for this channel."
: "Show raw JSON-RPC payloads for this agent."
}
>
<span className="min-w-0 flex-1">
<span className="flex items-center gap-2 text-sm font-medium">
<TerminalSquare className="h-4 w-4 text-muted-foreground" />
Raw
</span>
<span className="mt-0.5 block text-xs text-muted-foreground">
Show raw JSON-RPC activity.
</span>
</span>
<Switch
aria-hidden="true"
checked={showRawFeed}
className="pointer-events-none mt-0.5"
tabIndex={-1}
/>
</DropdownMenuItem>
<DropdownMenuItem
className="items-start gap-3"
data-testid="agent-session-toggle-animate-activity"
disabled={showRawFeed}
onSelect={(event) => {
event.preventDefault();
setTranscriptAnimationEnabled(!animateActivity);
}}
title={
showRawFeed
? "Raw activity rows don't animate in."
: animateActivity
? "Stop animating new activity rows."
: "Animate new activity rows as they arrive."
}
>
<span className="min-w-0 flex-1">
<span className="flex items-center gap-2 text-sm font-medium">
<Sparkles className="h-4 w-4 text-muted-foreground" />
Show Animations
</span>
</span>
<Switch
aria-hidden="true"
checked={animateActivity && !showRawFeed}
className="pointer-events-none mt-0.5"
tabIndex={-1}
/>
</DropdownMenuItem>
<DropdownMenuItem
className="items-start gap-3"
data-testid="agent-session-toggle-show-timestamps"
onSelect={(event) => {
event.preventDefault();
setTranscriptTimestampsEnabled(!showTimestamps);
}}
title={
showTimestamps
? "Hide per-row activity timestamps."
: "Show a timestamp under each activity row."
}
>
<span className="min-w-0 flex-1">
<span className="flex items-center gap-2 text-sm font-medium">
<Clock3 className="h-4 w-4 text-muted-foreground" />
Show Timestamps
</span>
</span>
<Switch
aria-hidden="true"
checked={showTimestamps}
className="pointer-events-none mt-0.5"
tabIndex={-1}
/>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="items-start gap-3"
data-testid="agent-session-stop-turn"
disabled={!canStopCurrentTurn}
onSelect={() => {
void handleInterruptTurn();
}}
title={
canStopCurrentTurn
? "Interrupt the current ACP turn without stopping the agent process."
: isWorking
? "Only locally managed agents can be interrupted from this community."
: "Available while the agent is working."
}
>
<Octagon className="mt-0.5 h-4 w-4 text-muted-foreground" />
<span className="min-w-0 flex-1">
<span className="block text-sm font-medium">
Stop current turn
</span>
{!canStopCurrentTurn ? (
<span className="mt-0.5 block text-xs text-muted-foreground">
Show raw JSON-RPC activity.
{isWorking
? "Only available for locally managed agents."
: "Available while the agent is working."}
</span>
</span>
<Switch
aria-hidden="true"
checked={showRawFeed}
className="pointer-events-none mt-0.5"
tabIndex={-1}
/>
</DropdownMenuItem>
<DropdownMenuItem
className="items-start gap-3"
data-testid="agent-session-toggle-animate-activity"
disabled={showRawFeed}
onSelect={(event) => {
event.preventDefault();
setTranscriptAnimationEnabled(!animateActivity);
}}
title={
showRawFeed
? "Raw activity rows don't animate in."
: animateActivity
? "Stop animating new activity rows."
: "Animate new activity rows as they arrive."
}
>
<span className="min-w-0 flex-1">
<span className="flex items-center gap-2 text-sm font-medium">
<Sparkles className="h-4 w-4 text-muted-foreground" />
Show Animations
</span>
</span>
<Switch
aria-hidden="true"
checked={animateActivity && !showRawFeed}
className="pointer-events-none mt-0.5"
tabIndex={-1}
/>
</DropdownMenuItem>
<DropdownMenuItem
className="items-start gap-3"
data-testid="agent-session-toggle-show-timestamps"
onSelect={(event) => {
event.preventDefault();
setTranscriptTimestampsEnabled(!showTimestamps);
}}
title={
showTimestamps
? "Hide per-row activity timestamps."
: "Show a timestamp under each activity row."
}
>
<span className="min-w-0 flex-1">
<span className="flex items-center gap-2 text-sm font-medium">
<Clock3 className="h-4 w-4 text-muted-foreground" />
Show Timestamps
</span>
</span>
<Switch
aria-hidden="true"
checked={showTimestamps}
className="pointer-events-none mt-0.5"
tabIndex={-1}
/>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="items-start gap-3"
data-testid="agent-session-stop-turn"
disabled={!canStopCurrentTurn}
onSelect={() => {
void handleInterruptTurn();
}}
title={
canStopCurrentTurn
? "Interrupt the current ACP turn without stopping the agent process."
: isWorking
? "Only locally managed agents can be interrupted from this community."
: "Available while the agent is working."
}
>
<Octagon className="mt-0.5 h-4 w-4 text-muted-foreground" />
<span className="min-w-0 flex-1">
<span className="block text-sm font-medium">
Stop current turn
</span>
{!canStopCurrentTurn ? (
<span className="mt-0.5 block text-xs text-muted-foreground">
{isWorking
? "Only available for locally managed agents."
: "Available while the agent is working."}
</span>
) : null}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : null}
) : null}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</AuxiliaryPanelHeaderActions>
);

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { Loader2 } from "lucide-react";
import { ExternalLink, Loader2 } from "lucide-react";
import { useAgentTranscript } from "@/features/agents/ui/useObserverEvents";
import {
@@ -10,6 +10,12 @@ import {
import type { UserProfileLookup } from "@/features/profile/lib/identity";
import type { ManagedAgent } from "@/shared/api/types";
import { cn } from "@/shared/lib/cn";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "@/shared/ui/context-menu";
import {
DEFAULT_POPOVER_HOVER_OPEN_DELAY_MS,
Popover,
@@ -25,6 +31,10 @@ type BotActivityBarProps = {
agents: BotActivityAgent[];
channelId?: string | null;
onOpenAgentSession: (pubkey: string, channelId?: string | null) => void;
onOpenAgentSessionExternal: (
pubkey: string,
channelId?: string | null,
) => void;
openAgentSessionPubkey: string | null;
profiles?: UserProfileLookup;
workingBotPubkeys: string[];
@@ -38,6 +48,7 @@ export function BotActivityComposerAction({
agents,
channelId = null,
onOpenAgentSession,
onOpenAgentSessionExternal,
openAgentSessionPubkey,
profiles,
workingBotPubkeys,
@@ -161,116 +172,147 @@ export function BotActivityComposerAction({
: `${workingAgents[0]?.name ?? "Agent"} +${workingAgents.length - 1}`;
return (
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild>
<button
aria-label={`${triggerLabel}. View activity.`}
className={cn(
"inline-flex items-center justify-center rounded-full border border-border/60 bg-background font-medium text-muted-foreground transition-colors hover:border-primary/30 hover:bg-primary/5 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring data-[state=open]:border-primary/40 data-[state=open]:bg-primary/10 data-[state=open]:text-primary",
isInline
? "min-w-0 gap-1.5 overflow-visible border-transparent bg-transparent px-0 text-xs font-normal leading-normal shadow-none hover:border-transparent hover:bg-transparent data-[state=open]:border-transparent data-[state=open]:bg-transparent"
: "h-9 min-w-9 gap-1.5 px-2 text-xs",
)}
data-testid="bot-activity-composer-trigger"
onBlur={closeWithDelay}
onClick={() => {
clearHoverTimer();
setOpen((current) => !current);
}}
onFocus={() => setOpen(true)}
onMouseEnter={openWithDelay}
onMouseLeave={closeWithDelay}
type="button"
>
<span className="flex h-4.5 items-center overflow-visible -space-x-1">
{workingAgents.slice(0, 2).map((agent) => (
<UserAvatar
avatarUrl={agentAvatarUrl(agent)}
className={cn(
"border border-background",
isInline ? "!h-4.5 !w-4.5 text-3xs" : "shrink-0",
)}
displayName={agent.name}
fallbackDelayMs={isInline ? 0 : undefined}
key={agent.pubkey}
size="xs"
/>
))}
</span>
{workingAgents.length > 2 ? (
<span className="text-2xs leading-none">
+{workingAgents.length - 2}
</span>
) : null}
<span
className={cn(
isInline
? "flex h-4.5 min-w-0 flex-1 items-center overflow-visible leading-none"
: "sr-only",
)}
>
{isInline ? (
<Shimmer className="-my-px truncate py-px">
{visibleStatusLabel}
</Shimmer>
) : (
"working"
)}
</span>
{isInline ? null : (
<Loader2 className="h-4 w-4 shrink-0 animate-spin opacity-70" />
)}
</button>
</PopoverTrigger>
<PopoverContent
align={isInline ? "start" : "end"}
className="w-64 p-1"
onMouseEnter={keepOpen}
onMouseLeave={closeWithDelay}
onOpenAutoFocus={(event) => event.preventDefault()}
side="top"
sideOffset={8}
>
<div className="px-2 py-1 text-xs font-medium text-muted-foreground">
Agents working
</div>
<div className="mt-1 flex flex-col gap-1">
{workingAgents.map((agent) => {
const isSelected = selectedPubkey === agent.pubkey.toLowerCase();
return (
<button
className={cn(
"flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors",
isSelected
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent hover:text-accent-foreground",
)}
data-testid={`bot-activity-composer-item-${agent.pubkey}`}
key={agent.pubkey}
onClick={() => {
clearHoverTimer();
setOpen(false);
onOpenAgentSession(agent.pubkey, channelId);
}}
type="button"
>
<UserAvatar
avatarUrl={agentAvatarUrl(agent)}
className="shrink-0"
displayName={agent.name}
size="sm"
/>
<span className="min-w-0 flex-1 truncate">{agent.name}</span>
<span className="shrink-0 whitespace-nowrap text-xs font-medium opacity-80">
View activity
<ContextMenu>
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild>
<ContextMenuTrigger asChild>
<button
aria-label={`${triggerLabel}. View activity.`}
className={cn(
"inline-flex items-center justify-center rounded-full border border-border/60 bg-background font-medium text-muted-foreground transition-colors hover:border-primary/30 hover:bg-primary/5 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring data-[state=open]:border-primary/40 data-[state=open]:bg-primary/10 data-[state=open]:text-primary",
isInline
? "min-w-0 gap-1.5 overflow-visible border-transparent bg-transparent px-0 text-xs font-normal leading-normal shadow-none hover:border-transparent hover:bg-transparent data-[state=open]:border-transparent data-[state=open]:bg-transparent"
: "h-9 min-w-9 gap-1.5 px-2 text-xs",
)}
data-testid="bot-activity-composer-trigger"
onBlur={closeWithDelay}
onClick={() => {
clearHoverTimer();
setOpen((current) => !current);
}}
onContextMenu={() => {
clearHoverTimer();
setOpen(false);
}}
onFocus={() => setOpen(true)}
onMouseEnter={openWithDelay}
onMouseLeave={closeWithDelay}
type="button"
>
<span className="flex h-4.5 items-center overflow-visible -space-x-1">
{workingAgents.slice(0, 2).map((agent) => (
<UserAvatar
avatarUrl={agentAvatarUrl(agent)}
className={cn(
"border border-background",
isInline ? "!h-4.5 !w-4.5 text-3xs" : "shrink-0",
)}
displayName={agent.name}
fallbackDelayMs={isInline ? 0 : undefined}
key={agent.pubkey}
size="xs"
/>
))}
</span>
{workingAgents.length > 2 ? (
<span className="text-2xs leading-none">
+{workingAgents.length - 2}
</span>
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground/70" />
</button>
);
})}
</div>
</PopoverContent>
</Popover>
) : null}
<span
className={cn(
isInline
? "flex h-4.5 min-w-0 flex-1 items-center overflow-visible leading-none"
: "sr-only",
)}
>
{isInline ? (
<Shimmer className="-my-px truncate py-px">
{visibleStatusLabel}
</Shimmer>
) : (
"working"
)}
</span>
{isInline ? null : (
<Loader2 className="h-4 w-4 shrink-0 animate-spin opacity-70" />
)}
</button>
</ContextMenuTrigger>
</PopoverTrigger>
<PopoverContent
align={isInline ? "start" : "end"}
className="w-64 p-1"
onMouseEnter={keepOpen}
onMouseLeave={closeWithDelay}
onOpenAutoFocus={(event) => event.preventDefault()}
side="top"
sideOffset={8}
>
<div className="px-2 py-1 text-xs font-medium text-muted-foreground">
Agents working
</div>
<div className="mt-1 flex flex-col gap-1">
{workingAgents.map((agent) => {
const isSelected = selectedPubkey === agent.pubkey.toLowerCase();
return (
<button
className={cn(
"flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors",
isSelected
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent hover:text-accent-foreground",
)}
data-testid={`bot-activity-composer-item-${agent.pubkey}`}
key={agent.pubkey}
onClick={() => {
clearHoverTimer();
setOpen(false);
onOpenAgentSession(agent.pubkey, channelId);
}}
type="button"
>
<UserAvatar
avatarUrl={agentAvatarUrl(agent)}
className="shrink-0"
displayName={agent.name}
size="sm"
/>
<span className="min-w-0 flex-1 truncate">{agent.name}</span>
<span className="shrink-0 whitespace-nowrap text-xs font-medium opacity-80">
View activity
</span>
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground/70" />
</button>
);
})}
</div>
</PopoverContent>
</Popover>
<ContextMenuContent className="min-w-56">
{workingAgents.map((agent) => (
<ContextMenuItem
data-testid={`bot-activity-open-external-${agent.pubkey}`}
key={agent.pubkey}
onSelect={() => {
clearHoverTimer();
setOpen(false);
onOpenAgentSessionExternal(agent.pubkey, channelId);
}}
>
<ExternalLink />
<span className="min-w-0 flex-1">
<span className="block">Open in external window</span>
{workingAgents.length > 1 ? (
<span className="block truncate text-xs text-muted-foreground">
{agent.name}
</span>
) : null}
</span>
</ContextMenuItem>
))}
</ContextMenuContent>
</ContextMenu>
);
}

View File

@@ -13,6 +13,9 @@ type ChannelComposerActivityAccessoryProps = {
onOpenAgentSession: ComponentProps<
typeof BotActivityComposerAction
>["onOpenAgentSession"];
onOpenAgentSessionExternal: ComponentProps<
typeof BotActivityComposerAction
>["onOpenAgentSessionExternal"];
openAgentSessionPubkey: ComponentProps<
typeof BotActivityComposerAction
>["openAgentSessionPubkey"];
@@ -27,6 +30,7 @@ export function ChannelComposerActivityAccessory({
channel,
currentPubkey,
onOpenAgentSession,
onOpenAgentSessionExternal,
openAgentSessionPubkey,
profiles,
typingPubkeys,
@@ -48,6 +52,7 @@ export function ChannelComposerActivityAccessory({
agents={agents}
channelId={channel?.id ?? null}
onOpenAgentSession={onOpenAgentSession}
onOpenAgentSessionExternal={onOpenAgentSessionExternal}
openAgentSessionPubkey={openAgentSessionPubkey}
profiles={profiles}
workingBotPubkeys={workingBotPubkeys}

View File

@@ -118,6 +118,7 @@ export const ChannelPane = React.memo(function ChannelPane({
onExpandThreadReplies,
onJoinChannel,
onOpenAgentSession,
onOpenAgentSessionExternal,
onOpenDm,
onOpenMembers,
onOpenProfilePanel,
@@ -743,6 +744,7 @@ export const ChannelPane = React.memo(function ChannelPane({
channel={activeChannel}
currentPubkey={currentPubkey}
onOpenAgentSession={onOpenAgentSession}
onOpenAgentSessionExternal={onOpenAgentSessionExternal}
openAgentSessionPubkey={openAgentSessionPubkey}
profiles={profiles}
typingPubkeys={typingPubkeys}
@@ -845,6 +847,7 @@ export const ChannelPane = React.memo(function ChannelPane({
agents={activityAgents}
channelId={activeChannel?.id ?? null}
onOpenAgentSession={onOpenAgentSession}
onOpenAgentSessionExternal={onOpenAgentSessionExternal}
openAgentSessionPubkey={openAgentSessionPubkey}
profiles={profiles}
workingBotPubkeys={threadComposerBotTypingPubkeys}

View File

@@ -96,6 +96,10 @@ export type ChannelPaneProps = {
onExpandThreadReplies: (message: TimelineMessage) => void;
onJoinChannel?: () => Promise<void>;
onOpenAgentSession: (pubkey: string, channelId?: string | null) => void;
onOpenAgentSessionExternal: (
pubkey: string,
channelId?: string | null,
) => void;
onOpenDm?: (pubkeys: string[]) => Promise<void> | void;
onOpenMembers?: () => void;
onOpenProfilePanel: (

View File

@@ -587,7 +587,7 @@ export function ChannelScreen({
setThreadReplyTargetId,
setThreadScrollTargetId,
});
const handleOpenAgentActivity = React.useCallback(
const handleOpenAgentActivityExternal = React.useCallback(
(pubkey: string, channelId?: string | null) => {
const destinationChannelId = channelId ?? activeChannelId;
if (!destinationChannelId) return;
@@ -610,6 +610,12 @@ export function ChannelScreen({
},
[activeChannelId, handleOpenAgentSession],
);
const handleOpenAgentActivity = React.useCallback(
(pubkey: string, channelId?: string | null) => {
handleOpenAgentSession(pubkey, channelId ?? activeChannelId);
},
[activeChannelId, handleOpenAgentSession],
);
const { handleOpenProfilePanel, handleCloseProfilePanel, handleOpenDm } =
useChannelProfilePanel({
closeAgentSession: handleCloseAgentSession,
@@ -943,6 +949,7 @@ export function ChannelScreen({
onMarkRead={handleMessageMarkRead}
onExpandThreadReplies={handleExpandThreadReplies}
onOpenAgentSession={handleOpenAgentActivity}
onOpenAgentSessionExternal={handleOpenAgentActivityExternal}
onOpenDm={handleOpenDm}
onOpenProfilePanel={handleOpenProfilePanel}
onResetThreadPanelWidth={handleThreadPanelWidthReset}

View File

@@ -2081,6 +2081,9 @@ test("shows and clears activity indicators for active channel agents", async ({
await expect(page.getByTestId("auxiliary-panel-close")).toBeVisible();
await expect(page.getByTestId("agent-transcript-now-summary")).toHaveCount(0);
await page.getByTestId("agent-session-settings-menu-trigger").click();
await expect(
page.getByTestId("agent-session-open-external-window"),
).toContainText("Open in external window");
await expect(page.getByTestId("agent-session-stop-turn")).toBeVisible();
await expect(page.getByTestId("agent-session-stop-turn")).toBeDisabled();
await page.keyboard.press("Escape");
@@ -2088,6 +2091,16 @@ test("shows and clears activity indicators for active channel agents", async ({
"No ACP activity yet",
);
await expect(page.getByTestId("message-typing-indicator")).toHaveCount(0);
await page.getByTestId("auxiliary-panel-close").click();
await page
.getByTestId("bot-activity-composer-trigger")
.click({ button: "right" });
await expect(
page.getByTestId(
`bot-activity-open-external-${TEST_IDENTITIES.alice.pubkey}`,
),
).toContainText("Open in external window");
await page.keyboard.press("Escape");
await page.evaluate((pubkey) => {
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({