oui-spec 0.2.0 → 0.4.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 +94 -122
- package/dist/{chunk-CVUUSPF4.js → chunk-BMCL2K74.js} +2 -2
- package/dist/chunk-BMCL2K74.js.map +1 -0
- package/dist/{chunk-MTMEW6MM.cjs → chunk-DCVVTP3V.cjs} +72 -23
- package/dist/chunk-DCVVTP3V.cjs.map +1 -0
- package/dist/chunk-DGMKSJTY.cjs +573 -0
- package/dist/chunk-DGMKSJTY.cjs.map +1 -0
- package/dist/chunk-F3SKRAGU.cjs +98 -0
- package/dist/chunk-F3SKRAGU.cjs.map +1 -0
- package/dist/{chunk-YDMGV72W.js → chunk-F3WKIRAU.js} +72 -23
- package/dist/chunk-F3WKIRAU.js.map +1 -0
- package/dist/chunk-GXTNZ44I.js +569 -0
- package/dist/chunk-GXTNZ44I.js.map +1 -0
- package/dist/chunk-IIZ6HL2L.js +89 -0
- package/dist/chunk-IIZ6HL2L.js.map +1 -0
- package/dist/chunk-NYCJZQSP.js +92 -0
- package/dist/chunk-NYCJZQSP.js.map +1 -0
- package/dist/chunk-ROGKEMLL.cjs +91 -0
- package/dist/chunk-ROGKEMLL.cjs.map +1 -0
- package/dist/{chunk-3DCIL4FV.cjs → chunk-ZKM4XWAB.cjs} +2 -2
- package/dist/chunk-ZKM4XWAB.cjs.map +1 -0
- package/dist/core/index.cjs +10 -6
- package/dist/core/index.d.cts +15 -8
- package/dist/core/index.d.ts +15 -8
- package/dist/core/index.js +3 -3
- package/dist/index.cjs +28 -12
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -5
- package/dist/react/index.cjs +15 -3
- package/dist/react/index.d.cts +33 -50
- package/dist/react/index.d.ts +33 -50
- package/dist/react/index.js +1 -1
- package/dist/spec/index.cjs +2 -2
- package/dist/spec/index.d.cts +35 -7
- package/dist/spec/index.d.ts +35 -7
- package/dist/spec/index.js +1 -1
- package/dist/{define-surface-D6XPtoqu.d.cts → surface-runtime-BzIKgL_I.d.cts} +102 -2
- package/dist/{define-surface-DXBop-uB.d.ts → surface-runtime-DeByfbKg.d.ts} +102 -2
- package/dist/transport/index.cjs +4 -4
- package/dist/transport/index.d.cts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +2 -2
- package/dist/{websocket-CrI0yRNo.d.cts → websocket-YfWILdZ4.d.cts} +36 -18
- package/dist/{websocket-CiWvkaz_.d.ts → websocket-c5tIUWY5.d.ts} +36 -18
- package/package.json +5 -1
- package/spec/OUI-SPEC-v0.1.md +229 -155
- package/dist/chunk-2ON5IEXB.cjs +0 -167
- package/dist/chunk-2ON5IEXB.cjs.map +0 -1
- package/dist/chunk-3DCIL4FV.cjs.map +0 -1
- package/dist/chunk-5TQ6AVQH.cjs +0 -108
- package/dist/chunk-5TQ6AVQH.cjs.map +0 -1
- package/dist/chunk-CVUUSPF4.js.map +0 -1
- package/dist/chunk-HK7OXB2W.cjs +0 -157
- package/dist/chunk-HK7OXB2W.cjs.map +0 -1
- package/dist/chunk-ILUTAHUJ.js +0 -154
- package/dist/chunk-ILUTAHUJ.js.map +0 -1
- package/dist/chunk-MTMEW6MM.cjs.map +0 -1
- package/dist/chunk-O6EI77LD.js +0 -106
- package/dist/chunk-O6EI77LD.js.map +0 -1
- package/dist/chunk-UIGRLZOX.js +0 -164
- package/dist/chunk-UIGRLZOX.js.map +0 -1
- package/dist/chunk-YDMGV72W.js.map +0 -1
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { createWebSocketTransport } from './chunk-F3WKIRAU.js';
|
|
2
|
+
|
|
3
|
+
// src/core/brand.ts
|
|
4
|
+
function brand(value) {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/core/define-surface.ts
|
|
9
|
+
function defineSurface(definition) {
|
|
10
|
+
const actionIds = /* @__PURE__ */ new Set();
|
|
11
|
+
for (const action of definition.actions) {
|
|
12
|
+
if (actionIds.has(action.id)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`[OUI] Duplicate action ID "${action.id}" in surface "${definition.id}"`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
actionIds.add(action.id);
|
|
18
|
+
}
|
|
19
|
+
if (definition.observations) {
|
|
20
|
+
const obsIds = /* @__PURE__ */ new Set();
|
|
21
|
+
for (const obs of definition.observations) {
|
|
22
|
+
if (obsIds.has(obs.id)) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`[OUI] Duplicate observation ID "${obs.id}" in surface "${definition.id}"`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
obsIds.add(obs.id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return brand({
|
|
31
|
+
...definition,
|
|
32
|
+
/** Extract the manifest (schema only, no handlers) for transmission to the agent runtime */
|
|
33
|
+
toManifest() {
|
|
34
|
+
return {
|
|
35
|
+
id: definition.id,
|
|
36
|
+
name: definition.name,
|
|
37
|
+
description: definition.description,
|
|
38
|
+
version: definition.version,
|
|
39
|
+
activation: definition.activation,
|
|
40
|
+
actions: definition.actions.map(actionToManifest),
|
|
41
|
+
observations: definition.observations?.map(obsToManifest),
|
|
42
|
+
metadata: definition.metadata
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
/** Execute an action by ID with the given params and context */
|
|
46
|
+
async executeAction(actionId, params, context) {
|
|
47
|
+
const action = definition.actions.find((a) => a.id === actionId);
|
|
48
|
+
if (!action) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: {
|
|
52
|
+
code: "ACTION_NOT_FOUND",
|
|
53
|
+
message: `Action "${actionId}" not found on surface "${definition.id}"`
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return await action.handler(params, context);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: {
|
|
63
|
+
code: "ACTION_EXECUTION_ERROR",
|
|
64
|
+
message: err instanceof Error ? err.message : String(err)
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
/** Get all action IDs */
|
|
70
|
+
getActionIds() {
|
|
71
|
+
return definition.actions.map((a) => a.id);
|
|
72
|
+
},
|
|
73
|
+
/** Get the polling config for an action (if async) */
|
|
74
|
+
getPollingConfig(actionId) {
|
|
75
|
+
const action = definition.actions.find((a) => a.id === actionId);
|
|
76
|
+
return action?.polling;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function actionToManifest(action) {
|
|
81
|
+
return {
|
|
82
|
+
id: action.id,
|
|
83
|
+
description: action.description,
|
|
84
|
+
input: action.input,
|
|
85
|
+
output: action.output,
|
|
86
|
+
confirm: action.confirm,
|
|
87
|
+
async: action.async,
|
|
88
|
+
polling: action.polling,
|
|
89
|
+
usage: action.usage,
|
|
90
|
+
preconditions: action.preconditions,
|
|
91
|
+
estimatedDuration: action.estimatedDuration,
|
|
92
|
+
tags: action.tags
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function obsToManifest(obs) {
|
|
96
|
+
return {
|
|
97
|
+
id: obs.id,
|
|
98
|
+
description: obs.description,
|
|
99
|
+
schema: obs.schema
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/core/create-oui.ts
|
|
104
|
+
function createOUI(config) {
|
|
105
|
+
const {
|
|
106
|
+
socket,
|
|
107
|
+
surfaces,
|
|
108
|
+
transport: transportConfig
|
|
109
|
+
} = config;
|
|
110
|
+
if (!socket) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"[OUI] createOUI requires a socket. OUI owns the wire; the integrator owns the connection."
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (!Array.isArray(surfaces) || surfaces.length === 0) {
|
|
116
|
+
throw new Error("[OUI] createOUI requires at least one surface.");
|
|
117
|
+
}
|
|
118
|
+
const seen = /* @__PURE__ */ new Set();
|
|
119
|
+
for (const surface of surfaces) {
|
|
120
|
+
if (seen.has(surface.id)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`[OUI] Duplicate surface id "${surface.id}". Registration is keyed by id, so the second would replace the first.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
seen.add(surface.id);
|
|
126
|
+
}
|
|
127
|
+
const transport = brand(
|
|
128
|
+
createWebSocketTransport(socket, transportConfig)
|
|
129
|
+
);
|
|
130
|
+
const byId = new Map(surfaces.map((s) => [s.id, s]));
|
|
131
|
+
return {
|
|
132
|
+
transport,
|
|
133
|
+
manifests: surfaces.map((s) => s.toManifest()),
|
|
134
|
+
async start() {
|
|
135
|
+
await transport.connect();
|
|
136
|
+
for (const surface of surfaces) {
|
|
137
|
+
transport.registerSurface(surface.toManifest());
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
stop() {
|
|
141
|
+
for (const surface of surfaces) {
|
|
142
|
+
transport.deregisterSurface(surface.id);
|
|
143
|
+
}
|
|
144
|
+
transport.dispose();
|
|
145
|
+
},
|
|
146
|
+
getSurface(id) {
|
|
147
|
+
return byId.get(id);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/core/surface-runtime.ts
|
|
153
|
+
var DEFAULT_QUIET_MS = 250;
|
|
154
|
+
var DEFAULT_SETTLE_TIMEOUT_MS = 5e3;
|
|
155
|
+
var DEFAULT_DEDUPE_WINDOW = 500;
|
|
156
|
+
var SETTLE_TICK_MS = 50;
|
|
157
|
+
function createSurfaceRuntime(options = {}) {
|
|
158
|
+
const announce = options.announce ?? true;
|
|
159
|
+
const quietMs = options.settle?.quietMs ?? DEFAULT_QUIET_MS;
|
|
160
|
+
const settleTimeoutMs = options.settle?.timeoutMs ?? DEFAULT_SETTLE_TIMEOUT_MS;
|
|
161
|
+
const dedupeWindow = options.dedupeWindow ?? DEFAULT_DEDUPE_WINDOW;
|
|
162
|
+
let nextKey = 1;
|
|
163
|
+
const entries = [];
|
|
164
|
+
const observations = /* @__PURE__ */ new Map();
|
|
165
|
+
const serializedObservations = /* @__PURE__ */ new Map();
|
|
166
|
+
const holds = /* @__PURE__ */ new Set();
|
|
167
|
+
let nextHold = 1;
|
|
168
|
+
const results = /* @__PURE__ */ new Map();
|
|
169
|
+
const pollers = /* @__PURE__ */ new Map();
|
|
170
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
171
|
+
const acks = /* @__PURE__ */ new Map();
|
|
172
|
+
function ackFor(requestId) {
|
|
173
|
+
let ack = acks.get(requestId);
|
|
174
|
+
if (!ack) {
|
|
175
|
+
let resolve;
|
|
176
|
+
const promise = new Promise((r) => resolve = r);
|
|
177
|
+
ack = { promise, resolve };
|
|
178
|
+
acks.set(requestId, ack);
|
|
179
|
+
}
|
|
180
|
+
return ack;
|
|
181
|
+
}
|
|
182
|
+
function acknowledged(requestId) {
|
|
183
|
+
return ackFor(requestId).promise;
|
|
184
|
+
}
|
|
185
|
+
let lastChangeAt = Date.now();
|
|
186
|
+
let socket = null;
|
|
187
|
+
let transport = null;
|
|
188
|
+
let detachTransport = [];
|
|
189
|
+
function changed(settles = true) {
|
|
190
|
+
if (settles) lastChangeAt = Date.now();
|
|
191
|
+
for (const l of [...listeners]) l();
|
|
192
|
+
}
|
|
193
|
+
function activeFor(surfaceId) {
|
|
194
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
195
|
+
if (entries[i].surface.id === surfaceId) return entries[i];
|
|
196
|
+
}
|
|
197
|
+
return void 0;
|
|
198
|
+
}
|
|
199
|
+
function activeEntries() {
|
|
200
|
+
const seen = /* @__PURE__ */ new Set();
|
|
201
|
+
const out = [];
|
|
202
|
+
for (const e of entries) {
|
|
203
|
+
if (seen.has(e.surface.id)) continue;
|
|
204
|
+
seen.add(e.surface.id);
|
|
205
|
+
out.push(activeFor(e.surface.id));
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
function announceSurface(entry) {
|
|
210
|
+
if (!announce || !transport) return;
|
|
211
|
+
transport.registerSurface(entry.surface.toManifest());
|
|
212
|
+
const obs = observations.get(entry.surface.id);
|
|
213
|
+
if (obs) {
|
|
214
|
+
for (const [observationId, value] of obs) {
|
|
215
|
+
transport.pushObservation({
|
|
216
|
+
surfaceId: entry.surface.id,
|
|
217
|
+
observationId,
|
|
218
|
+
value,
|
|
219
|
+
timestamp: Date.now()
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function announceAll() {
|
|
225
|
+
for (const e of activeEntries()) announceSurface(e);
|
|
226
|
+
}
|
|
227
|
+
function setObservation(surfaceId, observationId, value, settles = true) {
|
|
228
|
+
const key = `${surfaceId}\0${observationId}`;
|
|
229
|
+
let serialized;
|
|
230
|
+
try {
|
|
231
|
+
serialized = JSON.stringify(value) ?? "undefined";
|
|
232
|
+
} catch {
|
|
233
|
+
serialized = String(value);
|
|
234
|
+
}
|
|
235
|
+
if (serializedObservations.get(key) === serialized) return;
|
|
236
|
+
serializedObservations.set(key, serialized);
|
|
237
|
+
let obs = observations.get(surfaceId);
|
|
238
|
+
if (!obs) {
|
|
239
|
+
obs = /* @__PURE__ */ new Map();
|
|
240
|
+
observations.set(surfaceId, obs);
|
|
241
|
+
}
|
|
242
|
+
obs.set(observationId, toJsonSafe(value));
|
|
243
|
+
if (announce && transport) {
|
|
244
|
+
transport.pushObservation({
|
|
245
|
+
surfaceId,
|
|
246
|
+
observationId,
|
|
247
|
+
value,
|
|
248
|
+
timestamp: Date.now()
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
changed(settles);
|
|
252
|
+
}
|
|
253
|
+
function forgetSurface(surfaceId) {
|
|
254
|
+
observations.delete(surfaceId);
|
|
255
|
+
for (const key of [...serializedObservations.keys()]) {
|
|
256
|
+
if (key.startsWith(`${surfaceId}\0`))
|
|
257
|
+
serializedObservations.delete(key);
|
|
258
|
+
}
|
|
259
|
+
for (const [key, timer] of pollers) {
|
|
260
|
+
if (key.startsWith(`${surfaceId}\0`)) {
|
|
261
|
+
clearInterval(timer);
|
|
262
|
+
pollers.delete(key);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function startPolling(requestId, surfaceId, actionId, polling, dispatchResult) {
|
|
267
|
+
const finish = async (success, data, error) => {
|
|
268
|
+
await acknowledged(requestId);
|
|
269
|
+
const snap = snapshot();
|
|
270
|
+
transport?.sendResult({
|
|
271
|
+
requestId,
|
|
272
|
+
success,
|
|
273
|
+
...data !== void 0 ? { data: toJsonSafe(data) } : {},
|
|
274
|
+
...error ? { error } : {},
|
|
275
|
+
interim: false,
|
|
276
|
+
timestamp: Date.now(),
|
|
277
|
+
surfaces: snap.surfaces,
|
|
278
|
+
observations: snap.observations
|
|
279
|
+
});
|
|
280
|
+
acks.delete(requestId);
|
|
281
|
+
};
|
|
282
|
+
const observationId = `${surfaceId}:${actionId}:status`;
|
|
283
|
+
const pollerKey = `${surfaceId}\0${actionId}`;
|
|
284
|
+
if (polling.subscribe) {
|
|
285
|
+
setObservation(
|
|
286
|
+
surfaceId,
|
|
287
|
+
observationId,
|
|
288
|
+
{ status: "dispatched", dispatchResult, interim: true },
|
|
289
|
+
false
|
|
290
|
+
);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!polling.resolve) {
|
|
294
|
+
setObservation(
|
|
295
|
+
surfaceId,
|
|
296
|
+
observationId,
|
|
297
|
+
{ status: "dispatched", dispatchResult, interim: false },
|
|
298
|
+
false
|
|
299
|
+
);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const existing = pollers.get(pollerKey);
|
|
303
|
+
if (existing) clearInterval(existing);
|
|
304
|
+
setObservation(
|
|
305
|
+
surfaceId,
|
|
306
|
+
observationId,
|
|
307
|
+
{ status: "polling", dispatchResult, interim: true },
|
|
308
|
+
false
|
|
309
|
+
);
|
|
310
|
+
const maxAttempts = polling.maxAttempts ?? Infinity;
|
|
311
|
+
const maxDuration = polling.maxDurationMs ?? Infinity;
|
|
312
|
+
const startedAt = Date.now();
|
|
313
|
+
let attempts = 0;
|
|
314
|
+
const timer = setInterval(async () => {
|
|
315
|
+
attempts++;
|
|
316
|
+
if (attempts > maxAttempts || Date.now() - startedAt > maxDuration) {
|
|
317
|
+
clearInterval(timer);
|
|
318
|
+
pollers.delete(pollerKey);
|
|
319
|
+
setObservation(
|
|
320
|
+
surfaceId,
|
|
321
|
+
observationId,
|
|
322
|
+
{ status: "timeout", dispatchResult, interim: false },
|
|
323
|
+
false
|
|
324
|
+
);
|
|
325
|
+
void finish(false, void 0, {
|
|
326
|
+
code: "TIMEOUT",
|
|
327
|
+
message: `"${actionId}" did not finish within its polling limits.`
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const entry = activeFor(surfaceId);
|
|
332
|
+
const action = entry?.surface.actions.find((a) => a.id === actionId);
|
|
333
|
+
if (!entry || !action?.polling?.resolve) {
|
|
334
|
+
clearInterval(timer);
|
|
335
|
+
pollers.delete(pollerKey);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const r = await action.polling.resolve(
|
|
340
|
+
dispatchResult,
|
|
341
|
+
entry.getContext()
|
|
342
|
+
);
|
|
343
|
+
setObservation(
|
|
344
|
+
surfaceId,
|
|
345
|
+
observationId,
|
|
346
|
+
{
|
|
347
|
+
...r.data,
|
|
348
|
+
interim: !r.done
|
|
349
|
+
},
|
|
350
|
+
false
|
|
351
|
+
);
|
|
352
|
+
if (r.done) {
|
|
353
|
+
clearInterval(timer);
|
|
354
|
+
pollers.delete(pollerKey);
|
|
355
|
+
void finish(true, r.data);
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
setObservation(
|
|
359
|
+
surfaceId,
|
|
360
|
+
observationId,
|
|
361
|
+
{ status: "poll_error", error: String(err), interim: true },
|
|
362
|
+
false
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}, polling.intervalMs);
|
|
366
|
+
pollers.set(pollerKey, timer);
|
|
367
|
+
}
|
|
368
|
+
async function waitUntilSettled(since) {
|
|
369
|
+
const deadline = since + settleTimeoutMs;
|
|
370
|
+
for (; ; ) {
|
|
371
|
+
const now = Date.now();
|
|
372
|
+
const quietFor = now - Math.max(since, lastChangeAt);
|
|
373
|
+
if (holds.size === 0 && quietFor >= quietMs) return true;
|
|
374
|
+
if (now >= deadline) return false;
|
|
375
|
+
await sleep(Math.min(SETTLE_TICK_MS, deadline - now));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function run(request) {
|
|
379
|
+
const startedAt = Date.now();
|
|
380
|
+
const entry = activeFor(request.surfaceId);
|
|
381
|
+
let result;
|
|
382
|
+
let interim = false;
|
|
383
|
+
if (!entry) {
|
|
384
|
+
const mounted = activeEntries().map((e) => e.surface.id);
|
|
385
|
+
result = {
|
|
386
|
+
success: false,
|
|
387
|
+
error: {
|
|
388
|
+
code: "SURFACE_NOT_MOUNTED",
|
|
389
|
+
message: `Surface "${request.surfaceId}" is not on screen. Mounted surfaces: ${mounted.length ? mounted.join(", ") : "none"}.`
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
} else {
|
|
393
|
+
result = await entry.surface.executeAction(
|
|
394
|
+
request.actionId,
|
|
395
|
+
request.params ?? {},
|
|
396
|
+
entry.getContext()
|
|
397
|
+
);
|
|
398
|
+
if (result.success) {
|
|
399
|
+
const action = entry.surface.actions.find(
|
|
400
|
+
(a) => a.id === request.actionId
|
|
401
|
+
);
|
|
402
|
+
if (action?.polling) {
|
|
403
|
+
interim = true;
|
|
404
|
+
startPolling(
|
|
405
|
+
request.requestId,
|
|
406
|
+
entry.surface.id,
|
|
407
|
+
action.id,
|
|
408
|
+
action.polling,
|
|
409
|
+
result.data ?? result.dispatchMeta
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const settled = await waitUntilSettled(startedAt);
|
|
415
|
+
const snap = snapshot();
|
|
416
|
+
return {
|
|
417
|
+
requestId: request.requestId,
|
|
418
|
+
success: result.success,
|
|
419
|
+
...result.data !== void 0 ? { data: toJsonSafe(result.data) } : {},
|
|
420
|
+
...result.error ? { error: toJsonSafe(result.error) } : {},
|
|
421
|
+
...interim ? { interim: true } : {},
|
|
422
|
+
durationMs: Date.now() - startedAt,
|
|
423
|
+
timestamp: Date.now(),
|
|
424
|
+
surfaces: snap.surfaces,
|
|
425
|
+
observations: snap.observations,
|
|
426
|
+
settled
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function execute(request) {
|
|
430
|
+
const seen = results.get(request.requestId);
|
|
431
|
+
if (seen) return seen;
|
|
432
|
+
const pending = run(request);
|
|
433
|
+
results.set(request.requestId, pending);
|
|
434
|
+
while (results.size > dedupeWindow) {
|
|
435
|
+
const oldest = results.keys().next().value;
|
|
436
|
+
results.delete(oldest);
|
|
437
|
+
}
|
|
438
|
+
return pending;
|
|
439
|
+
}
|
|
440
|
+
function snapshot() {
|
|
441
|
+
const surfaces = [];
|
|
442
|
+
const obs = {};
|
|
443
|
+
for (const e of activeEntries()) {
|
|
444
|
+
surfaces.push(e.surface.toManifest());
|
|
445
|
+
const values = observations.get(e.surface.id);
|
|
446
|
+
if (values && values.size > 0)
|
|
447
|
+
obs[e.surface.id] = Object.fromEntries(values);
|
|
448
|
+
}
|
|
449
|
+
return { surfaces, observations: obs };
|
|
450
|
+
}
|
|
451
|
+
function attach(next) {
|
|
452
|
+
if (next === socket) return;
|
|
453
|
+
for (const off of detachTransport) off();
|
|
454
|
+
detachTransport = [];
|
|
455
|
+
transport?.dispose();
|
|
456
|
+
transport = null;
|
|
457
|
+
socket = next;
|
|
458
|
+
if (!next) return;
|
|
459
|
+
const t = createWebSocketTransport(next, options.transport);
|
|
460
|
+
transport = t;
|
|
461
|
+
detachTransport.push(
|
|
462
|
+
t.onAction((request) => {
|
|
463
|
+
if (options.accept && !options.accept(request)) {
|
|
464
|
+
console.warn(
|
|
465
|
+
`[OUI] Refused a request the client does not accept: ${request.surfaceId}.${request.actionId} (${request.requestId})`
|
|
466
|
+
);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
void execute(request).then((result) => {
|
|
470
|
+
transport?.sendResult(result);
|
|
471
|
+
const ack = ackFor(request.requestId);
|
|
472
|
+
ack.resolve();
|
|
473
|
+
if (!result.interim) acks.delete(request.requestId);
|
|
474
|
+
});
|
|
475
|
+
})
|
|
476
|
+
);
|
|
477
|
+
if (announce) {
|
|
478
|
+
detachTransport.push(
|
|
479
|
+
t.onConnectionChange((connected) => {
|
|
480
|
+
if (connected) announceAll();
|
|
481
|
+
})
|
|
482
|
+
);
|
|
483
|
+
if (t.connected) announceAll();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (options.socket) attach(options.socket);
|
|
487
|
+
function mount(surface, getContext) {
|
|
488
|
+
const entry = {
|
|
489
|
+
key: nextKey++,
|
|
490
|
+
surface,
|
|
491
|
+
getContext
|
|
492
|
+
};
|
|
493
|
+
entries.push(entry);
|
|
494
|
+
announceSurface(entry);
|
|
495
|
+
changed();
|
|
496
|
+
let mounted = true;
|
|
497
|
+
return {
|
|
498
|
+
surfaceId: surface.id,
|
|
499
|
+
pushObservation(observationId, value) {
|
|
500
|
+
if (!mounted || activeFor(surface.id) !== entry) return;
|
|
501
|
+
setObservation(surface.id, observationId, value);
|
|
502
|
+
},
|
|
503
|
+
unmount() {
|
|
504
|
+
if (!mounted) return;
|
|
505
|
+
mounted = false;
|
|
506
|
+
const i = entries.indexOf(entry);
|
|
507
|
+
if (i >= 0) entries.splice(i, 1);
|
|
508
|
+
const replacement = activeFor(surface.id);
|
|
509
|
+
if (replacement) {
|
|
510
|
+
announceSurface(replacement);
|
|
511
|
+
} else {
|
|
512
|
+
forgetSurface(surface.id);
|
|
513
|
+
if (announce && transport) transport.deregisterSurface(surface.id);
|
|
514
|
+
}
|
|
515
|
+
changed();
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
mount,
|
|
521
|
+
attach,
|
|
522
|
+
snapshot,
|
|
523
|
+
execute,
|
|
524
|
+
hold() {
|
|
525
|
+
const id = nextHold++;
|
|
526
|
+
holds.add(id);
|
|
527
|
+
changed();
|
|
528
|
+
let released = false;
|
|
529
|
+
return () => {
|
|
530
|
+
if (released) return;
|
|
531
|
+
released = true;
|
|
532
|
+
holds.delete(id);
|
|
533
|
+
changed();
|
|
534
|
+
};
|
|
535
|
+
},
|
|
536
|
+
subscribe(listener) {
|
|
537
|
+
listeners.add(listener);
|
|
538
|
+
return () => {
|
|
539
|
+
listeners.delete(listener);
|
|
540
|
+
};
|
|
541
|
+
},
|
|
542
|
+
dispose() {
|
|
543
|
+
for (const timer of pollers.values()) clearInterval(timer);
|
|
544
|
+
pollers.clear();
|
|
545
|
+
attach(null);
|
|
546
|
+
entries.length = 0;
|
|
547
|
+
observations.clear();
|
|
548
|
+
serializedObservations.clear();
|
|
549
|
+
holds.clear();
|
|
550
|
+
listeners.clear();
|
|
551
|
+
acks.clear();
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function sleep(ms) {
|
|
556
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
557
|
+
}
|
|
558
|
+
function toJsonSafe(value) {
|
|
559
|
+
if (value === void 0) return void 0;
|
|
560
|
+
try {
|
|
561
|
+
return JSON.parse(JSON.stringify(value));
|
|
562
|
+
} catch {
|
|
563
|
+
return String(value);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export { createOUI, createSurfaceRuntime, defineSurface };
|
|
568
|
+
//# sourceMappingURL=chunk-GXTNZ44I.js.map
|
|
569
|
+
//# sourceMappingURL=chunk-GXTNZ44I.js.map
|