@ggui-ai/protocol-reference-server 0.2.0-alpha.4 → 0.4.0-rc.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/dist/render.d.ts CHANGED
@@ -1,62 +1,102 @@
1
1
  /**
2
- * In-memory render store for the reference server.
2
+ * In-memory GguiSession store for the reference server.
3
3
  *
4
- * Renders are ephemeral and process-local — this is the whole
4
+ * GguiSessions are ephemeral and process-local — this is the whole
5
5
  * point of the reference server. Persistence is explicitly out of
6
6
  * scope. Restart drops state; that's documented behavior, not a TODO.
7
7
  *
8
- * Each render carries an actionSpec map that the `register-actionspec`
9
- * ConformanceHost directive populates. The action router
10
- * (`./action-router.ts`) consults this map at dispatch time to
11
- * resolve action-name → tool-name → handler.
8
+ * Each GguiSession carries:
9
+ * - an optional declared `actionSpec` (installed by the
10
+ * `create-session` ConformanceHost directive) — the action
11
+ * handler (`./action-router.ts`) enforces the declared-action
12
+ * contract (name membership + per-entry payload schema) for
13
+ * inbound `data:submit` envelopes against it;
14
+ * - an event ledger (the consume buffer) — appended actions get a
15
+ * monotonic per-session `sequence` the action ack echoes back;
16
+ * - an outbound stream-sequence cursor — the `emit-envelope`
17
+ * ConformanceHost directive stamps `StreamEnvelope.seq` on
18
+ * injected `{type:'data'}` frames from it;
19
+ * - an optional persisted `hostContext` — the
20
+ * `host_context_observed` Client→Server handler
21
+ * (`./host-context.ts`) overwrites it with the iframe-observed
22
+ * `HostContextProjection`; the ConformanceHost's
23
+ * `readSessionField` seam reads it back for `session-state`
24
+ * grading.
12
25
  *
13
26
  * Wire-shape note: the conformance kit drives the reference server
14
- * over the SPEC §12.2 wire using the field name `sessionId` (its
15
- * fixture catalog has not yet been migrated to the canonical
16
- * `renderId`). The reference server READS that wire field and binds
17
- * it to a `renderId` internally — the value is a render identity
18
- * regardless of which spelling the consumer sends.
27
+ * over the SPEC §12.2 wire using the canonical render-identity field
28
+ * `sessionId`.
19
29
  */
30
+ import type { ActionSpec, HostContextProjection } from '@ggui-ai/protocol';
20
31
  /**
21
- * Actionspec entry maps an action name (the value wired into DOM
22
- * `data-ggui-action` attributes on real UIs; here sent verbatim on
23
- * the fixture's inputEnvelope) to the registered tool name the
24
- * router should dispatch to.
32
+ * Deployment-level default app id — this server's identity-default
33
+ * (SPEC §12.2: a subscribe MAY omit `appId`; the server resolves the
34
+ * caller's identity-default app). The reference server's identity
35
+ * model is no-auth — every caller is the same anonymous identity — so
36
+ * the per-identity mapping a real deployment configures collapses to
37
+ * one deployment-wide constant. The value matches the conformance
38
+ * kit's conventional tenant (`'conformance'`, the appId the kit's
39
+ * runner stamps on its subscribe frames) because grading by the kit is
40
+ * the only deployment context this package has.
25
41
  */
26
- export interface ActionSpecEntry {
27
- readonly name: string;
28
- readonly tool: string;
29
- }
42
+ export declare const DEPLOYMENT_DEFAULT_APP_ID = "conformance";
30
43
  /**
31
- * Streamspec entry binds a stream channel to a "refresh tool" — the
32
- * tool the action-router invokes after a successful wired-action
33
- * dispatch to produce the channel's next snapshot. Mirrors the real
34
- * `streamSpec[channel].tool` shape declared by ggui blueprints (SPEC
35
- * §2.3 StreamSpec refresh triggers): a wired action mutates state,
36
- * the refresh tool reads fresh state, the stream-update wraps the
37
- * read into an envelope on the named channel.
38
- *
39
- * Reference-server scope: refresh-tool invocation is unconditional
40
- * — every successful wired-action dispatch fans out through every
41
- * registered streamSpec for the render. Real ggui servers may
42
- * filter by which actions touch which channels; the reference
43
- * server's narrower contract is "any successful action triggers all
44
- * declared refreshes", which is sufficient for the kit's
45
- * `stream-refresh-success` proof and stays under the package's
46
- * 20–50 LOC budget for refresh-stream support.
44
+ * One appended consume-buffer event. The ledger is the reference
45
+ * server's stand-in for a real ggui server's persisted event store —
46
+ * `sequence` is assigned at append time, starts at 1, and increases
47
+ * monotonically per session. The agent-side drain (`ggui_consume`) is
48
+ * an MCP surface this WS-only server does not implement; the ledger
49
+ * exists so the ack's `payload.sequence` is a real persistence proof,
50
+ * not a fabricated counter.
47
51
  */
