fix(server,web): live in-progress output survives refresh and reconnect (#77)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-27 22:31:57 +08:00
committed by GitHub
parent 3d3495528c
commit 094ebd6118
18 changed files with 1214 additions and 40 deletions
+12 -6
View File
@@ -15,10 +15,15 @@ import type { OmniMessage } from "@prismshadow/penguin-core/omnimessage";
import type { ServerEvent } from "@prismshadow/penguin-server/api";
export interface StreamHandlers {
/** A single OmniMessage (full/streaming/event, envelope as-is). */
onOmniMessage: (msg: OmniMessage) => void;
/** A single server event. */
onServerEvent: (event: ServerEvent) => void;
/**
* A single OmniMessage (full/streaming/event, envelope as-is). `eventId` is the SSE
* event id assigned by the server channel (`<epoch>-<seq>`; null if the event carried
* none) — stream-controller uses it to align buffered events with the live-tail cursor
* that GET /messages returns.
*/
onOmniMessage: (msg: OmniMessage, eventId: string | null) => void;
/** A single server event (`eventId`: same as onOmniMessage). */
onServerEvent: (event: ServerEvent, eventId: string | null) => void;
/** Connection established (including a successful auto-reconnect). */
onOpen?: () => void;
/**
@@ -37,14 +42,15 @@ function subscribe(url: string, handlers: StreamHandlers): StreamConnection {
const source = new EventSource(url);
source.onmessage = (e: MessageEvent<string>) => {
try {
handlers.onOmniMessage(JSON.parse(e.data) as OmniMessage);
// Every server event carries an `id:` line; lastEventId is "" only if none did.
handlers.onOmniMessage(JSON.parse(e.data) as OmniMessage, e.lastEventId || null);
} catch {
// Ignore lines that fail to parse (the protocol guarantees single-line JSON data, so this shouldn't normally happen).
}
};
source.addEventListener("server_event", (e: MessageEvent<string>) => {
try {
handlers.onServerEvent(JSON.parse(e.data) as ServerEvent);
handlers.onServerEvent(JSON.parse(e.data) as ServerEvent, e.lastEventId || null);
} catch {
// Same as above.
}