Files
buzz/desktop/src/features/agents/ui/useObserverEvents.ts
Bradley Axen 968691806e feat(desktop): live ACP session sidebar with sticky scroll
- Add local ACP session observer (SSE hook, transcript builder, types)
- Bot activity bar with clickable agent pills to open sidebar
- Agent session thread panel with transcript list, tool items, thoughts
- Sticky scroll: auto-follows new content unless user scrolls up
2026-04-27 21:29:16 -07:00

66 lines
1.7 KiB
TypeScript

import * as React from "react";
import type { ConnectionState, ObserverEvent } from "./agentSessionTypes";
const MAX_OBSERVER_EVENTS = 800;
export function useObserverEvents(
observerUrl: string | null,
enabled: boolean,
) {
const [events, setEvents] = React.useState<ObserverEvent[]>([]);
const [connectionState, setConnectionState] =
React.useState<ConnectionState>("idle");
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
React.useEffect(() => {
setEvents([]);
setErrorMessage(null);
if (!observerUrl || !enabled) {
setConnectionState("idle");
return;
}
setConnectionState("connecting");
const source = new EventSource(observerUrl);
source.onopen = () => {
setConnectionState("open");
setErrorMessage(null);
};
source.onmessage = (event) => {
try {
const parsed = JSON.parse(event.data) as ObserverEvent;
setEvents((current) => {
const next = [...current, parsed];
return next.length > MAX_OBSERVER_EVENTS
? next.slice(next.length - MAX_OBSERVER_EVENTS)
: next;
});
} catch (error) {
setErrorMessage(
error instanceof Error
? `Observer event parse failed: ${error.message}`
: "Observer event parse failed.",
);
}
};
source.onerror = () => {
setConnectionState((current) =>
current === "open" ? "closed" : "error",
);
setErrorMessage("Observer stream is not available right now.");
};
return () => {
source.close();
setConnectionState("closed");
};
}, [enabled, observerUrl]);
return { connectionState, errorMessage, events };
}