mirror of
https://github.com/block/buzz.git
synced 2026-11-03 07:51:22 +00:00
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 <cursoragent@cursor.com>
This commit is contained in:
@@ -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<void> {
|
||||
const label = windowLabel(input.channelId, input.agentPubkey);
|
||||
writeObserverSeed(input);
|
||||
|
||||
const existing = await WebviewWindow.getByLabel(label);
|
||||
if (existing) {
|
||||
|
||||
@@ -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<ManagedAgent, "pubkey" | "status">[],
|
||||
) {
|
||||
|
||||
@@ -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<HTMLDivElement>();
|
||||
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();
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user