From bd4980a8da369d4ab51465247c7500340a7794f6 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Mon, 24 Aug 2026 00:03:44 -0700 Subject: [PATCH] fix(desktop): keep popped activity channel-scoped Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Signed-off-by: Taylor Ho --- .../src-tauri/src/agent_activity_window.rs | 3 +- .../src/app/navigation/useAppNavigation.ts | 4 +- .../src/app/routes/channels.$channelId.tsx | 3 ++ .../src/features/channels/ui/ChannelPane.tsx | 17 ++++---- .../tests/e2e/agent-activity-window.spec.ts | 42 +++++++++++++++++-- 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/desktop/src-tauri/src/agent_activity_window.rs b/desktop/src-tauri/src/agent_activity_window.rs index f7d28ae1bb..e79d8ef040 100644 --- a/desktop/src-tauri/src/agent_activity_window.rs +++ b/desktop/src-tauri/src/agent_activity_window.rs @@ -34,6 +34,7 @@ fn activity_route(community_id: &str, channel_id: &Uuid, pubkey: &str) -> String let query = form_urlencoded::Serializer::new(String::new()) .append_pair("community", community_id) .append_pair("agentSession", pubkey) + .append_pair("agentSessionChannel", &channel_id.to_string()) .finish(); format!("index.html#/channels/{channel_id}?{query}") } @@ -111,7 +112,7 @@ mod tests { assert_eq!( activity_route("community & one", &channel_id, &pubkey), format!( - "index.html#/channels/{channel_id}?community=community+%26+one&agentSession={pubkey}" + "index.html#/channels/{channel_id}?community=community+%26+one&agentSession={pubkey}&agentSessionChannel={channel_id}" ) ); } diff --git a/desktop/src/app/navigation/useAppNavigation.ts b/desktop/src/app/navigation/useAppNavigation.ts index c290bb67f4..4e5829e97b 100644 --- a/desktop/src/app/navigation/useAppNavigation.ts +++ b/desktop/src/app/navigation/useAppNavigation.ts @@ -25,10 +25,12 @@ export function useAppNavigation() { const companionCoordinates = currentCompanionWindowKind() === "agent-activity" && typeof companionSearch.community === "string" && - typeof companionSearch.agentSession === "string" + typeof companionSearch.agentSession === "string" && + typeof companionSearch.agentSessionChannel === "string" ? { community: companionSearch.community, agentSession: companionSearch.agentSession, + agentSessionChannel: companionSearch.agentSessionChannel, } : undefined; diff --git a/desktop/src/app/routes/channels.$channelId.tsx b/desktop/src/app/routes/channels.$channelId.tsx index fa04107f49..14cc275350 100644 --- a/desktop/src/app/routes/channels.$channelId.tsx +++ b/desktop/src/app/routes/channels.$channelId.tsx @@ -13,6 +13,8 @@ import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback"; type ChannelRouteSearch = { agentSession?: string; + /** Immutable channel scope carried by dedicated activity windows. */ + agentSessionChannel?: string; /** Immutable origin community carried by dedicated activity windows. */ community?: string; /** @@ -38,6 +40,7 @@ function validateChannelSearch( ): ChannelRouteSearch { return { agentSession: nonEmptyString(search.agentSession), + agentSessionChannel: nonEmptyString(search.agentSessionChannel), community: nonEmptyString(search.community), autoSend: nonEmptyString(search.autoSend), messageId: nonEmptyString(search.messageId), diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index 74ac1b6463..6765680ef3 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -880,15 +880,16 @@ export const ChannelPane = React.memo(function ChannelPane({ })() ) : activeChannel && selectedAgent ? ( (() => { - // When the panel was opened from a different channel than the - // currently active one, re-scope it to the active channel so - // that both the content/header AND channel-backed actions (e.g. - // Stop current turn) operate on the same channel object. + // In normal app navigation, re-scope a panel opened from another + // channel so its content and channel-backed actions stay aligned. + // A dedicated activity companion has an immutable channel scope, + // even when one of its links navigates the underlying route. const effectiveAgentSessionChannelId = - openAgentSessionChannelId && - activeChannel.id !== openAgentSessionChannelId - ? activeChannelId - : openAgentSessionChannelId; + isDedicatedActivityWindow || + !openAgentSessionChannelId || + activeChannel.id === openAgentSessionChannelId + ? openAgentSessionChannelId + : activeChannelId; const panel = ( typeof window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__ === "function", ); await page.evaluate( - ({ agentPubkey, channelId }) => { + ({ agentPubkey, channelId, generalChannelId }) => { window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__?.({ agentPubkey, events: [ @@ -53,10 +53,40 @@ test("keeps dedicated activity windows connected when opening a channel referenc }, }, }, + { + seq: 2, + timestamp: new Date().toISOString(), + kind: "acp_read", + agentIndex: 0, + channelId: generalChannelId, + sessionId: "session-other-channel", + turnId: "turn-other-channel", + payload: { + method: "session/update", + params: { + update: { + sessionUpdate: "agent_message_chunk", + content: { + type: "text", + text: "Other channel activity must stay hidden", + }, + }, + }, + }, + }, ], }); }, - { agentPubkey: AGENT_PUBKEY, channelId: AGENTS_CHANNEL_ID }, + { + agentPubkey: AGENT_PUBKEY, + channelId: AGENTS_CHANNEL_ID, + generalChannelId: GENERAL_CHANNEL_ID, + }, + ); + + await expect(panel).toContainText("Activity · #agents"); + await expect(panel).not.toContainText( + "Other channel activity must stay hidden", ); const channelReference = panel.getByRole("button", { @@ -67,9 +97,13 @@ test("keeps dedicated activity windows connected when opening a channel referenc await expect(page).toHaveURL( new RegExp( - `#/channels/${GENERAL_CHANNEL_ID}\\?.*community=${COMMUNITY_ID}`, + `#/channels/${GENERAL_CHANNEL_ID}\\?.*community=${COMMUNITY_ID}.*agentSessionChannel=${AGENTS_CHANNEL_ID}`, ), ); await expect(panel).toBeVisible(); + await expect(panel).toContainText("Activity · #agents"); + await expect(panel).not.toContainText( + "Other channel activity must stay hidden", + ); await expect(page.locator("body")).not.toBeEmpty(); });