@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/README.md +22 -10
- package/dist/action-router.d.ts +28 -31
- package/dist/action-router.d.ts.map +1 -1
- package/dist/action-router.js +77 -278
- package/dist/conformance-host.d.ts +2 -32
- package/dist/conformance-host.d.ts.map +1 -1
- package/dist/conformance-host.js +190 -138
- package/dist/host-context.d.ts +70 -0
- package/dist/host-context.d.ts.map +1 -0
- package/dist/host-context.js +139 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/render.d.ts +145 -81
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +87 -68
- package/dist/server.d.ts +2 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +105 -51
- package/package.json +5 -5
- package/dist/tool-registry.d.ts +0 -63
- package/dist/tool-registry.d.ts.map +0 -1
- package/dist/tool-registry.js +0 -99
package/dist/render.d.ts
CHANGED
|
@@ -1,62 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* In-memory
|
|
2
|
+
* In-memory GguiSession store for the reference server.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
15
|
-
*
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
|
27
|
-
readonly name: string;
|
|
28
|
-
readonly tool: string;
|
|
29
|
-
}
|
|
42
|
+
export declare const DEPLOYMENT_DEFAULT_APP_ID = "conformance";
|
|
30
43
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
|
49
|
-
readonly
|
|
50
|
-
|
|
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
|
|
53
|
-
readonly
|
|
59
|
+
export interface GguiSession {
|
|
60
|
+
readonly sessionId: string;
|
|
54
61
|
readonly appId: string;
|
|
55
|
-
|
|
56
|
-
|
|
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-
|
|
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-
|
|
108
|
+
* Why per-GguiSession, not per-instance: parallel kit fixtures share
|
|
69
109
|
* one `ReferenceServer`. Mutating the instance-level override would
|
|
70
|
-
* leak across
|
|
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
|
|
77
|
-
*
|
|
78
|
-
*
|
|
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
|
-
*
|
|
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
|
|
139
|
+
export declare class GguiSessionStore {
|
|
90
140
|
private readonly renders;
|
|
91
141
|
private lastCreated;
|
|
92
|
-
create(
|
|
142
|
+
create(sessionId: string, appId: string): GguiSession;
|
|
93
143
|
/**
|
|
94
|
-
* The
|
|
95
|
-
* ConformanceHost's
|
|
96
|
-
*
|
|
97
|
-
* adapter
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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
|
-
|
|
102
|
-
get(
|
|
103
|
-
close(
|
|
104
|
-
addSubscriber(
|
|
105
|
-
removeSubscriber(
|
|
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
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* `
|
|
113
|
-
* the
|
|
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
|
-
|
|
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
|
|
169
|
+
* {@link GguiSession.versionOverride} so the WS subscribe handler
|
|
120
170
|
* advertises this value (and emits UPGRADE_REQUIRED keyed off it)
|
|
121
|
-
* for THIS
|
|
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
|
|
125
|
-
*
|
|
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
|
-
|
|
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
|
|
198
|
+
* downstream consequences (sequencing, fan-out).
|
|
134
199
|
*
|
|
135
|
-
* Returns `true` if the
|
|
136
|
-
* received the frame; `false` if the
|
|
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
|
|
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(
|
|
211
|
+
injectFrame(sessionId: string, frame: unknown): boolean;
|
|
148
212
|
}
|
|
149
213
|
//# sourceMappingURL=render.d.ts.map
|
package/dist/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
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
|
|
30
|
+
export class GguiSessionStore {
|
|
27
31
|
renders = new Map();
|
|
28
32
|
lastCreated;
|
|
29
|
-
create(
|
|
30
|
-
const existing = this.renders.get(
|
|
33
|
+
create(sessionId, appId) {
|
|
34
|
+
const existing = this.renders.get(sessionId);
|
|
31
35
|
if (existing !== undefined) {
|
|
32
|
-
this.lastCreated =
|
|
36
|
+
this.lastCreated = sessionId;
|
|
33
37
|
return existing;
|
|
34
38
|
}
|
|
35
39
|
const render = {
|
|
36
|
-
|
|
40
|
+
sessionId,
|
|
37
41
|
appId,
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
events: [],
|
|
43
|
+
streamSeq: 0,
|
|
40
44
|
subscribers: new Set(),
|
|
41
45
|
};
|
|
42
|
-
this.renders.set(
|
|
43
|
-
this.lastCreated =
|
|
46
|
+
this.renders.set(sessionId, render);
|
|
47
|
+
this.lastCreated = sessionId;
|
|
44
48
|
return render;
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
|
-
* The
|
|
48
|
-
* ConformanceHost's
|
|
49
|
-
*
|
|
50
|
-
* adapter
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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
|
-
|
|
58
|
+
lastCreatedSessionId() {
|
|
55
59
|
return this.lastCreated;
|
|
56
60
|
}
|
|
57
|
-
get(
|
|
58
|
-
return this.renders.get(
|
|
61
|
+
get(sessionId) {
|
|
62
|
+
return this.renders.get(sessionId);
|
|
59
63
|
}
|
|
60
|
-
close(
|
|
61
|
-
return this.renders.delete(
|
|
64
|
+
close(sessionId) {
|
|
65
|
+
return this.renders.delete(sessionId);
|
|
62
66
|
}
|
|
63
|
-
addSubscriber(
|
|
64
|
-
const render = this.create(
|
|
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(
|
|
69
|
-
const render = this.renders.get(
|
|
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
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* `
|
|
84
|
-
* the
|
|
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
|
-
|
|
87
|
-
const render = this.create(
|
|
88
|
-
render.
|
|
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
|
|
94
|
+
* {@link GguiSession.versionOverride} so the WS subscribe handler
|
|
94
95
|
* advertises this value (and emits UPGRADE_REQUIRED keyed off it)
|
|
95
|
-
* for THIS
|
|
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
|
|
99
|
-
*
|
|
99
|
+
* Same "create-if-missing" semantics as the other setters so
|
|
100
|
+
* directive ordering relative to subscribe doesn't matter.
|
|
100
101
|
*/
|
|
101
|
-
setVersionOverride(
|
|
102
|
-
const render = this.create(
|
|
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
|
|
130
|
+
* downstream consequences (sequencing, fan-out).
|
|
111
131
|
*
|
|
112
|
-
* Returns `true` if the
|
|
113
|
-
* received the frame; `false` if the
|
|
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
|
|
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(
|
|
125
|
-
const render = this.renders.get(
|
|
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 {
|
|
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:
|
|
35
|
-
readonly tools: ToolRegistry;
|
|
33
|
+
readonly renders: GguiSessionStore;
|
|
36
34
|
private readonly options;
|
|
37
35
|
private http;
|
|
38
36
|
private wss;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
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"}
|