Archived
feat(sessions): stream in-flight partial when joining a mid-turn session
Seed the in-flight partial assistant message (text, thinking, and
in-progress tool calls) when opening or reconnecting to a session that is
mid-stream, then continue streaming live deltas on top of it. Replaces the
blocking "Catching up..." placeholder and the end-of-turn transcript reload.
Server stamps every per-session UI event with a monotonic seq at the
SessionEventHub publish choke point and exposes
GET /sessions/:sessionId/stream-snapshot returning { seq, partial }. The
client fetches the snapshot on join, seeds the normalized partial into the
in-memory transcript (never the history cache), and applies buffered/live
events using the seq watermark for exactly-once delivery.
The snapshot is a progressive enhancement: a 404 from an older remote
pi-web or a not-yet-restarted session daemon falls back to an empty seed
(seq 0, drops nothing), so sessions still open and stream normally. The
stream-snapshot route is registered in the federation allowlist for
remote-machine proxying.
This commit is contained in:
@@ -11,6 +11,7 @@ export interface RealtimeSocket {
|
||||
export class SessionEventHub {
|
||||
private readonly socketsBySession = new Map<string, Set<RealtimeSocket>>();
|
||||
private readonly globalSockets = new Set<RealtimeSocket>();
|
||||
private readonly seqBySession = new Map<string, number>();
|
||||
|
||||
add(sessionId: string, socket: RealtimeSocket): void {
|
||||
let sockets = this.socketsBySession.get(sessionId);
|
||||
@@ -30,12 +31,24 @@ export class SessionEventHub {
|
||||
}
|
||||
|
||||
publish(sessionId: string, event: SessionUiEvent): void {
|
||||
const payload = JSON.stringify(projectBrowserSessionEvent(event));
|
||||
const seq = (this.seqBySession.get(sessionId) ?? 0) + 1;
|
||||
this.seqBySession.set(sessionId, seq);
|
||||
const payload = JSON.stringify({ ...projectBrowserSessionEvent(event), seq });
|
||||
for (const socket of this.socketsBySession.get(sessionId) ?? []) {
|
||||
if (socket.readyState === socket.OPEN) socket.send(payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Last per-session sequence number stamped by {@link publish} (0 before any
|
||||
* event). Callers building a join-time stream snapshot read this as the
|
||||
* watermark: buffered live events with `seq <= currentSeq` are already
|
||||
* reflected in the snapshot's partial and must be dropped by the client.
|
||||
*/
|
||||
currentSeq(sessionId: string): number {
|
||||
return this.seqBySession.get(sessionId) ?? 0;
|
||||
}
|
||||
|
||||
publishGlobal(event: GlobalSessionEvent): void {
|
||||
this.publishRealtime(event);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user