@evandu/super-pi 0.1.70 → 0.1.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -94,6 +94,10 @@ export type Message = {
94
94
  piEntryId?: string;
95
95
  toolCalls?: ToolCallBlock[];
96
96
  thinking?: string;
97
+ thinkingBlocks?: {
98
+ at: number;
99
+ text: string;
100
+ }[];
97
101
  images?: MessageImage[];
98
102
  attachments?: MessageAttachments;
99
103
  agentIdentity?: {
@@ -241,11 +245,17 @@ export type ToolCallNotice = {
241
245
  * - read out of the arguments as far as they have arrived. It is `undefined` when nothing identifying has
242
246
  * been written yet, which is a different fact from an empty one: the caller has a placeholder word for the
243
247
  * second case and must not invent one for the first.
248
+ *
249
+ * `live` is the arguments AS THEY STAND - the whole text written so far, never the newest fragment alone.
250
+ * It shares its meaning with `tool_execution_update`'s output (which Pi sends already accumulated), so one
251
+ * field with two meanings would make the caller store one kind of text by appending and the other by
252
+ * replacing - which is a flicker and a duplication at once. Pi sends argument fragments raw; accumulating
253
+ * them is the runtime's job, where the fragments already are.
244
254
  */
245
255
  export interface ToolCallPreparing {
246
256
  contentIndex: number;
247
257
  name: string;
248
- delta?: string;
258
+ live?: string;
249
259
  detail?: string;
250
260
  }
251
261
  /**
@@ -315,6 +325,9 @@ export type Conversation = {
315
325
  status: RunStatus;
316
326
  messages: Message[];
317
327
  archivedAt?: string;
328
+ /** Pi's own resolved transcript path, observed from this conversation's live worker. It is only a
329
+ * locator: revert still verifies the file header and every retained boundary before writing. */
330
+ piSessionFile?: string;
318
331
  /** Set when the user explicitly names the session; automatic first-message titles must not overwrite it. */
319
332
  titleLocked?: boolean;
320
333
  /**
@@ -912,6 +925,11 @@ export type ClientCommandBody =
912
925
  type: "catalog.fetch";
913
926
  conversationId?: string;
914
927
  }
928
+ /** Enables bounded transport coalescing for high-frequency streaming events on this socket. */
929
+ | {
930
+ type: "stream.configure";
931
+ coalesce: boolean;
932
+ }
915
933
  /** Asks for the agent catalog read from disk. Answered by `agent.catalog`. */
916
934
  | {
917
935
  type: "agent.catalog.fetch";
@@ -1080,6 +1098,7 @@ export type ServerEvent = {
1080
1098
  messageId: string;
1081
1099
  delta: string;
1082
1100
  active: boolean;
1101
+ at?: number;
1083
1102
  }
1084
1103
  /**
1085
1104
  * THE ANSWER TO `conversation.fork`, as ONE event with an `ok` discriminant rather than two events or a
@@ -11,9 +11,14 @@ import type { CatalogService } from "./application/catalog-service.js";
11
11
  export declare class WsBroadcaster implements EventBroadcaster {
12
12
  private readonly getState;
13
13
  private readonly sockets;
14
+ private readonly coalescedSockets;
15
+ private readonly pendingStreams;
14
16
  constructor(getState: () => AppState);
15
17
  register(socket: WebSocket): void;
16
18
  unregister(socket: WebSocket): void;
19
+ configureStreaming(socket: WebSocket, coalesce: boolean): void;
20
+ private flushStreaming;
21
+ private enqueueStreaming;
17
22
  getSockets(): Set<WebSocket>;
18
23
  broadcast(event: ServerEvent): void;
19
24
  broadcastState(): void;
@@ -7,6 +7,8 @@ import { isAuthorized } from "./http-server.js";
7
7
  export class WsBroadcaster {
8
8
  getState;
9
9
  sockets = new Set();
10
+ coalescedSockets = new Set();
11
+ pendingStreams = new Map();
10
12
  constructor(getState) {
11
13
  this.getState = getState;
12
14
  }
@@ -14,16 +16,56 @@ export class WsBroadcaster {
14
16
  this.sockets.add(socket);
15
17
  }
16
18
  unregister(socket) {
19
+ this.coalescedSockets.delete(socket);
20
+ const pending = this.pendingStreams.get(socket);
21
+ if (pending)
22
+ clearTimeout(pending.timer);
23
+ this.pendingStreams.delete(socket);
17
24
  this.sockets.delete(socket);
18
25
  }
26
+ configureStreaming(socket, coalesce) {
27
+ if (coalesce)
28
+ this.coalescedSockets.add(socket);
29
+ else {
30
+ this.flushStreaming(socket);
31
+ this.coalescedSockets.delete(socket);
32
+ }
33
+ }
34
+ flushStreaming(socket) {
35
+ const pending = this.pendingStreams.get(socket);
36
+ if (!pending)
37
+ return;
38
+ clearTimeout(pending.timer);
39
+ this.pendingStreams.delete(socket);
40
+ for (const event of pending.events.values())
41
+ if (socket.readyState === WebSocket.OPEN)
42
+ socket.send(JSON.stringify(event));
43
+ }
44
+ enqueueStreaming(socket, event) {
45
+ let pending = this.pendingStreams.get(socket);
46
+ if (!pending) {
47
+ const events = new Map();
48
+ pending = { events, timer: setTimeout(() => this.flushStreaming(socket), 16) };
49
+ this.pendingStreams.set(socket, pending);
50
+ }
51
+ const key = `${event.type}\u0000${event.conversationId}\u0000${event.messageId}`;
52
+ const previous = pending.events.get(key);
53
+ pending.events.set(key, previous ? { ...previous, delta: previous.delta + event.delta } : event);
54
+ }
19
55
  getSockets() {
20
56
  return this.sockets;
21
57
  }
22
58
  broadcast(event) {
23
- const payload = JSON.stringify(event);
59
+ const streaming = event.type === "message.delta" || event.type === "message.thinking";
24
60
  for (const socket of this.sockets) {
25
- if (socket.readyState === WebSocket.OPEN) {
26
- socket.send(payload);
61
+ if (socket.readyState !== WebSocket.OPEN)
62
+ continue;
63
+ if (streaming && this.coalescedSockets.has(socket))
64
+ this.enqueueStreaming(socket, event);
65
+ else {
66
+ // A state/tool/lifecycle event is a transcript boundary. Flush its preceding text first.
67
+ this.flushStreaming(socket);
68
+ socket.send(JSON.stringify(event));
27
69
  }
28
70
  }
29
71
  }
@@ -169,6 +211,10 @@ export class WsServer {
169
211
  socket.send(JSON.stringify({ type: "error", message: "command failed" }));
170
212
  return;
171
213
  }
214
+ if (parsed.type === "stream.configure") {
215
+ this.broadcaster.configureStreaming(socket, parsed.coalesce);
216
+ return;
217
+ }
172
218
  // A send represents a streaming operation. It must be admitted immediately so a second send
173
219
  // can observe the active run and enter the durable follow-up queue; waiting for the first model
174
220
  // turn here makes every follow-up test time out and defeats the queue semantics.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,6 +34,8 @@
34
34
  "check": "node scripts/run-check.mjs"
35
35
  },
36
36
  "check:logic": [
37
+ "scripts/stream-checkpoint.test.mjs",
38
+ "scripts/stream-transport.test.mjs",
37
39
  "scripts/lan-access.test.mjs",
38
40
  "scripts/terminal-pty.test.mjs",
39
41
  "scripts/persistence-repository.test.mjs",
@@ -69,7 +71,6 @@
69
71
  "scripts/revert-session-format.test.mjs",
70
72
  "scripts/revert-session-file.test.mjs",
71
73
  "scripts/revert-real-pi.test.mjs",
72
- "scripts/revert-browser.test.mjs",
73
74
  "scripts/bg-task.test.mjs",
74
75
  "scripts/todo-tool.test.mjs",
75
76
  "scripts/tool-calls.test.mjs",
@@ -84,6 +85,7 @@
84
85
  "scripts/active-memory.test.mjs"
85
86
  ],
86
87
  "check:browser": [
88
+ "scripts/revert-browser.test.mjs",
87
89
  "scripts/extension-ui-browser.test.mjs",
88
90
  "scripts/inline-rename-blur.test.mjs",
89
91
  "scripts/message-timestamps-browser.test.mjs",
@@ -93,6 +95,7 @@
93
95
  "scripts/workflow-browser.test.mjs",
94
96
  "scripts/conversation-reconcile-browser.test.mjs",
95
97
  "scripts/visual-defects-browser.test.mjs",
98
+ "scripts/session-state-animation-browser.test.mjs",
96
99
  "scripts/agent-vis-card-browser.test.mjs",
97
100
  "scripts/queue-dock.test.mjs",
98
101
  "scripts/md-render-browser.test.mjs",