From aa781c6d12c0de732211146a52abc45bad98b148 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Sun, 23 Aug 2026 20:54:29 -0700 Subject: [PATCH] fix(desktop): show unavailable activity state Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Signed-off-by: Taylor Ho --- .../src/features/channels/ui/ChannelPane.tsx | 27 ++++++++++- .../features/channels/ui/ChannelPane.types.ts | 1 + .../features/channels/ui/ChannelScreen.tsx | 11 +++-- .../ui/useChannelAgentSessions.test.mjs | 31 +++++++++++++ .../channels/ui/useChannelAgentSessions.ts | 46 +++++++++++++++---- 5 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 desktop/src/features/channels/ui/useChannelAgentSessions.test.mjs 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 ? ( +
+
) : 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,