@ggui-ai/mcp-server 0.25.0 → 0.26.0

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.
Files changed (59) hide show
  1. package/README.md +2 -2
  2. package/dist/api-renders-routes.d.ts +46 -5
  3. package/dist/api-renders-routes.d.ts.map +1 -1
  4. package/dist/api-renders-routes.js +98 -39
  5. package/dist/api-renders-stream-route.d.ts +2 -1
  6. package/dist/api-renders-stream-route.d.ts.map +1 -1
  7. package/dist/api-renders-stream-route.js +14 -2
  8. package/dist/build-mcp.d.ts +16 -0
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +48 -20
  11. package/dist/console-auth.d.ts +10 -10
  12. package/dist/console-auth.d.ts.map +1 -1
  13. package/dist/first-seen.d.ts +14 -0
  14. package/dist/first-seen.d.ts.map +1 -0
  15. package/dist/first-seen.js +19 -0
  16. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -1
  17. package/dist/ggui-session-channel/action-ingress.js +96 -8
  18. package/dist/ggui-session-channel/channel-subscriptions.d.ts +0 -1
  19. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -1
  20. package/dist/ggui-session-channel/channel-subscriptions.js +4 -1
  21. package/dist/ggui-session-channel/internal-types.d.ts +20 -12
  22. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -1
  23. package/dist/ggui-session-channel/outbound.d.ts.map +1 -1
  24. package/dist/ggui-session-channel/outbound.js +18 -6
  25. package/dist/ggui-session-channel/stream-epoch.d.ts +61 -0
  26. package/dist/ggui-session-channel/stream-epoch.d.ts.map +1 -0
  27. package/dist/ggui-session-channel/stream-epoch.js +47 -0
  28. package/dist/ggui-session-channel/subscribe.d.ts +29 -17
  29. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
  30. package/dist/ggui-session-channel/subscribe.js +247 -124
  31. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -1
  32. package/dist/ggui-session-channel/subscriber-lifecycle.js +17 -7
  33. package/dist/ggui-session-channel.d.ts +13 -5
  34. package/dist/ggui-session-channel.d.ts.map +1 -1
  35. package/dist/ggui-session-channel.js +2 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/instructions-presets.d.ts +22 -21
  40. package/dist/instructions-presets.d.ts.map +1 -1
  41. package/dist/instructions-presets.js +38 -38
  42. package/dist/llm-backed-negotiator.d.ts +1 -36
  43. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  44. package/dist/llm-backed-negotiator.js +0 -196
  45. package/dist/mcp-apps-outbound.d.ts +39 -90
  46. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  47. package/dist/mcp-apps-outbound.js +45 -3
  48. package/dist/mcp-endpoint-routes.d.ts.map +1 -1
  49. package/dist/mcp-endpoint-routes.js +4 -1
  50. package/dist/server.d.ts +108 -33
  51. package/dist/server.d.ts.map +1 -1
  52. package/dist/server.js +181 -48
  53. package/dist/storage.d.ts +9 -1
  54. package/dist/storage.d.ts.map +1 -1
  55. package/dist/storage.js +4 -3
  56. package/dist/view-proof-gate.d.ts +96 -0
  57. package/dist/view-proof-gate.d.ts.map +1 -0
  58. package/dist/view-proof-gate.js +167 -0
  59. package/package.json +14 -15
