diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx
index 8b8a4101ce..131c9df0e1 100644
--- a/desktop/src/features/channels/ui/ChannelPane.tsx
+++ b/desktop/src/features/channels/ui/ChannelPane.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { Hash, LogIn } from "lucide-react";
+import { Hash, LoaderCircle, LogIn } from "lucide-react";
import { AnimatePresence } from "motion/react";
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { useMediaUpload } from "@/features/messages/lib/useMediaUpload";
@@ -87,6 +87,7 @@ export const ChannelPane = React.memo(function ChannelPane({
isMessageUnreadById,
isJoining = false,
isSinglePanelView = false,
+ isAgentSessionLoading = false,
isSending,
isTimelineLoading,
entranceMessageId = null,
@@ -914,6 +915,30 @@ export const ChannelPane = React.memo(function ChannelPane({
);
return wrapAux(panel, "agent-session-thread-panel");
})()
+ ) : isSinglePanelView && openAgentSessionPubkey ? (
+
+
+ {isAgentSessionLoading ? (
+
+ ) : null}
+
+ {isAgentSessionLoading
+ ? "Loading agent activity…"
+ : "Agent activity unavailable"}
+
+
+ {isAgentSessionLoading
+ ? "Buzz is finding this agent."
+ : "This agent is no longer available in this community. You can close this window and open another activity feed from Buzz."}
+
+
+
) : profilePanelPubkey ? (
(() => {
const panel = (
diff --git a/desktop/src/features/channels/ui/ChannelPane.types.ts b/desktop/src/features/channels/ui/ChannelPane.types.ts
index 62b9f8a645..be746d4c62 100644
--- a/desktop/src/features/channels/ui/ChannelPane.types.ts
+++ b/desktop/src/features/channels/ui/ChannelPane.types.ts
@@ -54,6 +54,7 @@ export type ChannelPaneProps = {
isHuddleTranscript?: boolean;
isJoining?: boolean;
isSinglePanelView?: boolean;
+ isAgentSessionLoading?: boolean;
isSending: boolean;
isTimelineLoading: boolean;
/** Newly-created message that should receive the one-shot conversation arrival motion. */
diff --git a/desktop/src/features/channels/ui/ChannelScreen.tsx b/desktop/src/features/channels/ui/ChannelScreen.tsx
index 3d2bee490e..626b8cd396 100644
--- a/desktop/src/features/channels/ui/ChannelScreen.tsx
+++ b/desktop/src/features/channels/ui/ChannelScreen.tsx
@@ -306,6 +306,10 @@ export function ChannelScreen({
welcomeGuideAgent,
});
const relayAgentsQuery = useRelayAgentsQuery();
+ const agentsLoading =
+ channelMembersQuery.isLoading ||
+ managedAgentsQuery.isLoading ||
+ relayAgentsQuery.isLoading;
const relayAgents = relayAgentsQuery.data ?? [];
const knownAgentPubkeys = React.useMemo(
() =>
@@ -568,16 +572,14 @@ export function ChannelScreen({
} = useChannelAgentSessions({
activeChannel,
activeChannelId,
- agentsLoaded:
- !channelMembersQuery.isLoading &&
- !managedAgentsQuery.isLoading &&
- !relayAgentsQuery.isLoading,
+ agentsLoaded: !agentsLoading,
channelMembers,
handleOpenThread,
managedAgents: agentSessionCandidates,
openAgentSessionPubkey,
openThreadHeadId: effectiveOpenThreadHeadId,
profilePanelPubkey,
+ preserveUnresolvedSession: isDedicatedActivityWindow,
setChannelManagementOpen,
setExpandedThreadReplyIds,
setOpenAgentSessionChannelId,
@@ -890,6 +892,7 @@ export function ChannelScreen({
isMessageUnreadById={isMessageUnread}
isFollowingThread={isNotifiedForEffectiveThread}
isSending={sendMessageMutation.isPending}
+ isAgentSessionLoading={agentsLoading}
isSinglePanelView={isSinglePanelView}
isTimelineLoading={isTimelineLoading}
messages={timelineMessages}
diff --git a/desktop/src/features/channels/ui/useChannelAgentSessions.test.mjs b/desktop/src/features/channels/ui/useChannelAgentSessions.test.mjs
new file mode 100644
index 0000000000..68372aa2fb
--- /dev/null
+++ b/desktop/src/features/channels/ui/useChannelAgentSessions.test.mjs
@@ -0,0 +1,31 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import { shouldClearUnresolvedAgentSession } from "./useChannelAgentSessions.ts";
+
+const unresolvedSession = {
+ agentSessionAgents: [],
+ agentsLoaded: true,
+ openAgentSessionPubkey: "agent-pubkey",
+ profilePanelPubkey: null,
+};
+
+test("dedicated activity windows preserve unresolved sessions for an empty state", () => {
+ assert.equal(
+ shouldClearUnresolvedAgentSession({
+ ...unresolvedSession,
+ preserveUnresolvedSession: true,
+ }),
+ false,
+ );
+});
+
+test("the in-app panel still clears stale unresolved sessions", () => {
+ assert.equal(
+ shouldClearUnresolvedAgentSession({
+ ...unresolvedSession,
+ preserveUnresolvedSession: false,
+ }),
+ true,
+ );
+});
diff --git a/desktop/src/features/channels/ui/useChannelAgentSessions.ts b/desktop/src/features/channels/ui/useChannelAgentSessions.ts
index 20c561e798..cb4dc0e3a6 100644
--- a/desktop/src/features/channels/ui/useChannelAgentSessions.ts
+++ b/desktop/src/features/channels/ui/useChannelAgentSessions.ts
@@ -39,6 +39,7 @@ type UseChannelAgentSessionsOptions = {
openAgentSessionPubkey: string | null;
openThreadHeadId: string | null;
profilePanelPubkey?: string | null;
+ preserveUnresolvedSession?: boolean;
setChannelManagementOpen: (open: boolean) => void;
setExpandedThreadReplyIds: (value: Set) => void;
setOpenAgentSessionChannelId: PanelValueSetter;
@@ -163,6 +164,33 @@ export function getChannelAgentSessionAgents({
});
}
+export function shouldClearUnresolvedAgentSession({
+ agentSessionAgents,
+ agentsLoaded,
+ openAgentSessionPubkey,
+ preserveUnresolvedSession,
+ profilePanelPubkey,
+}: {
+ agentSessionAgents: ChannelAgentSessionAgent[];
+ agentsLoaded: boolean;
+ openAgentSessionPubkey: string | null;
+ preserveUnresolvedSession: boolean;
+ profilePanelPubkey: string | null;
+}): boolean {
+ return Boolean(
+ openAgentSessionPubkey &&
+ !preserveUnresolvedSession &&
+ agentsLoaded &&
+ normalizePubkey(profilePanelPubkey ?? "") !==
+ normalizePubkey(openAgentSessionPubkey) &&
+ !agentSessionAgents.some(
+ (agent) =>
+ normalizePubkey(agent.pubkey) ===
+ normalizePubkey(openAgentSessionPubkey),
+ ),
+ );
+}
+
export function useChannelAgentSessions({
activeChannel,
activeChannelId,
@@ -173,6 +201,7 @@ export function useChannelAgentSessions({
openAgentSessionPubkey,
openThreadHeadId,
profilePanelPubkey = null,
+ preserveUnresolvedSession = false,
setChannelManagementOpen,
setExpandedThreadReplyIds,
setOpenAgentSessionChannelId,
@@ -298,15 +327,13 @@ export function useChannelAgentSessions({
// agent queries have settled. Once loaded, a channel that legitimately has
// zero agents will still auto-close a stale param.
if (
- openAgentSessionPubkey &&
- agentsLoaded &&
- normalizePubkey(profilePanelPubkey ?? "") !==
- normalizePubkey(openAgentSessionPubkey) &&
- !agentSessionAgents.some(
- (agent) =>
- normalizePubkey(agent.pubkey) ===
- normalizePubkey(openAgentSessionPubkey),
- )
+ shouldClearUnresolvedAgentSession({
+ agentSessionAgents,
+ agentsLoaded,
+ openAgentSessionPubkey,
+ preserveUnresolvedSession,
+ profilePanelPubkey,
+ })
) {
returnTarget.clear();
setOpenAgentSessionPubkey(null, { replace: true });
@@ -315,6 +342,7 @@ export function useChannelAgentSessions({
agentSessionAgents,
agentsLoaded,
openAgentSessionPubkey,
+ preserveUnresolvedSession,
profilePanelPubkey,
returnTarget,
setOpenAgentSessionPubkey,