@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.
- package/README.md +2 -2
- package/dist/api-renders-routes.d.ts +46 -5
- package/dist/api-renders-routes.d.ts.map +1 -1
- package/dist/api-renders-routes.js +98 -39
- package/dist/api-renders-stream-route.d.ts +2 -1
- package/dist/api-renders-stream-route.d.ts.map +1 -1
- package/dist/api-renders-stream-route.js +14 -2
- package/dist/build-mcp.d.ts +16 -0
- package/dist/build-mcp.d.ts.map +1 -1
- package/dist/build-mcp.js +48 -20
- package/dist/console-auth.d.ts +10 -10
- package/dist/console-auth.d.ts.map +1 -1
- package/dist/first-seen.d.ts +14 -0
- package/dist/first-seen.d.ts.map +1 -0
- package/dist/first-seen.js +19 -0
- package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -1
- package/dist/ggui-session-channel/action-ingress.js +96 -8
- package/dist/ggui-session-channel/channel-subscriptions.d.ts +0 -1
- package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -1
- package/dist/ggui-session-channel/channel-subscriptions.js +4 -1
- package/dist/ggui-session-channel/internal-types.d.ts +20 -12
- package/dist/ggui-session-channel/internal-types.d.ts.map +1 -1
- package/dist/ggui-session-channel/outbound.d.ts.map +1 -1
- package/dist/ggui-session-channel/outbound.js +18 -6
- package/dist/ggui-session-channel/stream-epoch.d.ts +61 -0
- package/dist/ggui-session-channel/stream-epoch.d.ts.map +1 -0
- package/dist/ggui-session-channel/stream-epoch.js +47 -0
- package/dist/ggui-session-channel/subscribe.d.ts +29 -17
- package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
- package/dist/ggui-session-channel/subscribe.js +247 -124
- package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -1
- package/dist/ggui-session-channel/subscriber-lifecycle.js +17 -7
- package/dist/ggui-session-channel.d.ts +13 -5
- package/dist/ggui-session-channel.d.ts.map +1 -1
- package/dist/ggui-session-channel.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/instructions-presets.d.ts +22 -21
- package/dist/instructions-presets.d.ts.map +1 -1
- package/dist/instructions-presets.js +38 -38
- package/dist/llm-backed-negotiator.d.ts +1 -36
- package/dist/llm-backed-negotiator.d.ts.map +1 -1
- package/dist/llm-backed-negotiator.js +0 -196
- package/dist/mcp-apps-outbound.d.ts +39 -90
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +45 -3
- package/dist/mcp-endpoint-routes.d.ts.map +1 -1
- package/dist/mcp-endpoint-routes.js +4 -1
- package/dist/server.d.ts +108 -33
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +181 -48
- package/dist/storage.d.ts +9 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +4 -3
- package/dist/view-proof-gate.d.ts +96 -0
- package/dist/view-proof-gate.d.ts.map +1 -0
- package/dist/view-proof-gate.js +167 -0
- package/package.json +14 -15
|
@@ -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
|
-
*
|
|
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 { PROTOCOL_SCHEMA_VERSION, UPGRADE_REQUIRED } from "@ggui-ai/protocol";
|
|
@@ -23,6 +23,56 @@ const BOOTSTRAP_PENDING_MARKER = "__bootstrap_pending__";
|
|
|
23
23
|
function isBootstrapPendingIdentity(identity) {
|
|
24
24
|
return identity.identity.kind === "user" && identity.identity.userId === BOOTSTRAP_PENDING_MARKER;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A subscriber's sink as every writer other than the subscribe tail sees it
|
|
28
|
+
* (ggui#1525). Until {@link release}, frames are held in arrival order;
|
|
29
|
+
* `release` flushes them and passes every later write straight through.
|
|
30
|
+
*
|
|
31
|
+
* The tail writes the ack and the replay frames through the raw sink, and
|
|
32
|
+
* awaits the ledger read in between when `sinceSequence` is set. Meanwhile
|
|
33
|
+
* the subscriber is registered, so the live pump, the direct walks (a
|
|
34
|
+
* `render` or `props_update`, a `drain_ack`, a cross-replica
|
|
35
|
+
* `externalBroadcast`) and a source-fed `channel_payload` poll can already
|
|
36
|
+
* write to it. Held here, their frames reach the wire after the replay,
|
|
37
|
+
* which is the order the ack promises: ack → replay → live.
|
|
38
|
+
*
|
|
39
|
+
* One writer stays outside, by design: `channel_error` goes straight to
|
|
40
|
+
* the socket, both when it answers the client's own `channel_subscribe`
|
|
41
|
+
* and when a source poll reports `POLL_FAILED`. It carries no stream
|
|
42
|
+
* state, and neither can precede the release: a poll exists only after a
|
|
43
|
+
* processed `channel_subscribe`, which the per-socket inbound chain
|
|
44
|
+
* handles after the subscribe resolves.
|
|
45
|
+
*
|
|
46
|
+
* A read that stalls holds these frames for as long as it stalls; the
|
|
47
|
+
* store's own read timeout is what bounds it.
|
|
48
|
+
*/
|
|
49
|
+
function holdUntilReplayed(sink) {
|
|
50
|
+
let held = [];
|
|
51
|
+
return {
|
|
52
|
+
sink: {
|
|
53
|
+
isOpen: () => sink.isOpen(),
|
|
54
|
+
end: (reason) => sink.end(reason),
|
|
55
|
+
write: (frame, opts) => {
|
|
56
|
+
if (held !== undefined) {
|
|
57
|
+
held.push({ frame, opts });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
sink.write(frame, opts);
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
release: () => {
|
|
64
|
+
const queue = held;
|
|
65
|
+
held = undefined;
|
|
66
|
+
if (queue === undefined)
|
|
67
|
+
return;
|
|
68
|
+
for (const { frame, opts } of queue) {
|
|
69
|
+
if (!sink.isOpen())
|
|
70
|
+
return;
|
|
71
|
+
sink.write(frame, opts);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
26
76
|
export function createSubscribeHandlers(deps) {
|
|
27
77
|
async function resolveIdentityFromUpgrade(req) {
|
|
28
78
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -166,7 +216,7 @@ export function createSubscribeHandlers(deps) {
|
|
|
166
216
|
// AuthAdapter identity — iframes don't carry bearer tokens.
|
|
167
217
|
// Mutually-exclusive on purpose.
|
|
168
218
|
let effectiveIdentity = identity;
|
|
169
|
-
let
|
|
219
|
+
let viaWsToken = false;
|
|
170
220
|
let tokenBoundAppId;
|
|
171
221
|
if (typeof payload.wsToken === "string" && payload.wsToken.length > 0) {
|
|
172
222
|
if (!deps.bootstrap) {
|
|
@@ -221,10 +271,7 @@ export function createSubscribeHandlers(deps) {
|
|
|
221
271
|
},
|
|
222
272
|
source: "apikey",
|
|
223
273
|
};
|
|
224
|
-
|
|
225
|
-
// work — so a downstream failure doesn't leave the client with
|
|
226
|
-
// no way to resume.
|
|
227
|
-
mintedSessionToken = deps.bootstrap.issueSessionToken(bound.sessionId, bound.appId);
|
|
274
|
+
viaWsToken = true;
|
|
228
275
|
deps.logger.info("render_channel_bootstrap_accepted", {
|
|
229
276
|
sessionId: bound.sessionId,
|
|
230
277
|
appId: bound.appId,
|
|
@@ -331,8 +378,13 @@ export function createSubscribeHandlers(deps) {
|
|
|
331
378
|
ws,
|
|
332
379
|
...(payload.sinceSequence !== undefined ? { sinceSequence: payload.sinceSequence } : {}),
|
|
333
380
|
...(payload.fromSeq !== undefined ? { fromSeq: payload.fromSeq } : {}),
|
|
381
|
+
...(typeof payload.fromEpoch === "string" && payload.fromEpoch !== "" ? { fromEpoch: payload.fromEpoch } : {}),
|
|
334
382
|
...(message.requestId ? { requestId: message.requestId } : {}),
|
|
335
|
-
...(
|
|
383
|
+
...(viaWsToken
|
|
384
|
+
? { credential: "ws_token" }
|
|
385
|
+
: cookieBound !== undefined
|
|
386
|
+
? { credential: "console_cookie" }
|
|
387
|
+
: {}),
|
|
336
388
|
});
|
|
337
389
|
}
|
|
338
390
|
async function completeSubscribe(args) {
|
|
@@ -342,10 +394,18 @@ export function createSubscribeHandlers(deps) {
|
|
|
342
394
|
// between here and registration gets seq > snapshotSeq, so the
|
|
343
395
|
// subscriber will receive it via live fan-out (not via replay).
|
|
344
396
|
//
|
|
345
|
-
// This is race-safe
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
|
|
397
|
+
// This is race-safe: a delivery landing between this read and
|
|
398
|
+
// `register(sub)` (the replay read below is awaited) is either in the
|
|
399
|
+
// replay or published to the fan-out iterator subscribed below, and
|
|
400
|
+
// the per-subscriber `seq <= replayCompletedSeq` guard drops the
|
|
401
|
+
// copies the replay already carries.
|
|
402
|
+
//
|
|
403
|
+
// The seq and the epoch it counts in are read at once when the buffer
|
|
404
|
+
// can (ggui#1531); a buffer that predates epochs gives the seq alone.
|
|
405
|
+
const snapshot = deps.streamBuffer.currentCursor !== undefined
|
|
406
|
+
? await deps.streamBuffer.currentCursor(stored.id)
|
|
407
|
+
: { seq: await deps.streamBuffer.currentSeq(stored.id) };
|
|
408
|
+
const snapshotSeq = snapshot.seq;
|
|
349
409
|
// Phase B: a render IS the addressable unit, so the active item
|
|
350
410
|
// is the resolved render's visible-bits surface itself.
|
|
351
411
|
const activeItem = stored.render;
|
|
@@ -360,8 +420,16 @@ export function createSubscribeHandlers(deps) {
|
|
|
360
420
|
const activeStreamSpec = activeItem.type !== "mcpApps" && activeItem.type !== "system"
|
|
361
421
|
? activeItem.streamSpec
|
|
362
422
|
: undefined;
|
|
423
|
+
//
|
|
424
|
+
// A resume whose cursor belongs to a counter that has since restarted
|
|
425
|
+
// (ggui#1531) replays everything retained, from 0, and reports the gap:
|
|
426
|
+
// its epoch differs from the session's, or, from a client that sent no
|
|
427
|
+
// epoch, its `fromSeq` is above anything the counter has assigned.
|
|
428
|
+
const counterRestarted = args.fromSeq !== undefined &&
|
|
429
|
+
((args.fromEpoch !== undefined && snapshot.epoch !== undefined && args.fromEpoch !== snapshot.epoch) ||
|
|
430
|
+
args.fromSeq > snapshotSeq);
|
|
363
431
|
const replay = args.fromSeq !== undefined
|
|
364
|
-
? await deps.streamBuffer.replay(stored.id, args.fromSeq, activeStreamSpec)
|
|
432
|
+
? await deps.streamBuffer.replay(stored.id, counterRestarted ? 0 : args.fromSeq, activeStreamSpec)
|
|
365
433
|
: await deps.streamBuffer.replay(stored.id, 0, undefined);
|
|
366
434
|
// Subscribe to the StreamFanout BEFORE constructing the Subscriber:
|
|
367
435
|
// the seam returns an AsyncIterable whose iterator we hand off; the
|
|
@@ -370,139 +438,194 @@ export function createSubscribeHandlers(deps) {
|
|
|
370
438
|
// point onward queues into our iterator — paired with the
|
|
371
439
|
// replayCompletedSeq cursor below, that's race-free.
|
|
372
440
|
const fanoutIter = deps.streamFanout.subscribe(stored.id)[Symbol.asyncIterator]();
|
|
441
|
+
const gate = holdUntilReplayed(sink);
|
|
373
442
|
const base = {
|
|
374
443
|
sessionId: stored.id,
|
|
375
444
|
appId: stored.appId,
|
|
376
445
|
identity: args.identity,
|
|
377
446
|
connectedAt: Date.now(),
|
|
378
|
-
|
|
447
|
+
replayCursor: { seq: snapshotSeq, epoch: snapshot.epoch },
|
|
379
448
|
iter: fanoutIter,
|
|
380
449
|
// Per-subscriber channel-subscribe tracker. Populated
|
|
381
450
|
// lazily by the `channel_subscribe` handler when the operator
|
|
382
451
|
// wired `streamWebSocketLocalTools`; stays empty otherwise
|
|
383
452
|
// (structurally so for SSE — no inbound channel).
|
|
384
453
|
channelSubs: new Map(),
|
|
385
|
-
|
|
454
|
+
// Every writer but this tail goes through the gate until the
|
|
455
|
+
// replay is on the wire (ggui#1525); the tail writes to `sink`.
|
|
456
|
+
sink: gate.sink,
|
|
386
457
|
};
|
|
387
458
|
const sub = args.transport === "ws"
|
|
388
459
|
? { ...base, transport: "ws", ws: args.ws }
|
|
389
460
|
: { ...base, transport: "sse" };
|
|
390
461
|
deps.register(sub);
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
transport: args.transport,
|
|
396
|
-
fromSeq: args.fromSeq,
|
|
397
|
-
snapshotSeq,
|
|
398
|
-
replayCount: replay?.envelopes.length ?? 0,
|
|
399
|
-
replayTruncated: replay?.truncated ?? false,
|
|
400
|
-
bootstrap: args.sessionToken !== undefined,
|
|
401
|
-
});
|
|
402
|
-
const ackPayload = {
|
|
403
|
-
sequence: stored.eventSequence,
|
|
404
|
-
timestamp: Date.now(),
|
|
405
|
-
session: stored.render,
|
|
406
|
-
streamSeq: snapshotSeq,
|
|
407
|
-
// Advertise the server's protocol version on every successful
|
|
408
|
-
// subscribe ack (SPEC §11.2.2). Clients whose
|
|
409
|
-
// CLIENT_SUPPORTED_VERSIONS doesn't contain this string surface
|
|
410
|
-
// UpgradeRequiredError to their caller; clients that don't wire
|
|
411
|
-
// the handshake ignore the field (legacy-pass-through).
|
|
412
|
-
serverVersion: PROTOCOL_SCHEMA_VERSION,
|
|
413
|
-
...(replay?.truncated ? { replayTruncated: true } : {}),
|
|
414
|
-
...(args.sessionToken !== undefined ? { sessionToken: args.sessionToken } : {}),
|
|
415
|
-
};
|
|
416
|
-
sink.write({
|
|
417
|
-
type: "ack",
|
|
418
|
-
payload: ackPayload,
|
|
419
|
-
...(args.requestId ? { requestId: args.requestId } : {}),
|
|
420
|
-
});
|
|
421
|
-
// R7 — GguiSessionEvent ledger replay. When `args.sinceSequence` is
|
|
422
|
-
// present, fetch events with `seq > sinceSequence` from the per-
|
|
423
|
-
// render ledger and emit each as a `render_event` wire frame
|
|
424
|
-
// BEFORE the per-channel stream-buffer replay. Consumers dispatch
|
|
425
|
-
// by `event.type` to fold the wire-frame-equivalent handler
|
|
426
|
-
// (render/props_update/etc.) — same cursor model as the HTTP
|
|
427
|
-
// `/api/sessions/:id/events?sinceSequence=N` endpoint.
|
|
428
|
-
//
|
|
429
|
-
// Each replay frame carries `resumeId` = the ledger seq — the SSE
|
|
430
|
-
// sink stamps it as the `id:` field so the browser's Last-Event-ID
|
|
431
|
-
// lands on the exact same cursor space; the WS sink ignores it.
|
|
462
|
+
// From here the subscriber is registered, so the live pump and the
|
|
463
|
+
// direct walks can write to it; the gate holds their frames until the
|
|
464
|
+
// ack and every replay frame below are written (ggui#1525), and the
|
|
465
|
+
// `finally` always releases them.
|
|
432
466
|
//
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
467
|
+
// A subscribe that throws past this point (a ledger read that fails)
|
|
468
|
+
// is not left registered (ggui#1528). The client may already hold the
|
|
469
|
+
// ack and would run on a partial replay with no signal, and on SSE
|
|
470
|
+
// nothing else would ever unregister it. So the subscriber is
|
|
471
|
+
// unregistered and its transport ended with `internal_error` (WS
|
|
472
|
+
// close 1011; the SSE stream ends), the client re-subscribes with a
|
|
473
|
+
// fresh replay, and the error still reaches the caller. Not
|
|
474
|
+
// `service_restart`: its 1012 invites an immediate reconnect, and a
|
|
475
|
+
// failure that repeats for one render (a row the store cannot read)
|
|
476
|
+
// would become a tight reconnect loop per open view.
|
|
477
|
+
try {
|
|
478
|
+
// The first ledger page is read BEFORE the ack (ggui#1528). It is the
|
|
479
|
+
// read that can fail for a row the store cannot read, and failing
|
|
480
|
+
// before the ack means the client gets no ack for a subscribe that
|
|
481
|
+
// could not complete: a client whose retry budget resets on an
|
|
482
|
+
// accepted ack then counts the attempt as failed and backs off to
|
|
483
|
+
// its cap, instead of reconnecting once a second forever. Only the
|
|
484
|
+
// first page: every SSE open carries `sinceSequence`, often 0, so
|
|
485
|
+
// reading every page first would hold a render's whole ledger in
|
|
486
|
+
// memory per open. Later pages stream after the ack as before.
|
|
487
|
+
const sinceSeqValid = args.sinceSequence !== undefined && Number.isInteger(args.sinceSequence) && args.sinceSequence >= 0
|
|
488
|
+
? args.sinceSequence
|
|
489
|
+
: undefined;
|
|
490
|
+
const firstLedgerPage = sinceSeqValid !== undefined ? await deps.renderStore.listEventsSince(stored.id, sinceSeqValid, 100) : undefined;
|
|
491
|
+
const resumeTruncated = args.fromSeq !== undefined && (replay?.truncated === true || counterRestarted);
|
|
492
|
+
deps.logger.info("render_channel_subscribed", {
|
|
493
|
+
sessionId: stored.id,
|
|
494
|
+
appId: stored.appId,
|
|
495
|
+
identityKind: args.identity.identity.kind,
|
|
496
|
+
transport: args.transport,
|
|
497
|
+
fromSeq: args.fromSeq,
|
|
498
|
+
snapshotSeq,
|
|
499
|
+
replayCount: replay?.envelopes.length ?? 0,
|
|
500
|
+
replayTruncated: resumeTruncated,
|
|
501
|
+
...(counterRestarted ? { counterRestarted: true } : {}),
|
|
502
|
+
// The credential the subscribe came on (ggui#1488): a wsToken, the
|
|
503
|
+
// console cookie, or the adapter's bearer source. The first two ride
|
|
504
|
+
// a synthetic identity whose own `source` would misreport them.
|
|
505
|
+
bootstrap: args.credential === "ws_token",
|
|
506
|
+
source: args.credential ?? args.identity.source,
|
|
507
|
+
});
|
|
508
|
+
const ackPayload = {
|
|
509
|
+
sequence: stored.eventSequence,
|
|
510
|
+
timestamp: Date.now(),
|
|
511
|
+
session: stored.render,
|
|
512
|
+
streamSeq: snapshotSeq,
|
|
513
|
+
// The epoch `streamSeq` counts in (ggui#1531), when the buffer has one.
|
|
514
|
+
...(snapshot.epoch !== undefined ? { streamEpoch: snapshot.epoch } : {}),
|
|
515
|
+
// Advertise the server's protocol version on every successful
|
|
516
|
+
// subscribe ack (SPEC §11.2.2). Clients whose
|
|
517
|
+
// CLIENT_SUPPORTED_VERSIONS doesn't contain this string surface
|
|
518
|
+
// UpgradeRequiredError to their caller; clients that don't wire
|
|
519
|
+
// the handshake ignore the field (legacy-pass-through).
|
|
520
|
+
serverVersion: PROTOCOL_SCHEMA_VERSION,
|
|
521
|
+
// Only a resume can have a gap: `replayTruncated` is "absent on
|
|
522
|
+
// fresh subscribes" (`AckPayload`). A fresh subscribe still walks the
|
|
523
|
+
// reserved channels from 0, and a buffer whose retention expired
|
|
524
|
+
// reports that walk truncated, which says nothing to this client.
|
|
525
|
+
...(resumeTruncated ? { replayTruncated: true } : {}),
|
|
526
|
+
};
|
|
527
|
+
sink.write({
|
|
528
|
+
type: "ack",
|
|
529
|
+
payload: ackPayload,
|
|
530
|
+
...(args.requestId ? { requestId: args.requestId } : {}),
|
|
531
|
+
});
|
|
532
|
+
// R7 — GguiSessionEvent ledger replay. When `args.sinceSequence` is
|
|
533
|
+
// present, fetch events with `seq > sinceSequence` from the per-
|
|
534
|
+
// render ledger and emit each as a `render_event` wire frame
|
|
535
|
+
// BEFORE the per-channel stream-buffer replay. Consumers dispatch
|
|
536
|
+
// by `event.type` to fold the wire-frame-equivalent handler
|
|
537
|
+
// (render/props_update/etc.) — same cursor model as the HTTP
|
|
538
|
+
// `/api/sessions/:id/events?sinceSequence=N` endpoint.
|
|
539
|
+
//
|
|
540
|
+
// Each replay frame carries `resumeId` = the ledger seq — the SSE
|
|
541
|
+
// sink stamps it as the `id:` field so the browser's Last-Event-ID
|
|
542
|
+
// lands on the exact same cursor space; the WS sink ignores it.
|
|
543
|
+
//
|
|
544
|
+
// Pagination: a hasMore-loop over 100-event pages (the HTTP
|
|
545
|
+
// route's default page size). Strict superset of the previous
|
|
546
|
+
// single-page-100 WS behavior.
|
|
547
|
+
//
|
|
548
|
+
// Horizon gate: a cursor below the server's replay horizon OR
|
|
549
|
+
// above `lastSequence` (stale from a different deployment) emits
|
|
550
|
+
// an error frame with `code: 'REPLAY_HORIZON_PASSED'` and skips
|
|
551
|
+
// the replay. The frame carries `resumeId` = lastSequence — a
|
|
552
|
+
// dispatched id-bearing frame advances the browser's Last-Event-ID
|
|
553
|
+
// to the fresh high-water mark, and the ack snapshot already
|
|
554
|
+
// re-mounted state. Client recovery: re-mount from a fresh /state
|
|
555
|
+
// read (WS) / the already-delivered ack (SSE).
|
|
556
|
+
if (args.sinceSequence !== undefined) {
|
|
557
|
+
if (sinceSeqValid === undefined) {
|
|
558
|
+
sink.write({
|
|
559
|
+
type: "error",
|
|
560
|
+
payload: {
|
|
561
|
+
code: "INVALID_SINCE_SEQUENCE",
|
|
562
|
+
message: "sinceSequence must be a non-negative integer",
|
|
563
|
+
},
|
|
564
|
+
...(args.requestId ? { requestId: args.requestId } : {}),
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
const sinceSeq = sinceSeqValid;
|
|
569
|
+
let cursor = sinceSeq;
|
|
570
|
+
let firstPage = true;
|
|
571
|
+
for (;;) {
|
|
572
|
+
// The first page was read before the ack; later pages are read here.
|
|
573
|
+
const ledger = firstPage
|
|
574
|
+
? firstLedgerPage
|
|
575
|
+
: await deps.renderStore.listEventsSince(stored.id, cursor, 100);
|
|
576
|
+
if (ledger === null || ledger === undefined) {
|
|
577
|
+
// GguiSession disappeared between resolve and ledger read —
|
|
578
|
+
// already handled by the broader error envelope path;
|
|
579
|
+
// nothing to do here.
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
if (firstPage && (sinceSeq > ledger.lastSequence || sinceSeq < ledger.horizonSeq)) {
|
|
583
|
+
sink.write({
|
|
584
|
+
type: "error",
|
|
585
|
+
payload: {
|
|
586
|
+
code: "REPLAY_HORIZON_PASSED",
|
|
587
|
+
message: `cursor ${sinceSeq} is outside replayable range [${ledger.horizonSeq}, ${ledger.lastSequence}]`,
|
|
588
|
+
details: { currentSequence: ledger.lastSequence },
|
|
589
|
+
},
|
|
590
|
+
...(args.requestId ? { requestId: args.requestId } : {}),
|
|
591
|
+
}, { resumeId: String(ledger.lastSequence) });
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
firstPage = false;
|
|
595
|
+
for (const event of ledger.events) {
|
|
596
|
+
// GguiSessionEvent is now the wire-shape ledger primitive
|
|
597
|
+
// (Wave 7 of flatten-render-identity, 2026-05-28); no
|
|
598
|
+
// projection — emit the store's row directly.
|
|
599
|
+
sink.write({ type: "render_event", payload: event }, { resumeId: String(event.seq) });
|
|
600
|
+
cursor = event.seq;
|
|
601
|
+
}
|
|
602
|
+
// Defensive: an empty page with hasMore would loop forever;
|
|
603
|
+
// the store contract never produces it, but a broken adapter
|
|
604
|
+
// must not spin the server.
|
|
605
|
+
if (!ledger.hasMore || ledger.events.length === 0)
|
|
606
|
+
break;
|
|
487
607
|
}
|
|
488
|
-
// Defensive: an empty page with hasMore would loop forever;
|
|
489
|
-
// the store contract never produces it, but a broken adapter
|
|
490
|
-
// must not spin the server.
|
|
491
|
-
if (!ledger.hasMore || ledger.events.length === 0)
|
|
492
|
-
break;
|
|
493
608
|
}
|
|
494
609
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
610
|
+
// Send replay frames AFTER the ack. Ordering by `seq` ASC — the
|
|
611
|
+
// buffer returns them pre-sorted. Client sees ack(streamSeq=N) →
|
|
612
|
+
// up to N replay `data` frames → live tail (seq > N). No explicit
|
|
613
|
+
// "replay end" marker is needed; the client uses envelope.seq as
|
|
614
|
+
// the single source of truth for ordering.
|
|
615
|
+
if (replay) {
|
|
616
|
+
for (const env of replay.envelopes) {
|
|
617
|
+
sink.write({ type: "data", payload: env });
|
|
618
|
+
}
|
|
504
619
|
}
|
|
505
620
|
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
deps.unregister(sub);
|
|
623
|
+
sink.end("internal_error");
|
|
624
|
+
throw err;
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
627
|
+
gate.release();
|
|
628
|
+
}
|
|
506
629
|
return {
|
|
507
630
|
detach: () => {
|
|
508
631
|
deps.unregister(sub);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscriber-lifecycle.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/subscriber-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"subscriber-lifecycle.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/subscriber-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEpE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,aAAa,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,kFAAkF;IAClF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;CACnC;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,uBAAuB,GAAG,mBAAmB,CA0H5F"}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* hosted cross-replica pubsub scoping keys on "does this replica hold any
|
|
18
18
|
* subscriber for this render", regardless of transport.
|
|
19
19
|
*/
|
|
20
|
+
import { admitLiveEnvelope } from './stream-epoch.js';
|
|
20
21
|
export function createSubscriberLifecycle(deps) {
|
|
21
22
|
/**
|
|
22
23
|
* Per-render local subscriber count. Drives the
|
|
@@ -35,12 +36,12 @@ export function createSubscriberLifecycle(deps) {
|
|
|
35
36
|
* frames with `seq <= replayCompletedSeq` were (or will be)
|
|
36
37
|
* delivered via the replay path on subscribe.
|
|
37
38
|
*
|
|
38
|
-
* The
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
39
|
+
* The ordering invariant ack → replay frames → live frames is kept by
|
|
40
|
+
* the subscribe tail, not by timing: `sub.sink` is gated until the
|
|
41
|
+
* tail has written the ack and every replay frame, so whatever the
|
|
42
|
+
* pump writes meanwhile is held and flushed after them. The tail is
|
|
43
|
+
* NOT synchronous when `sinceSequence` is set (it awaits the ledger
|
|
44
|
+
* read), which is why the gate exists (ggui#1525).
|
|
44
45
|
*/
|
|
45
46
|
async function pumpSubscriber(sub) {
|
|
46
47
|
try {
|
|
@@ -48,7 +49,16 @@ export function createSubscriberLifecycle(deps) {
|
|
|
48
49
|
const { value, done } = await sub.iter.next();
|
|
49
50
|
if (done)
|
|
50
51
|
return;
|
|
51
|
-
|
|
52
|
+
const admission = admitLiveEnvelope(sub.replayCursor, value);
|
|
53
|
+
if (admission.epochChanged !== undefined) {
|
|
54
|
+
deps.logger.info("stream_epoch_changed", {
|
|
55
|
+
sessionId: sub.sessionId,
|
|
56
|
+
from: admission.epochChanged.from,
|
|
57
|
+
to: admission.epochChanged.to,
|
|
58
|
+
transport: sub.transport,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (!admission.deliver)
|
|
52
62
|
continue;
|
|
53
63
|
if (!sub.sink.isOpen()) {
|
|
54
64
|
await sub.iter.return?.();
|
|
@@ -59,7 +59,7 @@ import type { IncomingMessage } from "node:http";
|
|
|
59
59
|
import type { Duplex } from "node:stream";
|
|
60
60
|
import { type GguiSessionChannelLocalToolsOptions } from "./ggui-session-channel/channel-subscriptions.js";
|
|
61
61
|
import type { SubscriberSink } from "./ggui-session-channel/internal-types.js";
|
|
62
|
-
import { type GguiSessionChannelBootstrap, type GguiSessionChannelCookieAuth } from "./ggui-session-channel/subscribe.js";
|
|
62
|
+
import { type GguiSessionChannelBootstrap, type GguiSessionChannelCookieAuth, type SubscribeCredential } from "./ggui-session-channel/subscribe.js";
|
|
63
63
|
import type { Logger } from "./logger.js";
|
|
64
64
|
/** Default URL path for the channel endpoint. Operators can override. */
|
|
65
65
|
export declare const DEFAULT_RENDER_CHANNEL_PATH = "/ws";
|
|
@@ -112,7 +112,7 @@ export declare const DEFAULT_PRE_SUBSCRIBE_IDLE_MS = 30000;
|
|
|
112
112
|
*/
|
|
113
113
|
export declare const DEFAULT_MAX_PRE_SUBSCRIBE_CONNECTIONS = 1024;
|
|
114
114
|
export type { GguiSessionChannelLocalToolsOptions } from "./ggui-session-channel/channel-subscriptions.js";
|
|
115
|
-
export type { GguiSessionChannelBootstrap, GguiSessionChannelBootstrapVerifyResult, GguiSessionChannelCookieAuth, } from "./ggui-session-channel/subscribe.js";
|
|
115
|
+
export type { GguiSessionChannelBootstrap, GguiSessionChannelBootstrapVerifyResult, GguiSessionChannelCookieAuth, SubscribeCredential, } from "./ggui-session-channel/subscribe.js";
|
|
116
116
|
export type { SubscriberSink } from "./ggui-session-channel/internal-types.js";
|
|
117
117
|
/**
|
|
118
118
|
* Thrown by {@link GguiSessionChannelServer.attachExternalSubscriber}
|
|
@@ -223,9 +223,9 @@ export interface GguiSessionChannelOptions {
|
|
|
223
223
|
readonly streamFanout?: StreamFanout;
|
|
224
224
|
/**
|
|
225
225
|
* Optional bootstrap-auth plumbing. When present, the channel
|
|
226
|
-
* accepts `SubscribePayload.wsToken
|
|
227
|
-
*
|
|
228
|
-
*
|
|
226
|
+
* accepts `SubscribePayload.wsToken`; it mints no reconnect
|
|
227
|
+
* credential (ggui#1488). When absent, bootstrap tokens are rejected
|
|
228
|
+
* with `BOOTSTRAP_NOT_SUPPORTED`.
|
|
229
229
|
*/
|
|
230
230
|
readonly bootstrap?: GguiSessionChannelBootstrap;
|
|
231
231
|
/**
|
|
@@ -580,6 +580,14 @@ export interface GguiSessionChannelServer {
|
|
|
580
580
|
readonly sink: SubscriberSink;
|
|
581
581
|
readonly sinceSequence?: number;
|
|
582
582
|
readonly fromSeq?: number;
|
|
583
|
+
/** The epoch `fromSeq` was counted in (ggui#1531); read only beside it. */
|
|
584
|
+
readonly fromEpoch?: string;
|
|
585
|
+
/**
|
|
586
|
+
* The credential the transport authenticated (ggui#1488), logged as
|
|
587
|
+
* `render_channel_subscribed.source`; the SSE route passes
|
|
588
|
+
* `"ws_token"`. Absent: the identity's own auth source.
|
|
589
|
+
*/
|
|
590
|
+
readonly credential?: SubscribeCredential;
|
|
583
591
|
}): Promise<{
|
|
584
592
|
readonly detach: () => void;
|
|
585
593
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ggui-session-channel.d.ts","sourceRoot":"","sources":["../src/ggui-session-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACd,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,OAAO,EAEL,KAAK,mCAAmC,EACzC,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAEV,cAAc,EAGf,MAAM,0CAA0C,CAAC;AAGlD,OAAO,EAEL,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,
|
|
1
|
+
{"version":3,"file":"ggui-session-channel.d.ts","sourceRoot":"","sources":["../src/ggui-session-channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -204,7 +204,6 @@ export function createGguiSessionChannelServer(opts) {
|
|
|
204
204
|
renderStore: opts.renderStore,
|
|
205
205
|
localTools: opts.streamWebSocketLocalTools,
|
|
206
206
|
subscribersByWs,
|
|
207
|
-
send,
|
|
208
207
|
sendChannelError,
|
|
209
208
|
});
|
|
210
209
|
// Action ingress + consume bridge — contract gate, ledger + pipe
|
|
@@ -312,6 +311,8 @@ export function createGguiSessionChannelServer(opts) {
|
|
|
312
311
|
transport: "sse",
|
|
313
312
|
...(args.sinceSequence !== undefined ? { sinceSequence: args.sinceSequence } : {}),
|
|
314
313
|
...(args.fromSeq !== undefined ? { fromSeq: args.fromSeq } : {}),
|
|
314
|
+
...(args.fromEpoch !== undefined ? { fromEpoch: args.fromEpoch } : {}),
|
|
315
|
+
...(args.credential !== undefined ? { credential: args.credential } : {}),
|
|
315
316
|
});
|
|
316
317
|
},
|
|
317
318
|
get subscriberCount() {
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type { HandlerContext, SharedHandler } from '@ggui-ai/mcp-server-handlers
|
|
|
29
29
|
export type { GadgetDescriptor, McpUiDisplayMode, GguiSession, SystemGguiSession, } from '@ggui-ai/protocol';
|
|
30
30
|
export type { GenerationDeps } from '@ggui-ai/mcp-server-handlers';
|
|
31
31
|
export { buildNoCredentialsGguiSession } from '@ggui-ai/mcp-server-handlers';
|
|
32
|
-
export { buildOpsBundleHandlers, createGguiServer, defaultHandlers } from './server.js';
|
|
32
|
+
export { buildOpsBundleHandlers, createGguiServer, defaultHandlers, DEFAULT_CLOSE_GRACE_MS } from './server.js';
|
|
33
33
|
export type { CreateGguiServerOptions, ErrorMapperResult, GguiServer, OpsBundleDeps, } from './server.js';
|
|
34
34
|
export { AnthropicStructuredCallError, buildLlmCaller } from './llm-backed-negotiator.js';
|
|
35
35
|
export type { AnthropicStructuredCallFailureKind, BuildLlmCallerOptions, } from './llm-backed-negotiator.js';
|