@@ -0,0 +1,19 @@
1
+ export function createFirstSeen(capacity) {
2
+ if (!Number.isInteger(capacity) || capacity < 1) {
3
+ throw new RangeError(`capacity must be a positive integer, got ${capacity}`);
4
+ }
5
+ const seen = new Set();
6
+ return {
7
+ first(key) {
8
+ if (seen.has(key))
9
+ return false;
10
+ if (seen.size >= capacity) {
11
+ const oldest = seen.values().next();
12
+ if (!oldest.done)
13
+ seen.delete(oldest.value);
14
+ }
15
+ seen.add(key);
16
+ return true;
17
+ },
18
+ };
19
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"action-ingress.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/action-ingress.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAOvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA+G9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,mBAAmB,CACjB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CA+K1E"}
1
+ {"version":3,"file":"action-ingress.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/action-ingress.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAEhB,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAOlC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA+G9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,mBAAmB,CACjB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CA6O1E"}
@@ -177,13 +177,63 @@ export function createActionIngress(deps) {
177
177
  // reaped) degrades to ledger-only with a warn — the WS client has
178
178
  // no `ui/message` fallback to branch on, so a new error frame
179
179
  // would be vocabulary without a consumer.
180
+ //
181
+ // The append's outcome (ggui#1517) is not read here: this path mints
182
+ // the pipe id per frame (`toConsumeEventEntry`), so a duplicate cannot
183
+ // arise, and the ledger write is this path's load-bearing one.
184
+ // The pipe entry (and its minted actionId, which also names the holder of
185
+ // a oneShot spend, ggui#1424) is built once per frame.
186
+ const entry = envelope.type === "data:submit" ? toConsumeEventEntry(envelope, sub.sessionId) : null;
187
+ // ggui#1424 — the channel door refuses a second gesture on a spent
188
+ // oneShot too, so the server's refusal holds at both doors. The claim is
189
+ // taken BEFORE the writes under this frame's minted actionId; the answer
190
+ // decides as it does on the tool door (`claimDispatchSpend` there):
191
+ // already-spent by another holder whose delivery is marked → refused with
192
+ // the channel's existing CONTRACT_VIOLATION error frame (#1358), one
193
+ // violation at `actionSpec.<name>.oneShot`, no ledger row, no pipe entry,
194
+ // one named line; an unmarked holder is taken over; the same holder, no
195
+ // holder, a non-oneShot action, a store that answers no claim, or a store
196
+ // failure (fail-open, named) → the frame goes on as before. The store
197
+ // enforces `delivered`: the refusal arm reads it only as the store
198
+ // answers it. Delivery is marked after the writes below.
199
+ const framePayload = envelope.payload;
200
+ const oneShotPayload = entry !== null &&
201
+ activeItem !== undefined &&
202
+ framePayload !== null &&
203
+ typeof framePayload === "object" &&
204
+ !Array.isArray(framePayload) &&
205
+ typeof framePayload.action === "string"
206
+ ? { action: framePayload.action, data: framePayload.data }
207
+ : null;
208
+ if (oneShotPayload !== null && entry !== null && activeItem !== undefined) {
209
+ const verdict = await claimChannelSpend(deps, sub.sessionId, activeItem, oneShotPayload, entry.actionId);
210
+ if (verdict.kind === "refuse") {
211
+ deps.logger.warn("render_channel_one_shot_refused", {
212
+ sessionId: sub.sessionId,
213
+ action: oneShotPayload.action,
214
+ actionId: entry.actionId,
215
+ by: verdict.by,
216
+ });
217
+ deps.sendError(ws, "CONTRACT_VIOLATION", `actionSpec.${oneShotPayload.action} is declared oneShot and already fired on this card; a second gesture is refused`, message.requestId, {
218
+ error: "contract_violation",
219
+ violations: [
220
+ {
221
+ field: `actionSpec.${oneShotPayload.action}.oneShot`,
222
+ keyword: "oneShot",
223
+ message: "this one-shot action already fired on this card",
224
+ expected: verdict.by,
225
+ received: entry.actionId,
226
+ },
227
+ ],
228
+ hint: "The action already fired on this card. Render a new card to fire it again.",
229
+ });
230
+ return;
231
+ }
232
+ }
180
233
  const consumeWrite = (() => {
181
- if (deps.pendingEventConsumer === undefined || envelope.type !== "data:submit") {
234
+ if (deps.pendingEventConsumer === undefined || entry === null) {
182
235
  return Promise.resolve();
183
236
  }
184
- const entry = toConsumeEventEntry(envelope, sub.sessionId);
185
- if (entry === null)
186
- return Promise.resolve();
187
237
  return deps.pendingEventConsumer.append(sub.sessionId, {
188
238
  // The pipe entry's stable id doubles as the `drain_ack` key —
189
239
  // same convention as the relay path's iframe-supplied id.
@@ -218,10 +268,12 @@ export function createActionIngress(deps) {
218
268
  return;
219
269
  }
220
270
  const seq = ledgerResult.value;
221
- // ggui#1223 / #1305 — the gesture is in the ledger, so a committed
222
- // `oneShot` now spends its card durably, BEFORE the ack: a client that
223
- // holds the ack can reload and find the card spent. FAIL-OPEN, as on the
224
- // tool path: a failed record is named and never withholds the ack.
271
+ // ggui#1223 / #1305 / #1424 — the gesture is in the ledger, so the
272
+ // holder MARKS its spend delivered (claimed above, before the writes),
273
+ // BEFORE the ack: a client that holds the ack can reload and find the
274
+ // card spent. FAIL-OPEN, as on the tool path: a failed record is named
275
+ // and never withholds the ack. With no pipe entry (the frame did not
276
+ // parse as a dispatch) the spend is recorded held by nobody, as before.
225
277
  if (envelope.type === "data:submit" && activeItem !== undefined) {
226
278
  const payload = envelope.payload;
227
279
  if (payload !== null &&
@@ -235,6 +287,7 @@ export function createActionIngress(deps) {
235
287
  render: activeItem,
236
288
  action: payload.action,
237
289
  data: payload.data,
290
+ ...(entry !== null ? { actionId: entry.actionId, delivered: true } : {}),
238
291
  });
239
292
  }
240
293
  catch (err) {
@@ -254,3 +307,38 @@ export function createActionIngress(deps) {
254
307
  }
255
308
  return { handleInboundAction };
256
309
  }
310
+ async function claimChannelSpend(deps, sessionId, render, payload, actionId) {
311
+ const claimOnce = async (spend) => {
312
+ try {
313
+ return await recordCommittedOneShot({
314
+ store: deps.renderStore,
315
+ sessionId,
316
+ render,
317
+ action: payload.action,
318
+ data: payload.data,
319
+ ...spend,
320
+ });
321
+ }
322
+ catch (err) {
323
+ deps.logger.warn("render_channel_spent_oneshot_persist_failed", {
324
+ sessionId,
325
+ action: payload.action,
326
+ error: err instanceof Error ? err.message : String(err),
327
+ });
328
+ return undefined;
329
+ }
330
+ };
331
+ const first = await claimOnce({ actionId });
332
+ const claim = first?.claim;
333
+ if (claim === undefined || claim.outcome !== "already-spent" || claim.by === undefined || claim.by === actionId) {
334
+ return { kind: "proceed" };
335
+ }
336
+ if (claim.delivered === true)
337
+ return { kind: "refuse", by: claim.by };
338
+ const retaken = await claimOnce({ actionId, reclaimFrom: claim.by });
339
+ const second = retaken?.claim;
340
+ if (second === undefined || second.outcome !== "already-spent" || second.by === undefined || second.by === actionId) {
341
+ return { kind: "proceed" };
342
+ }
343
+ return { kind: "refuse", by: second.by };
344
+ }
@@ -69,7 +69,6 @@ export interface ChannelSubscriptionsDeps {
69
69
  * subscriber.
70
70
  */
71
71
  readonly subscribersByWs: WeakMap<WebSocket, WsSubscriber>;
72
- readonly send: Outbound["send"];
73
72
  readonly sendChannelError: Outbound["sendChannelError"];
74
73
  }
75
74
  export interface ChannelSubscriptions {
@@ -1 +1 @@
1
- {"version":3,"file":"channel-subscriptions.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/channel-subscriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAA4B,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAa9C;;;;;;;GAOG;AACH,MAAM,WAAW,mCAAmC;IAClD;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,mCAAmC,GAAG,SAAS,CAAC;IACrE;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,sBAAsB,CACpB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,mBAAmB,CAAA;KAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;OAKG;IACH,wBAAwB,CACtB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,qBAAqB,CAAA;KAAE,GAC1D,IAAI,CAAC;CACT;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CAiQ/F"}
1
+ {"version":3,"file":"channel-subscriptions.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/channel-subscriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAA4B,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAa9C;;;;;;;GAOG;AACH,MAAM,WAAW,mCAAmC;IAClD;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,mCAAmC,GAAG,SAAS,CAAC;IACrE;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC3D,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,sBAAsB,CACpB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,mBAAmB,CAAA;KAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;OAKG;IACH,wBAAwB,CACtB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,qBAAqB,CAAA;KAAE,GAC1D,IAAI,CAAC;CACT;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CAoQ/F"}
@@ -68,7 +68,10 @@ export function createChannelSubscriptions(deps) {
68
68
  if (sub.ws.readyState !== sub.ws.OPEN)
69
69
  return;
70
70
  state.seq += 1;
71
- deps.send(sub.ws, {
71
+ // Through the subscriber's sink, like every data-carrying writer,
72
+ // so a poll that lands while the subscribe tail is still writing
73
+ // its replay is held until after it (ggui#1525).
74
+ sub.sink.write({
72
75
  type: "channel_payload",
73
76
  payload: {
74
77
  sessionId: sub.sessionId,
@@ -3,6 +3,7 @@
3
3
  * of the package's public surface — everything publishable is exported
4
4
  * (or re-exported) from `../ggui-session-channel.ts`.
5
5
  */
6
+ import type { StreamReplayCursor } from './stream-epoch.js';
6
7
  import type { AuthResult, BufferedStreamEnvelope } from "@ggui-ai/mcp-server-core";
7
8
  import type { JsonObject } from "@ggui-ai/protocol";
8
9
  import type { WebSocketMessage } from "@ggui-ai/protocol/transport/websocket";
@@ -30,12 +31,16 @@ export interface SubscriberSink {
30
31
  }): void;
31
32
  /**
32
33
  * Terminate the transport. WS maps `service_restart` → close 1012
33
- * (reconnect immediately — same server family is still up) and
34
+ * (reconnect immediately — same server family is still up),
34
35
  * `session_expired` → close 1008 (policy violation — do not blind-
35
- * reconnect); SSE ends the response, letting `EventSource`
36
- * auto-reconnect hit the HTTP pre-gate for the authoritative verdict.
36
+ * reconnect), and `internal_error` → close 1011 (the server could not
37
+ * complete the subscribe; a client reconnects with its ordinary
38
+ * backoff, since a failure that may repeat must not invite an
39
+ * immediate retry, ggui#1528). SSE ends the response for every reason,
40
+ * letting `EventSource` auto-reconnect hit the HTTP pre-gate for the
41
+ * authoritative verdict.
37
42
  */
38
- end(reason: "service_restart" | "session_expired"): void;
43
+ end(reason: "service_restart" | "session_expired" | "internal_error"): void;
39
44
  }
40
45
  /**
41
46
  * Fields shared by every subscriber regardless of transport (one
@@ -48,16 +53,19 @@ interface SubscriberBase {
48
53
  readonly identity: AuthResult;
49
54
  readonly connectedAt: number;
50
55
  /**
51
- * Largest outbound `seq` the initial replay (or subscribe snapshot)
52
- * covered for this subscriber. Live fan-out skips envelopes with
53
- * `seq <= replayCompletedSeq` to prevent double delivery — those
54
- * were (or will be) delivered via the replay phase.
56
+ * The replay boundary: the largest outbound `seq` the initial replay
57
+ * (or subscribe snapshot) covered for this subscriber, and the epoch
58
+ * that `seq` counts in (ggui#1531). Live fan-out skips envelopes of the
59
+ * same epoch with `seq <= replayCursor.seq` to prevent double delivery,
60
+ * since those were (or will be) delivered via the replay phase. A live
61
+ * envelope of another epoch means the counter restarted: it is
62
+ * delivered and moves the cursor (`admitLiveEnvelope`).
55
63
  *
56
- * For fresh subscribers (no `fromSeq`), this is the stream cursor
57
- * at subscribe time; they never see the pre-existing buffer, only
58
- * new deliveries.
64
+ * For fresh subscribers (no `fromSeq`), `seq` is the stream cursor at
65
+ * subscribe time; they never see the pre-existing buffer, only new
66
+ * deliveries.
59
67
  */
60
- readonly replayCompletedSeq: number;
68
+ readonly replayCursor: StreamReplayCursor;
61
69
  /**
62
70
  * Per-subscriber live-tail iterator from `streamFanout.subscribe`.
63
71
  * Owned by the subscriber for its full lifetime; ending it (via
@@ -1 +1 @@
1
- {"version":3,"file":"internal-types.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/internal-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,MAAM,IAAI,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5E;;;;;;OAMG;IACH,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,IAAI,CAAC;CAC1D;AAED;;;;GAIG;AACH,UAAU,cAAc;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;;;;;OASG;IACH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IACrD;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAC5D,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;CAC3B;AAED,mEAAmE;AACnE,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,aAAa,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;CAChD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,iBAAiB,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D"}
1
+ {"version":3,"file":"internal-types.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/internal-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,MAAM,IAAI,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5E;;;;;;;;;;OAUG;IACH,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,CAAC;CAC7E;AAED;;;;GAIG;AACH,UAAU,cAAc;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IACrD;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAC5D,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;CAC3B;AAED,mEAAmE;AACnE,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,aAAa,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;CAChD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,iBAAiB,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D"}
@@ -1 +1 @@
1
- {"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/outbound.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/outbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,EACb,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,wBAAwB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEtE,oEAAoE;AACpE,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,mBAAmB,GACnB,mBAAmB,GACnB,wBAAwB,GACxB,aAAa,CAAC;AAElB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,oEAAoE;IACpE,QAAQ,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC/C,0DAA0D;IAC1D,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;CAClF;AAgBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAc1E;AAED,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjD,8DAA8D;IAC9D,SAAS,CACP,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,GAChC,IAAI,CAAC;IACR;;;;;;;;;;OAUG;IACH,gBAAgB,CACd,EAAE,EAAE,SAAS,EACb,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,GAChC,IAAI,CAAC;IACR,sEAAsE;IACtE,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3E,4EAA4E;IAC5E,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1F,oEAAoE;IACpE,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF,iEAAiE;IACjE,YAAY,CAAC,IAAI,EAAE;QACjB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,GAAG,IAAI,CAAC;IACT,sEAAsE;IACtE,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACrE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAmO3D"}
@@ -11,6 +11,7 @@
11
11
  * `GguiSessionChannelServer` interface in `../ggui-session-channel.ts`;
12
12
  * inline comments here cover impl-level decisions only.
13
13
  */
14
+ import { admitLiveEnvelope } from "./stream-epoch.js";
14
15
  import { assertStreamContract } from "@ggui-ai/mcp-server-handlers/renders";
15
16
  /**
16
17
  * Low-level ws wire write shared by the closure-bound `send` helper and
@@ -43,7 +44,7 @@ export function createWsSink(ws, logger) {
43
44
  },
44
45
  end: (reason) => {
45
46
  try {
46
- ws.close(reason === "service_restart" ? 1012 : 1008, reason);
47
+ ws.close(reason === "service_restart" ? 1012 : reason === "internal_error" ? 1011 : 1008, reason);
47
48
  }
48
49
  catch {
49
50
  /* best-effort — socket may already be closing */
@@ -204,11 +205,22 @@ export function createOutbound(deps) {
204
205
  // subscribe-replay (policy 'all'/'latest') or is deliberately
205
206
  // invisible to late subscribers (policy 'none') — never re-send.
206
207
  // Unstamped frames were never buffered; replay cannot duplicate
207
- // them, so they pass. Non-data frames carry no seq semantics.
208
- if (frame.type === "data" &&
209
- typeof frame.payload.seq === "number" &&
210
- frame.payload.seq <= sub.replayCompletedSeq) {
211
- continue;
208
+ // them, so they pass. A frame of another epoch means the counter
209
+ // restarted: it passes and moves the subscriber's cursor, by the
210
+ // same rule as the pump (ggui#1531). Non-data frames carry no seq
211
+ // semantics.
212
+ if (frame.type === "data") {
213
+ const admission = admitLiveEnvelope(sub.replayCursor, frame.payload);
214
+ if (admission.epochChanged !== undefined) {
215
+ deps.logger.info("stream_epoch_changed", {
216
+ sessionId,
217
+ from: admission.epochChanged.from,
218
+ to: admission.epochChanged.to,
219
+ transport: sub.transport,
220
+ });
221
+ }
222
+ if (!admission.deliver)
223
+ continue;
212
224
  }
213
225
  sub.sink.write(frame);
214
226
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The live tail's replay boundary, with the stream counter's epoch
3
+ * (ggui#1531). One rule, shared by the two places a live data frame is
4
+ * filtered against a subscriber's replay: the in-process pump
5
+ * (`subscriber-lifecycle.ts`) and the cross-replica direct walk
6
+ * (`outbound.ts`, `externalBroadcast`).
7
+ *
8
+ * A `seq` counts within one counter. When the counter restarts while a
9
+ * subscriber stays attached (a hosted counter evicted or expired, a
10
+ * server restarted over a persistent session store), the next frame
11
+ * arrives at a low `seq` with a new epoch. Filtering it by the old
12
+ * boundary would drop it, and every later one, with no signal: the
13
+ * frame of another epoch is delivered instead, and the subscriber moves
14
+ * to the new epoch with an empty boundary.
15
+ */
16
+ /**
17
+ * A subscriber's replay boundary: the latest `seq` its snapshot and
18
+ * replay covered, and the epoch that `seq` counts in (`undefined` when
19
+ * the snapshot had none: no counter yet, or a buffer that predates
20
+ * epochs). Mutable: a live frame of another epoch moves it.
21
+ */
22
+ export interface StreamReplayCursor {
23
+ seq: number;
24
+ epoch: string | undefined;
25
+ /**
26
+ * The epochs this cursor has moved away from, newest last, at most
27
+ * {@link RETIRED_EPOCHS_KEPT}. A late frame of one of them (a
28
+ * cross-replica frame published just before the restart) is dropped, so
29
+ * it cannot move the cursor back and empty its boundary again.
30
+ */
31
+ retired?: string[];
32
+ }
33
+ /** How many epochs a cursor remembers having left. */
34
+ export declare const RETIRED_EPOCHS_KEPT = 4;
35
+ /** What to do with one live data frame for one subscriber. */
36
+ export interface LiveAdmission {
37
+ readonly deliver: boolean;
38
+ /** Set when the frame moved the subscriber to another epoch. */
39
+ readonly epochChanged?: {
40
+ readonly from: string;
41
+ readonly to: string;
42
+ };
43
+ }
44
+ /**
45
+ * Decide whether a live data frame reaches a subscriber, and move its
46
+ * cursor when the frame's epoch differs:
47
+ * - an unstamped frame (no `seq`) was never buffered and always passes;
48
+ * - a late frame of an epoch the cursor has left is dropped;
49
+ * - a frame of another epoch than the cursor's is delivered, and the
50
+ * cursor moves to that epoch with an empty boundary (a restart);
51
+ * - the first epoch a cursor without one sees is adopted, and its seq
52
+ * boundary stands (the snapshot simply had no epoch yet);
53
+ * - otherwise a frame at or below the boundary was, or will be, sent
54
+ * by the replay, and is skipped.
55
+ * An absent epoch on the frame is unknown, never a mismatch.
56
+ */
57
+ export declare function admitLiveEnvelope(cursor: StreamReplayCursor, envelope: {
58
+ readonly seq?: number;
59
+ readonly streamEpoch?: string;
60
+ }): LiveAdmission;
61
+ //# sourceMappingURL=stream-epoch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-epoch.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/stream-epoch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACjE,aAAa,CAcf"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The live tail's replay boundary, with the stream counter's epoch
3
+ * (ggui#1531). One rule, shared by the two places a live data frame is
4
+ * filtered against a subscriber's replay: the in-process pump
5
+ * (`subscriber-lifecycle.ts`) and the cross-replica direct walk
6
+ * (`outbound.ts`, `externalBroadcast`).
7
+ *
8
+ * A `seq` counts within one counter. When the counter restarts while a
9
+ * subscriber stays attached (a hosted counter evicted or expired, a
10
+ * server restarted over a persistent session store), the next frame
11
+ * arrives at a low `seq` with a new epoch. Filtering it by the old
12
+ * boundary would drop it, and every later one, with no signal: the
13
+ * frame of another epoch is delivered instead, and the subscriber moves
14
+ * to the new epoch with an empty boundary.
15
+ */
16
+ /** How many epochs a cursor remembers having left. */
17
+ export const RETIRED_EPOCHS_KEPT = 4;
18
+ /**
19
+ * Decide whether a live data frame reaches a subscriber, and move its
20
+ * cursor when the frame's epoch differs:
21
+ * - an unstamped frame (no `seq`) was never buffered and always passes;
22
+ * - a late frame of an epoch the cursor has left is dropped;
23
+ * - a frame of another epoch than the cursor's is delivered, and the
24
+ * cursor moves to that epoch with an empty boundary (a restart);
25
+ * - the first epoch a cursor without one sees is adopted, and its seq
26
+ * boundary stands (the snapshot simply had no epoch yet);
27
+ * - otherwise a frame at or below the boundary was, or will be, sent
28
+ * by the replay, and is skipped.
29
+ * An absent epoch on the frame is unknown, never a mismatch.
30
+ */
31
+ export function admitLiveEnvelope(cursor, envelope) {
32
+ if (envelope.seq === undefined)
33
+ return { deliver: true };
34
+ const epoch = envelope.streamEpoch;
35
+ if (epoch !== undefined && cursor.retired?.includes(epoch) === true)
36
+ return { deliver: false };
37
+ if (epoch !== undefined && epoch !== cursor.epoch) {
38
+ const from = cursor.epoch;
39
+ cursor.epoch = epoch;
40
+ if (from !== undefined) {
41
+ cursor.retired = [...(cursor.retired ?? []), from].slice(-RETIRED_EPOCHS_KEPT);
42
+ cursor.seq = 0;
43
+ return { deliver: true, epochChanged: { from, to: epoch } };
44
+ }
45
+ }
46
+ return { deliver: envelope.seq > cursor.seq };
47
+ }
@@ -2,8 +2,8 @@
2
2
  * Subscribe / credential-path family for the live channel — upgrade-
3
3
  * time identity resolution across the three auth planes (bearer via
4
4
  * the AuthAdapter, ws-token bootstrap, console cookie) and the
5
- * `subscribe` handler itself: version handshake, bootstrap verify +
6
- * reconnect-credential mint, identity-default appId resolution,
5
+ * `subscribe` handler itself: version handshake, bootstrap verify,
6
+ * identity-default appId resolution,
7
7
  * dev-mode render provisioning, replay, registration, and the ack.
8
8
  */
9
9
  import type { AuthAdapter, AuthResult, GguiSessionStore, GguiSessionStreamBuffer, StoredGguiSession, StreamFanout } from "@ggui-ai/mcp-server-core";
@@ -25,10 +25,11 @@ import type { SubscriberLifecycle } from "./subscriber-lifecycle.js";
25
25
  * (invalid sig, expired, wrong kind, replayed, etc.).
26
26
  * 2. The bound `sessionId` MUST match the one on the subscribe
27
27
  * payload. Mismatches are rejected with a clean error.
28
- * 3. On success, the server mints a reconnect credential via
29
- * `issueSessionToken(sessionId, appId)` and returns it in
30
- * `AckPayload.sessionToken`. The iframe stores this for WS
31
- * reconnects via the normal bearer path.
28
+ * 3. On success, the subscribe binds to that identity. No reconnect
29
+ * credential is minted (ggui#1488): a reconnect presents a live
30
+ * `wsToken` again, renewed before it expires through
31
+ * `GET /api/sessions/:id/state`, or at any age through
32
+ * `ggui_runtime_refresh_ws_token`.
32
33
  *
33
34
  * Bootstrap auth is MUTUALLY EXCLUSIVE with the upstream `AuthAdapter`
34
35
  * bearer path at subscribe time — when a bootstrap token is present,
@@ -67,12 +68,6 @@ export interface GguiSessionChannelBootstrap {
67
68
  * kind failures.
68
69
  */
69
70
  verify(token: string): GguiSessionChannelBootstrapVerifyResult;
70
- /**
71
- * Mint a longer-lived reconnect credential to return in
72
- * `AckPayload.sessionToken`. Called only after a successful
73
- * `verify()` on a bootstrap subscribe.
74
- */
75
- issueSessionToken(sessionId: string, appId: string): string;
76
71
  }
77
72
  /**
78
73
  * Cookie-based authentication for the live-channel upgrade. Used
@@ -128,6 +123,12 @@ export interface SubscribeDeps {
128
123
  * `/api/sessions/:sessionId/stream`) runs BEFORE this; the tail trusts
129
124
  * `stored` + `identity` as already-authorized.
130
125
  */
126
+ /**
127
+ * A subscribe's credential when it is not the auth adapter's bearer
128
+ * (ggui#1488): a `wsToken`, or the console cookie. Logged as
129
+ * `render_channel_subscribed.source`.
130
+ */
131
+ export type SubscribeCredential = "ws_token" | "console_cookie";
131
132
  export type CompleteSubscribeArgs = {
132
133
  /** Resolved render row the subscriber binds to. */
133
134
  readonly stored: StoredGguiSession;
@@ -139,10 +140,17 @@ export type CompleteSubscribeArgs = {
139
140
  readonly sinceSequence?: number;
140
141
  /** Stream-buffer cursor — replays `data` frames per policy when set. */
141
142
  readonly fromSeq?: number;
143
+ /** The epoch `fromSeq` was counted in (ggui#1531); read only beside it. */
144
+ readonly fromEpoch?: string;
142
145
  /** Correlates the ack / error frames to a WS request. SSE has none. */
143
146
  readonly requestId?: string;
144
- /** Bootstrap-minted reconnect credential to stamp on the ack. */
145
- readonly sessionToken?: string;
147
+ /**
148
+ * The credential the subscribe came on, when it is not the auth
149
+ * adapter's bearer: a `wsToken` (the bootstrap path, on the WebSocket or
150
+ * the SSE stream) or the console cookie. Both resolve to a synthetic
151
+ * identity whose own `source` would misreport them.
152
+ */
153
+ readonly credential?: SubscribeCredential;
146
154
  } & ({
147
155
  readonly transport: "ws";
148
156
  /** The socket — kept on the subscriber row for inbound dispatch. */
@@ -168,9 +176,13 @@ export interface SubscribeHandlers {
168
176
  /**
169
177
  * The transport-neutral subscribe tail, order preserved exactly:
170
178
  * stream-cursor snapshot → stream-buffer replay read → StreamFanout
171
- * iterator → register (pump starts) → ack → GguiSessionEvent-ledger
172
- * replay (`render_event` frames, `resumeId` = ledger seq) →
173
- * stream-replay `data` frames. WS `handleSubscribe` calls this after
179
+ * iterator → register (pump starts; every write to the subscriber's
180
+ * sink is held until the replay below is written, ggui#1525) → the
181
+ * first GguiSessionEvent-ledger page read, when `sinceSequence` is set
182
+ * (before the ack, so a failed read sends none, ggui#1528) → ack →
183
+ * ledger replay (`render_event` frames, `resumeId` = ledger seq; later
184
+ * pages read as they go) → stream-replay `data` frames → release of the
185
+ * held frames. WS `handleSubscribe` calls this after
174
186
  * its auth/provision head; SSE attaches call it via
175
187
  * `GguiSessionChannelServer.attachExternalSubscriber`.
176
188
  *
@@ -1 +1 @@
1
- {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/subscribe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/subscribe.ts"],"names":[],"mappings":"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"}