From bdb3bb1ee159b4236b6abdda73e58fe12a289fe1 Mon Sep 17 00:00:00 2001 From: Thomas Petersen Date: Mon, 15 Jun 2026 11:28:38 -0400 Subject: [PATCH] fix(desktop): hydrate popped-out agent activity Seed native agent conversation windows with the opener's current observer events so the Activity transcript matches the right-side panel immediately. Co-authored-by: Cursor --- .../agents/lib/openAgentConversationWindow.ts | 25 ++++++++ .../src/features/agents/observerRelayStore.ts | 9 +++ .../features/agents/ui/AgentWindowScreen.tsx | 60 ++++++++++++++++++- .../features/channels/ui/ChannelScreen.tsx | 6 +- 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/desktop/src/features/agents/lib/openAgentConversationWindow.ts b/desktop/src/features/agents/lib/openAgentConversationWindow.ts index 487c50107e..814fa0d6ec 100644 --- a/desktop/src/features/agents/lib/openAgentConversationWindow.ts +++ b/desktop/src/features/agents/lib/openAgentConversationWindow.ts @@ -1,5 +1,6 @@ import { WebviewWindow } from "@tauri-apps/api/webviewWindow"; +import type { ObserverEvent } from "@/features/agents/ui/agentSessionTypes"; import type { ChannelType, ManagedAgent } from "@/shared/api/types"; import { normalizePubkey } from "@/shared/lib/pubkey"; @@ -10,6 +11,7 @@ export type OpenAgentConversationWindowInput = { channelId: string; channelName: string; channelType: ChannelType | null; + observerEvents?: readonly ObserverEvent[]; }; const DEFAULT_WIDTH = 460; @@ -29,6 +31,28 @@ function windowLabel(channelId: string, agentPubkey: string): string { ); } +export function agentConversationSeedStorageKey( + channelId: string, + agentPubkey: string, +): string { + return `buzz:agent-window-seed:${windowLabel(channelId, agentPubkey)}`; +} + +function writeObserverSeed(input: OpenAgentConversationWindowInput) { + if (!input.observerEvents?.length) { + return; + } + + window.localStorage.setItem( + agentConversationSeedStorageKey(input.channelId, input.agentPubkey), + JSON.stringify({ + agentPubkey: input.agentPubkey, + events: input.observerEvents, + writtenAt: Date.now(), + }), + ); +} + /** * Open (or focus) a real OS window hosting the agent's live activity log and a * composer to chat with it. The window loads the same frontend at the @@ -39,6 +63,7 @@ export async function openAgentConversationWindow( input: OpenAgentConversationWindowInput, ): Promise { const label = windowLabel(input.channelId, input.agentPubkey); + writeObserverSeed(input); const existing = await WebviewWindow.getByLabel(label); if (existing) { diff --git a/desktop/src/features/agents/observerRelayStore.ts b/desktop/src/features/agents/observerRelayStore.ts index ee8483dc86..73edd737bf 100644 --- a/desktop/src/features/agents/observerRelayStore.ts +++ b/desktop/src/features/agents/observerRelayStore.ts @@ -272,6 +272,15 @@ export function getAgentTranscript( return state?.items ?? EMPTY_TRANSCRIPT; } +export function seedAgentObserverEvents( + agentPubkey: string, + events: readonly ObserverEvent[], +) { + for (const event of events) { + appendAgentEvent(agentPubkey, event); + } +} + export function useManagedAgentObserverBridge( agents: readonly Pick[], ) { diff --git a/desktop/src/features/agents/ui/AgentWindowScreen.tsx b/desktop/src/features/agents/ui/AgentWindowScreen.tsx index 666f246bcb..3e76f5fd06 100644 --- a/desktop/src/features/agents/ui/AgentWindowScreen.tsx +++ b/desktop/src/features/agents/ui/AgentWindowScreen.tsx @@ -11,7 +11,11 @@ import { useActiveAgentTurnsBridge, } from "@/features/agents/activeAgentTurnsStore"; import { isManagedAgentActive } from "@/features/agents/lib/managedAgentControlActions"; -import { useManagedAgentObserverBridge } from "@/features/agents/observerRelayStore"; +import { agentConversationSeedStorageKey } from "@/features/agents/lib/openAgentConversationWindow"; +import { + seedAgentObserverEvents, + useManagedAgentObserverBridge, +} from "@/features/agents/observerRelayStore"; import { ManagedAgentSessionPanel } from "@/features/agents/ui/ManagedAgentSessionPanel"; import { useChannelsQuery } from "@/features/channels/hooks"; import { mergeAgentNamesIntoProfiles } from "@/features/channels/ui/useChannelActivityTyping"; @@ -27,6 +31,7 @@ import { Badge } from "@/shared/ui/badge"; import { Button } from "@/shared/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; import { UserAvatar } from "@/shared/ui/UserAvatar"; +import type { ObserverEvent } from "./agentSessionTypes"; type AgentWindowScreenProps = { channelId: string; @@ -36,6 +41,22 @@ type AgentWindowScreenProps = { channelType: ChannelType | null; }; +type ObserverSeedPayload = { + agentPubkey?: unknown; + events?: unknown; +}; + +function parseObserverSeed(raw: string): ObserverEvent[] | null { + try { + const parsed = JSON.parse(raw) as ObserverSeedPayload; + return Array.isArray(parsed.events) + ? (parsed.events as ObserverEvent[]) + : null; + } catch { + return null; + } +} + /** * Chrome-less conversation view that fills its own OS window (opened via * `openAgentConversationWindow`). Renders the agent's live activity log and a @@ -50,6 +71,43 @@ export function AgentWindowScreen({ channelType, }: AgentWindowScreenProps) { const { ref: scrollRef, onScroll } = useStickToBottom(); + const seedStorageKey = React.useMemo( + () => agentConversationSeedStorageKey(channelId, agentPubkey), + [channelId, agentPubkey], + ); + + const applyObserverSeed = React.useCallback( + (raw: string | null) => { + if (!raw) { + return; + } + const events = parseObserverSeed(raw); + if (!events?.length) { + return; + } + seedAgentObserverEvents(agentPubkey, events); + }, + [agentPubkey], + ); + + React.useEffect(() => { + applyObserverSeed(window.localStorage.getItem(seedStorageKey)); + window.localStorage.removeItem(seedStorageKey); + + function handleStorage(event: StorageEvent) { + if ( + event.storageArea !== window.localStorage || + event.key !== seedStorageKey + ) { + return; + } + applyObserverSeed(event.newValue); + window.localStorage.removeItem(seedStorageKey); + } + + window.addEventListener("storage", handleStorage); + return () => window.removeEventListener("storage", handleStorage); + }, [applyObserverSeed, seedStorageKey]); const managedAgentsQuery = useManagedAgentsQuery(); const relayAgentsQuery = useRelayAgentsQuery(); diff --git a/desktop/src/features/channels/ui/ChannelScreen.tsx b/desktop/src/features/channels/ui/ChannelScreen.tsx index 727ad08ef1..6f2835c757 100644 --- a/desktop/src/features/channels/ui/ChannelScreen.tsx +++ b/desktop/src/features/channels/ui/ChannelScreen.tsx @@ -18,7 +18,10 @@ import { usePersonasQuery, useRelayAgentsQuery, } from "@/features/agents/hooks"; -import { useManagedAgentObserverBridge } from "@/features/agents/observerRelayStore"; +import { + getAgentObserverSnapshot, + useManagedAgentObserverBridge, +} from "@/features/agents/observerRelayStore"; import { openAgentConversationWindow } from "@/features/agents/lib/openAgentConversationWindow"; import { mergeMessages, @@ -440,6 +443,7 @@ export function ChannelScreen({ channelId: activeChannel.id, channelName: activeChannel.name, channelType: activeChannel.channelType, + observerEvents: getAgentObserverSnapshot(agent.pubkey, true).events, }); }, [activeChannel, channelAgentSessionAgents],