fix(desktop): keep popped activity channel-scoped

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-24 00:03:44 -07:00
parent dde8a9ea74
commit bd4980a8da
5 changed files with 55 additions and 14 deletions

View File

@@ -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}"
)
);
}

View File

@@ -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;

View File

@@ -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),

View File

@@ -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 = (
<AgentSessionThreadPanel
agent={selectedAgent}

View File

@@ -23,7 +23,7 @@ test("keeps dedicated activity windows connected when opening a channel referenc
});
await page.goto(
`/#/channels/${AGENTS_CHANNEL_ID}?community=${COMMUNITY_ID}&agentSession=${AGENT_PUBKEY}`,
`/#/channels/${AGENTS_CHANNEL_ID}?community=${COMMUNITY_ID}&agentSession=${AGENT_PUBKEY}&agentSessionChannel=${AGENTS_CHANNEL_ID}`,
);
const panel = page.getByTestId("agent-session-thread-panel");
await expect(panel).toBeVisible({ timeout: 10_000 });
@@ -31,7 +31,7 @@ test("keeps dedicated activity windows connected when opening a channel referenc
() => 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();
});