fix(desktop): scope live observer feed to active channel on open

Both openAgentSession and selectAgentSession were calling
setOpenAgentSessionChannelId(channelId ?? null). When the activity-list
opener supplies no explicit channelId, the null propagated into
sessionChannelId, which caused scopeByChannel to skip filtering entirely
(its null fast-path returns all items). Combined with the shared per-pubkey
store, this let every channel's live frames appear in any channel's feed.

Fix: fall back to activeChannelId in both callbacks so opening from within
a channel always resolves a non-null scope key. activeChannelId is already
in the useChannelAgentSessions parameter list; add it to both callbacks'
dependency arrays.

Also fix the archive-load enabled gate: useLoadArchivedObserverEvents was
gated on isLive, so an idle agent's channel showed no archived observer
history. Change the enable condition to Boolean(sessionChannelId) so
archive history loads whenever a channel scope is resolved, regardless of
whether the agent is currently running.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
2026-07-08 15:02:45 -04:00
committed by Will Pfleger
co-authored by Will Pfleger
parent 2ee01cb85a
commit 3d8b027ca7
2 changed files with 18 additions and 4 deletions
@@ -131,7 +131,14 @@ export function AgentSessionThreadPanel({
: `Last updated ${new Date(latestActivityAt).toLocaleString()}`;
const { fetchOlderArchived, hasOlderArchived } =
useLoadArchivedObserverEvents(isLive, sessionChannelId ?? null);
useLoadArchivedObserverEvents(
// Archive history must load regardless of live status — an idle agent's
// channel should still show its archived observer history. Enable whenever
// there is a resolved sessionChannelId (the hook's owner_p guard handles
// the case where no save subscription exists).
Boolean(sessionChannelId),
sessionChannelId ?? null,
);
useLoadOlderOnScroll({
fetchOlder: fetchOlderArchived,
@@ -220,9 +220,14 @@ export function useChannelAgentSessions({
setThreadReplyTargetId(null);
setChannelManagementOpen(false);
setOpenAgentSessionPubkey(pubkey);
setOpenAgentSessionChannelId(channelId ?? null);
// Fall back to activeChannelId so opening from within a channel always
// scopes the panel to that channel — even when no explicit channelId is
// supplied (e.g. activity-list click). Without this, a null channelId
// bypasses scopeByChannel and lets all channels' live frames through.
setOpenAgentSessionChannelId(channelId ?? activeChannelId ?? null);
},
[
activeChannelId,
isAgentSessionOpen,
openThreadHeadId,
profilePanelPubkey,
@@ -260,9 +265,11 @@ export function useChannelAgentSessions({
const selectAgentSession = React.useCallback(
(pubkey: string, channelId?: string | null) => {
setOpenAgentSessionPubkey(pubkey);
setOpenAgentSessionChannelId(channelId ?? null);
// Same fallback as openAgentSession: use activeChannelId when the caller
// omits channelId, so the panel is always scoped to the current channel.
setOpenAgentSessionChannelId(channelId ?? activeChannelId ?? null);
},
[setOpenAgentSessionChannelId, setOpenAgentSessionPubkey],
[activeChannelId, setOpenAgentSessionChannelId, setOpenAgentSessionPubkey],
);
const openThreadAndCloseAgentSession = React.useCallback(