48
- export interface StreamSpecEntry {
49
- readonly channel: string;
50
- readonly tool: string;
52
+ export interface SessionEvent {
53
+ readonly sequence: number;
54
+ /** Event type, e.g. `'user.submitted'` for inbound actions. */
55
+ readonly type: string;
56
+ /** The appended envelope body, verbatim. */
57
+ readonly data: unknown;
51
58
  }
52
- export interface Render {
53
- readonly renderId: string;
59
+ export interface GguiSession {
60
+ readonly sessionId: string;
54
61
  readonly appId: string;
55
- readonly actionSpecs: Map<string, ActionSpecEntry>;
56
- readonly streamSpecs: Map<string, StreamSpecEntry>;
62
+ /**
63
+ * Declared actionSpec — the protocol's `ActionSpec` (action name →
64
+ * `ActionEntry`, including each entry's optional payload `schema`).
65
+ * `undefined` means no contract is declared and the action handler
66
+ * accepts every action (mirroring the first-party
67
+ * `assertActionContract` no-op on undeclared specs). Installed by
68
+ * the `create-session` directive's `actionSpec` field (the
69
+ * ConformanceHost adapter maps the kit's declaration onto the
70
+ * protocol type); never mutated after install.
71
+ */
72
+ actionSpec?: ActionSpec;
73
+ /** Consume-buffer event ledger. Append via {@link appendEvent}. */
74
+ readonly events: SessionEvent[];
75
+ /**
76
+ * Outbound stream-delivery sequence cursor — the `seq` stamped on
77
+ * the most recent `{type:'data'}` StreamEnvelope emitted for this
78
+ * render. Starts at 0 (nothing emitted yet); advanced by
79
+ * {@link GguiSessionStore.nextStreamSeq}. Per-render and monotonic,
80
+ * mirroring the StreamEnvelope sequencing contract (`seq` starts at
81
+ * 1, gap-free within a render). Distinct from the consume-buffer
82
+ * ledger's `sequence` — that counts INBOUND appended actions; this
83
+ * counts OUTBOUND stream deliveries.
84
+ */
85
+ streamSeq: number;
86
+ /**
87
+ * Persisted `HostContextProjection` — written by the
88
+ * `host_context_observed` Client→Server handler
89
+ * (`./host-context.ts`) as an idempotent overwrite (re-delivery
90
+ * replaces, never merges). `undefined` until the client's first
91
+ * observation lands. Read back through the ConformanceHost's
92
+ * `readSessionField('hostContext')` introspection seam, which is how
93
+ * the kit's `session-state` fixtures grade the persistence
94
+ * obligation.
95
+ */
96
+ hostContext?: HostContextProjection;
57
97
  readonly subscribers: Set<Subscriber>;
58
98
  /**
59
- * Per-render protocol-version override. When set, the WS subscribe
99
+ * Per-GguiSession protocol-version override. When set, the WS subscribe
60
100
  * + UPGRADE_REQUIRED paths advertise this value in place of the
61
101
  * server-instance-level `versionOverride`.
62
102
  *
@@ -65,85 +105,109 @@ export interface Render {
65
105
  * `server-version-override` setup directive in the conformance host
66
106
  * adapter — production code paths leave this `undefined`.
67
107
  *
68
- * Why per-render, not per-instance: parallel kit fixtures share
108
+ * Why per-GguiSession, not per-instance: parallel kit fixtures share
69
109
  * one `ReferenceServer`. Mutating the instance-level override would
70
- * leak across renders; the per-render field scopes the mismatch
110
+ * leak across GguiSessions; the per-GguiSession field scopes the mismatch
71
111
  * to the one fixture that asked for it.
72
112
  */
73
113
  versionOverride?: string;
74
114
  }
75
115
  /**
76
- * Minimal subscriber handle — the action router calls `send()` to
77
- * emit stream frames (including `_ggui:contract-error`) back to the
78
- * subscribed WebSocket.
116
+ * Minimal subscriber handle — the server calls `send()` to emit
117
+ * frames (acks, errors, stream envelopes) back to the subscribed
118
+ * WebSocket.
79
119
  */
80
120
  export interface Subscriber {
81
121
  send(frame: unknown): void;
82
122
  }
83
123
  /**
84
- * In-memory render store. Wraps a `Map<renderId, Render>` with
124
+ * Append one event to the session's consume-buffer ledger and return
125
+ * the monotonic sequence it was assigned. The action handler calls
126
+ * this BEFORE acking — append-then-ack is the ordering contract the
127
+ * kit's `action-ack-sequence` fixture grades.
128
+ */
129
+ export declare function appendEvent(render: GguiSession, event: {
130
+ readonly type: string;
131
+ readonly data: unknown;
132
+ }): number;
133
+ /**
134
+ * In-memory GguiSession store. Wraps a `Map<sessionId, GguiSession>` keyed by sessionId with
85
135
  * the operations the ConformanceHost adapter + WS subscribe handler
86
136
  * need. No locking — JS single-threaded; all calls originate from
87
137
  * the event loop.
88
138
  */
89
- export declare class RenderStore {
139
+ export declare class GguiSessionStore {
90
140
  private readonly renders;
91
141
  private lastCreated;
92
- create(renderId: string, appId: string): Render;
142
+ create(sessionId: string, appId: string): GguiSession;
93
143
  /**
94
- * The renderId most recently passed to `create()`. Used by the
95
- * ConformanceHost's `register-actionspec` dispatcher — that
96
- * directive doesn't carry a renderId in its JSON shape, so the
97
- * adapter needs the "most recently created" scope to bind the
98
- * actionspec to. This matches the fixture-authoring convention that
99
- * create-render always precedes register-actionspec.
144
+ * The sessionId most recently passed to `create()`. Used by the
145
+ * ConformanceHost's directive dispatchers whose JSON shapes don't
146
+ * carry a sessionId (`server-version-override`, `emit-envelope`) —
147
+ * the adapter scopes them to the "most recently created" render,
148
+ * matching the fixture-authoring convention that create-session
149
+ * always precedes the scoped directive.
100
150
  */
101
- lastCreatedRenderId(): string | undefined;
102
- get(renderId: string): Render | undefined;
103
- close(renderId: string): boolean;
104
- addSubscriber(renderId: string, subscriber: Subscriber): Render;
105
- removeSubscriber(renderId: string, subscriber: Subscriber): void;
106
- registerActionSpec(renderId: string, entry: ActionSpecEntry): void;
151
+ lastCreatedSessionId(): string | undefined;
152
+ get(sessionId: string): GguiSession | undefined;
153
+ close(sessionId: string): boolean;
154
+ addSubscriber(sessionId: string, subscriber: Subscriber): GguiSession;
155
+ removeSubscriber(sessionId: string, subscriber: Subscriber): void;
107
156
  /**
108
- * Register a stream channel ↔ refresh-tool binding on the named
109
- * render. Same "create-if-missing" semantics as
110
- * {@link registerActionSpec} so the ConformanceHost adapter can
111
- * dispatch this directive before subscribe lands. Keyed by
112
- * `entry.channel` — registering the same channel twice replaces
113
- * the prior binding, matching the action-spec map's behavior.
157
+ * Install the declared actionSpec on the named render. Same
158
+ * "create-if-missing" semantics as the other setters so directive
159
+ * ordering relative to subscribe doesn't matter. Called by the
160
+ * `create-session` ConformanceHost directive when the fixture
161
+ * authors an `actionSpec` — actions are part of the render's
162
+ * identity, so the declaration rides session creation rather than a
163
+ * separate registration directive.
114
164
  */
115
- registerStreamSpec(renderId: string, entry: StreamSpecEntry): void;
165
+ declareActionSpec(sessionId: string, actionSpec: ActionSpec): void;
116
166
  /**
117
167
  * Set the per-render protocol-version override. Used by the
118
168
  * `server-version-override` ConformanceHost directive — populates
119
- * {@link Render.versionOverride} so the WS subscribe handler
169
+ * {@link GguiSession.versionOverride} so the WS subscribe handler
120
170
  * advertises this value (and emits UPGRADE_REQUIRED keyed off it)
121
- * for THIS render only, leaving parallel renders on the instance-
171
+ * for THIS GguiSession only, leaving parallel GguiSessions on the instance-
122
172
  * level default.
123
173
  *
124
- * Same "create-if-missing" semantics as the other register* setters
125
- * so directive ordering relative to subscribe doesn't matter.
174
+ * Same "create-if-missing" semantics as the other setters so
175
+ * directive ordering relative to subscribe doesn't matter.
176
+ */
177
+ setVersionOverride(sessionId: string, version: string): void;
178
+ /**
179
+ * Assign the next outbound stream sequence for the named render —
180
+ * advances {@link GguiSession.streamSeq} and returns the new value
181
+ * (first assignment = 1). Called by the `emit-envelope`
182
+ * ConformanceHost directive when stamping the `StreamEnvelope` it
183
+ * injects. Sequence assignment is emission-scoped, not
184
+ * delivery-scoped: a frame emitted with zero subscribers attached
185
+ * still consumes its sequence number, mirroring buffer-backed
186
+ * servers that assign `seq` at append time regardless of who is
187
+ * connected.
188
+ *
189
+ * Same "create-if-missing" semantics as the other setters so
190
+ * directive ordering relative to subscribe doesn't matter.
126
191
  */
127
- setVersionOverride(renderId: string, version: string): void;
192
+ nextStreamSeq(sessionId: string): number;
128
193
  /**
129
194
  * Fan out a frame to every subscriber on the named render. Used by
130
195
  * the `emit-envelope` ConformanceHost directive — kit fixtures use
131
196
  * it to inject WS-observable side-effects (envelopes the server
132
197
  * would not normally emit on its own) so the kit can assert
133
- * downstream consequences (sequencing, fan-out, observability).
198
+ * downstream consequences (sequencing, fan-out).
134
199
  *
135
- * Returns `true` if the render existed and at least one subscriber
136
- * received the frame; `false` if the render is unknown OR has no
200
+ * Returns `true` if the GguiSession existed and at least one subscriber
201
+ * received the frame; `false` if the GguiSession is unknown OR has no
137
202
  * subscribers attached. Caller may use the boolean to log a warning
138
203
  * when a fixture's directive-injection lands before any subscribe
139
204
  * — the directive then has no observable effect, which is usually
140
205
  * a fixture-authoring bug worth surfacing.
141
206
  *
142
207
  * Subscriber-level send failures (closed socket, etc.) are
143
- * swallowed per the same convention as the action router's
144
- * `broadcast()` — one bad subscriber must not block fan-out to the
208
+ * swallowed — one bad subscriber must not block fan-out to the
145
209
  * rest.
146
210
  */
147
- injectFrame(renderId: string, frame: unknown): boolean;
211
+ injectFrame(sessionId: string, frame: unknown): boolean;
148
212
  }
149
213
  //# sourceMappingURL=render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,WAAW,CAAqB;IAExC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAkB/C;;;;;;;OAOG;IACH,mBAAmB,IAAI,MAAM,GAAG,SAAS;IAIzC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIzC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIhC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM;IAM/D,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI;IAMhE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI;IAKlE;;;;;;;OAOG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI;IAKlE;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAK3D;;;;;;;;;;;;;;;;;;OAkBG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;CAcvD"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,gBAAgB,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC;IAChC;;;;;;;;;OASG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACvD,MAAM,CAIR;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,WAAW,CAAqB;IAExC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW;IAkBrD;;;;;;;OAOG;IACH,oBAAoB,IAAI,MAAM,GAAG,SAAS;IAI1C,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI/C,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIjC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,WAAW;IAMrE,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI;IAMjE;;;;;;;;OAQG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI;IAKlE;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAK5D;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAMxC;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;CAcxD"}
package/dist/render.js CHANGED
@@ -1,128 +1,147 @@
1
1
  /**
2
- * In-memory render store for the reference server.
3
- *
4
- * Renders are ephemeral and process-local — this is the whole
5
- * point of the reference server. Persistence is explicitly out of
6
- * scope. Restart drops state; that's documented behavior, not a TODO.
7
- *
8
- * Each render carries an actionSpec map that the `register-actionspec`
9
- * ConformanceHost directive populates. The action router
10
- * (`./action-router.ts`) consults this map at dispatch time to
11
- * resolve action-name → tool-name → handler.
12
- *
13
- * Wire-shape note: the conformance kit drives the reference server
14
- * over the SPEC §12.2 wire using the field name `sessionId` (its
15
- * fixture catalog has not yet been migrated to the canonical
16
- * `renderId`). The reference server READS that wire field and binds
17
- * it to a `renderId` internally — the value is a render identity
18
- * regardless of which spelling the consumer sends.
2
+ * Deployment-level default app id — this server's identity-default
3
+ * (SPEC §12.2: a subscribe MAY omit `appId`; the server resolves the
4
+ * caller's identity-default app). The reference server's identity
5
+ * model is no-auth — every caller is the same anonymous identity — so
6
+ * the per-identity mapping a real deployment configures collapses to
7
+ * one deployment-wide constant. The value matches the conformance
8
+ * kit's conventional tenant (`'conformance'`, the appId the kit's
9
+ * runner stamps on its subscribe frames) because grading by the kit is
10
+ * the only deployment context this package has.
19
11
  */
12
+ export const DEPLOYMENT_DEFAULT_APP_ID = 'conformance';
20
13
  /**
21
- * In-memory render store. Wraps a `Map<renderId, Render>` with
14
+ * Append one event to the session's consume-buffer ledger and return
15
+ * the monotonic sequence it was assigned. The action handler calls
16
+ * this BEFORE acking — append-then-ack is the ordering contract the
17
+ * kit's `action-ack-sequence` fixture grades.
18
+ */
19
+ export function appendEvent(render, event) {
20
+ const sequence = render.events.length + 1;
21
+ render.events.push({ sequence, type: event.type, data: event.data });
22
+ return sequence;
23
+ }
24
+ /**
25
+ * In-memory GguiSession store. Wraps a `Map<sessionId, GguiSession>` keyed by sessionId with
22
26
  * the operations the ConformanceHost adapter + WS subscribe handler
23
27
  * need. No locking — JS single-threaded; all calls originate from
24
28
  * the event loop.
25
29
  */
26
- export class RenderStore {
30
+ export class GguiSessionStore {
27
31
  renders = new Map();
28
32
  lastCreated;
29
- create(renderId, appId) {
30
- const existing = this.renders.get(renderId);
33
+ create(sessionId, appId) {
34
+ const existing = this.renders.get(sessionId);
31
35
  if (existing !== undefined) {
32
- this.lastCreated = renderId;
36
+ this.lastCreated = sessionId;
33
37
  return existing;
34
38
  }
35
39
  const render = {
36
- renderId,
40
+ sessionId,
37
41
  appId,
38
- actionSpecs: new Map(),
39
- streamSpecs: new Map(),
42
+ events: [],
43
+ streamSeq: 0,
40
44
  subscribers: new Set(),
41
45
  };
42
- this.renders.set(renderId, render);
43
- this.lastCreated = renderId;
46
+ this.renders.set(sessionId, render);
47
+ this.lastCreated = sessionId;
44
48
  return render;
45
49
  }
46
50
  /**
47
- * The renderId most recently passed to `create()`. Used by the
48
- * ConformanceHost's `register-actionspec` dispatcher — that
49
- * directive doesn't carry a renderId in its JSON shape, so the
50
- * adapter needs the "most recently created" scope to bind the
51
- * actionspec to. This matches the fixture-authoring convention that
52
- * create-render always precedes register-actionspec.
51
+ * The sessionId most recently passed to `create()`. Used by the
52
+ * ConformanceHost's directive dispatchers whose JSON shapes don't
53
+ * carry a sessionId (`server-version-override`, `emit-envelope`) —
54
+ * the adapter scopes them to the "most recently created" render,
55
+ * matching the fixture-authoring convention that create-session
56
+ * always precedes the scoped directive.
53
57
  */
54
- lastCreatedRenderId() {
58
+ lastCreatedSessionId() {
55
59
  return this.lastCreated;
56
60
  }
57
- get(renderId) {
58
- return this.renders.get(renderId);
61
+ get(sessionId) {
62
+ return this.renders.get(sessionId);
59
63
  }
60
- close(renderId) {
61
- return this.renders.delete(renderId);
64
+ close(sessionId) {
65
+ return this.renders.delete(sessionId);
62
66
  }
63
- addSubscriber(renderId, subscriber) {
64
- const render = this.create(renderId, 'conformance');
67
+ addSubscriber(sessionId, subscriber) {
68
+ const render = this.create(sessionId, DEPLOYMENT_DEFAULT_APP_ID);
65
69
  render.subscribers.add(subscriber);
66
70
  return render;
67
71
  }
68
- removeSubscriber(renderId, subscriber) {
69
- const render = this.renders.get(renderId);
72
+ removeSubscriber(sessionId, subscriber) {
73
+ const render = this.renders.get(sessionId);
70
74
  if (render === undefined)
71
75
  return;
72
76
  render.subscribers.delete(subscriber);
73
77
  }
74
- registerActionSpec(renderId, entry) {
75
- const render = this.create(renderId, 'conformance');
76
- render.actionSpecs.set(entry.name, entry);
77
- }
78
78
  /**
79
- * Register a stream channel ↔ refresh-tool binding on the named
80
- * render. Same "create-if-missing" semantics as
81
- * {@link registerActionSpec} so the ConformanceHost adapter can
82
- * dispatch this directive before subscribe lands. Keyed by
83
- * `entry.channel` — registering the same channel twice replaces
84
- * the prior binding, matching the action-spec map's behavior.
79
+ * Install the declared actionSpec on the named render. Same
80
+ * "create-if-missing" semantics as the other setters so directive
81
+ * ordering relative to subscribe doesn't matter. Called by the
82
+ * `create-session` ConformanceHost directive when the fixture
83
+ * authors an `actionSpec` — actions are part of the render's
84
+ * identity, so the declaration rides session creation rather than a
85
+ * separate registration directive.
85
86
  */
86
- registerStreamSpec(renderId, entry) {
87
- const render = this.create(renderId, 'conformance');
88
- render.streamSpecs.set(entry.channel, entry);
87
+ declareActionSpec(sessionId, actionSpec) {
88
+ const render = this.create(sessionId, DEPLOYMENT_DEFAULT_APP_ID);
89
+ render.actionSpec = actionSpec;
89
90
  }
90
91
  /**
91
92
  * Set the per-render protocol-version override. Used by the
92
93
  * `server-version-override` ConformanceHost directive — populates
93
- * {@link Render.versionOverride} so the WS subscribe handler
94
+ * {@link GguiSession.versionOverride} so the WS subscribe handler
94
95
  * advertises this value (and emits UPGRADE_REQUIRED keyed off it)
95
- * for THIS render only, leaving parallel renders on the instance-
96
+ * for THIS GguiSession only, leaving parallel GguiSessions on the instance-
96
97
  * level default.
97
98
  *
98
- * Same "create-if-missing" semantics as the other register* setters
99
- * so directive ordering relative to subscribe doesn't matter.
99
+ * Same "create-if-missing" semantics as the other setters so
100
+ * directive ordering relative to subscribe doesn't matter.
100
101
  */
101
- setVersionOverride(renderId, version) {
102
- const render = this.create(renderId, 'conformance');
102
+ setVersionOverride(sessionId, version) {
103
+ const render = this.create(sessionId, DEPLOYMENT_DEFAULT_APP_ID);
103
104
  render.versionOverride = version;
104
105
  }
106
+ /**
107
+ * Assign the next outbound stream sequence for the named render —
108
+ * advances {@link GguiSession.streamSeq} and returns the new value
109
+ * (first assignment = 1). Called by the `emit-envelope`
110
+ * ConformanceHost directive when stamping the `StreamEnvelope` it
111
+ * injects. Sequence assignment is emission-scoped, not
112
+ * delivery-scoped: a frame emitted with zero subscribers attached
113
+ * still consumes its sequence number, mirroring buffer-backed
114
+ * servers that assign `seq` at append time regardless of who is
115
+ * connected.
116
+ *
117
+ * Same "create-if-missing" semantics as the other setters so
118
+ * directive ordering relative to subscribe doesn't matter.
119
+ */
120
+ nextStreamSeq(sessionId) {
121
+ const render = this.create(sessionId, DEPLOYMENT_DEFAULT_APP_ID);
122
+ render.streamSeq += 1;
123
+ return render.streamSeq;
124
+ }
105
125
  /**
106
126
  * Fan out a frame to every subscriber on the named render. Used by
107
127
  * the `emit-envelope` ConformanceHost directive — kit fixtures use
108
128
  * it to inject WS-observable side-effects (envelopes the server
109
129
  * would not normally emit on its own) so the kit can assert
110
- * downstream consequences (sequencing, fan-out, observability).
130
+ * downstream consequences (sequencing, fan-out).
111
131
  *
112
- * Returns `true` if the render existed and at least one subscriber
113
- * received the frame; `false` if the render is unknown OR has no
132
+ * Returns `true` if the GguiSession existed and at least one subscriber
133
+ * received the frame; `false` if the GguiSession is unknown OR has no
114
134
  * subscribers attached. Caller may use the boolean to log a warning
115
135
  * when a fixture's directive-injection lands before any subscribe
116
136
  * — the directive then has no observable effect, which is usually
117
137
  * a fixture-authoring bug worth surfacing.
118
138
  *
119
139
  * Subscriber-level send failures (closed socket, etc.) are
120
- * swallowed per the same convention as the action router's
121
- * `broadcast()` — one bad subscriber must not block fan-out to the
140
+ * swallowed — one bad subscriber must not block fan-out to the
122
141
  * rest.
123
142
  */
124
- injectFrame(renderId, frame) {
125
- const render = this.renders.get(renderId);
143
+ injectFrame(sessionId, frame) {
144
+ const render = this.renders.get(sessionId);
126
145
  if (render === undefined)
127
146
  return false;
128
147
  if (render.subscribers.size === 0)
package/dist/server.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { RenderStore } from './render.js';
2
- import { ToolRegistry } from './tool-registry.js';
1
+ import { GguiSessionStore } from './render.js';
3
2
  export interface ReferenceServerOptions {
4
3
  /** Port to bind. `0` = ephemeral — use {@link ReferenceServer.port}
5
4
  * to read the resolved port after start. */
@@ -31,8 +30,7 @@ export interface ReferenceServerOptions {
31
30
  readonly versionOverride?: string;
32
31
  }
33
32
  export declare class ReferenceServer {
34
- readonly renders: RenderStore;
35
- readonly tools: ToolRegistry;
33
+ readonly renders: GguiSessionStore;
36
34
  private readonly options;
37
35
  private http;
38
36
  private wss;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC;iDAC6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,OAAO,cAAqB;IACrC,QAAQ,CAAC,KAAK,eAAsB;IAEpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,SAAS,CAAuB;gBAE5B,OAAO,EAAE,sBAAsB;IAS3C;;;;;;OAMG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,sDAAsD;IACtD,IAAI,IAAI,IAAI,MAAM,CAKjB;IAED,4DAA4D;IAC5D,IAAI,OAAO,IAAI,MAAM,CAEpB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,OAAO,CAAC,gBAAgB;YAoCV,aAAa;IAyC3B,OAAO,CAAC,eAAe;CA4ExB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA+BA,OAAO,EAEL,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,sBAAsB;IACrC;iDAC6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,OAAO,mBAA0B;IAE1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,SAAS,CAAuB;gBAE5B,OAAO,EAAE,sBAAsB;IAS3C;;;;;;OAMG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,sDAAsD;IACtD,IAAI,IAAI,IAAI,MAAM,CAKjB;IAED,4DAA4D;IAC5D,IAAI,OAAO,IAAI,MAAM,CAEpB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,OAAO,CAAC,gBAAgB;IAoCxB,OAAO,CAAC,aAAa;IAyDrB,OAAO,CAAC,eAAe;CA6GxB"}