@rynx-ai/server 0.1.0 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/browser-surface-control-queue.d.ts +42 -0
  2. package/dist/browser-surface-control-queue.js +187 -0
  3. package/dist/browser-surface-ws.d.ts +14 -0
  4. package/dist/browser-surface-ws.js +355 -0
  5. package/dist/control-api.d.ts +59 -4
  6. package/dist/control-api.js +2213 -132
  7. package/dist/control-web/assets/highlighted-body-OFNGDK62-e-w61YLy.js +1 -0
  8. package/dist/control-web/assets/index-BQ7XVBKO.css +32 -0
  9. package/dist/control-web/assets/index-ClpzuBJx.js +606 -0
  10. package/dist/control-web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  11. package/dist/control-web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  12. package/dist/control-web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  13. package/dist/control-web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  14. package/dist/control-web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  15. package/dist/control-web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  16. package/dist/control-web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  17. package/dist/control-web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  18. package/dist/control-web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  19. package/dist/control-web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  20. package/dist/control-web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  21. package/dist/control-web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  22. package/dist/control-web/assets/mermaid-GHXKKRXX-DZn7Hbr2.js +131 -0
  23. package/dist/control-web/assets/rynx-wordmark-LL1QRYHD.png +0 -0
  24. package/dist/control-web/favicon.png +0 -0
  25. package/dist/control-web/favicon.svg +38 -0
  26. package/dist/control-web/index.html +16 -0
  27. package/dist/control-web-dist.d.ts +1 -1
  28. package/dist/control-web-dist.js +19 -14
  29. package/dist/desktop-browser-host.d.ts +153 -0
  30. package/dist/desktop-browser-host.js +936 -0
  31. package/dist/direct-runtime-browser-surface-server.d.ts +74 -0
  32. package/dist/direct-runtime-browser-surface-server.js +821 -0
  33. package/dist/direct-runtime-server.d.ts +137 -0
  34. package/dist/direct-runtime-server.js +1931 -0
  35. package/dist/emulator-surface-ws.d.ts +6 -0
  36. package/dist/emulator-surface-ws.js +197 -0
  37. package/dist/machine-session-service.d.ts +182 -0
  38. package/dist/machine-session-service.js +559 -0
  39. package/dist/provider-cli-host.d.ts +10 -0
  40. package/dist/provider-cli-host.js +1 -0
  41. package/dist/remote-runtime-dispatcher.d.ts +94 -0
  42. package/dist/remote-runtime-dispatcher.js +380 -0
  43. package/dist/remote-runtime-session-projection.d.ts +19 -0
  44. package/dist/remote-runtime-session-projection.js +556 -0
  45. package/dist/remote-runtime.d.ts +22 -0
  46. package/dist/remote-runtime.js +32 -0
  47. package/dist/runtime-web-auth.d.ts +21 -0
  48. package/dist/runtime-web-auth.js +92 -0
  49. package/dist/server.d.ts +292 -10
  50. package/dist/server.js +314 -98
  51. package/dist/session-browser-service.d.ts +218 -0
  52. package/dist/session-browser-service.js +686 -0
  53. package/dist/session-browser-surface-coordinator.d.ts +23 -0
  54. package/dist/session-browser-surface-coordinator.js +563 -0
  55. package/dist/session-emulator-service.d.ts +167 -0
  56. package/dist/session-emulator-service.js +464 -0
  57. package/dist/session-runtime-index.d.ts +17 -0
  58. package/dist/session-runtime-index.js +86 -0
  59. package/dist/session-terminal-host.d.ts +46 -0
  60. package/dist/session-terminal-host.js +252 -0
  61. package/dist/terminal-ws.d.ts +9 -20
  62. package/dist/terminal-ws.js +420 -97
  63. package/package.json +9 -7
@@ -0,0 +1,23 @@
1
+ import { type RuntimeBrowserSurfaceOpenFrame } from "@rynx-ai/protocol/runtime-browser-surface";
2
+ import { type DirectRuntimeBrowserSurfaceAttachment, type DirectRuntimeBrowserSurfaceHost } from "./direct-runtime-browser-surface-server.js";
3
+ import { SessionBrowserService } from "./session-browser-service.js";
4
+ export interface SessionBrowserSurfaceCoordinatorOptions {
5
+ frameAcknowledgeTimeoutMs?: number;
6
+ maxPendingEvents?: number;
7
+ maxPendingEventBytes?: number;
8
+ }
9
+ /**
10
+ * Owns the one-stream-per-Page lease, frame acknowledgement, and human-driver
11
+ * rules above physical Browser capture. It never exposes a Host Page ID or CDP.
12
+ */
13
+ export declare class SessionBrowserSurfaceCoordinator implements DirectRuntimeBrowserSurfaceHost {
14
+ private readonly browsers;
15
+ private readonly attachments;
16
+ private readonly pageQueues;
17
+ private readonly frameAcknowledgeTimeoutMs;
18
+ private readonly maxPendingEvents;
19
+ private readonly maxPendingEventBytes;
20
+ constructor(browsers: SessionBrowserService, options?: SessionBrowserSurfaceCoordinatorOptions);
21
+ open(frame: RuntimeBrowserSurfaceOpenFrame, context: Parameters<DirectRuntimeBrowserSurfaceHost["open"]>[1]): Promise<DirectRuntimeBrowserSurfaceAttachment>;
22
+ private exclusivePage;
23
+ }
@@ -0,0 +1,563 @@
1
+ import { RUNTIME_BROWSER_SURFACE_MAX_CONTROL_FRAME_BYTES, RUNTIME_BROWSER_SURFACE_MAX_ENCODED_FRAME_BYTES, RUNTIME_BROWSER_SURFACE_FRAME_ACK_TIMEOUT_MS, RUNTIME_BROWSER_SURFACE_MAX_RELIABLE_INPUT_QUEUE, } from "@rynx-ai/protocol/runtime-browser-surface";
2
+ import { DirectRuntimeBrowserSurfaceHostError, } from "./direct-runtime-browser-surface-server.js";
3
+ import { SessionBrowserHostError, SessionBrowserServiceError, SessionBrowserSurfaceUnsupportedError, } from "./session-browser-service.js";
4
+ const DEFAULT_MAX_PENDING_EVENTS = 8;
5
+ const DEFAULT_MAX_PENDING_EVENT_BYTES = RUNTIME_BROWSER_SURFACE_MAX_ENCODED_FRAME_BYTES +
6
+ RUNTIME_BROWSER_SURFACE_MAX_CONTROL_FRAME_BYTES * DEFAULT_MAX_PENDING_EVENTS;
7
+ /**
8
+ * Owns the one-stream-per-Page lease, frame acknowledgement, and human-driver
9
+ * rules above physical Browser capture. It never exposes a Host Page ID or CDP.
10
+ */
11
+ export class SessionBrowserSurfaceCoordinator {
12
+ browsers;
13
+ attachments = new Map();
14
+ pageQueues = new Map();
15
+ frameAcknowledgeTimeoutMs;
16
+ maxPendingEvents;
17
+ maxPendingEventBytes;
18
+ constructor(browsers, options = {}) {
19
+ this.browsers = browsers;
20
+ this.frameAcknowledgeTimeoutMs = options.frameAcknowledgeTimeoutMs
21
+ ?? RUNTIME_BROWSER_SURFACE_FRAME_ACK_TIMEOUT_MS;
22
+ if (!Number.isSafeInteger(this.frameAcknowledgeTimeoutMs) || this.frameAcknowledgeTimeoutMs < 1) {
23
+ throw new Error("Browser Surface frame acknowledgement timeout must be positive");
24
+ }
25
+ this.maxPendingEvents = positiveLimit(options.maxPendingEvents, DEFAULT_MAX_PENDING_EVENTS, "Browser Surface pending event limit");
26
+ this.maxPendingEventBytes = positiveLimit(options.maxPendingEventBytes, DEFAULT_MAX_PENDING_EVENT_BYTES, "Browser Surface pending event byte limit");
27
+ }
28
+ async open(frame, context) {
29
+ if (frame.requestedRole === "control" &&
30
+ !context.peer.subject.effectiveAuthorities.includes("daemon.full")) {
31
+ throw new DirectRuntimeBrowserSurfaceHostError("unauthorized", "Runtime grant does not allow Browser control");
32
+ }
33
+ const pageKey = `${frame.sessionId}\u0000${frame.pageId}`;
34
+ return this.exclusivePage(pageKey, async () => {
35
+ context.signal.throwIfAborted();
36
+ try {
37
+ await this.browsers.validateSurfaceTarget({
38
+ sessionId: frame.sessionId,
39
+ browserGeneration: frame.browserGeneration,
40
+ pageId: frame.pageId,
41
+ });
42
+ }
43
+ catch (error) {
44
+ throw mapSurfaceOpenError(error);
45
+ }
46
+ context.signal.throwIfAborted();
47
+ const previous = this.attachments.get(pageKey);
48
+ if (previous)
49
+ await previous.replace();
50
+ let source;
51
+ try {
52
+ source = await this.browsers.openSurface({
53
+ sessionId: frame.sessionId,
54
+ browserGeneration: frame.browserGeneration,
55
+ pageId: frame.pageId,
56
+ format: frame.format,
57
+ quality: frame.quality,
58
+ maximumFrameRate: frame.maximumFrameRate,
59
+ signal: context.signal,
60
+ });
61
+ }
62
+ catch (error) {
63
+ throw mapSurfaceOpenError(error);
64
+ }
65
+ if (context.signal.aborted) {
66
+ await source.close().catch(() => undefined);
67
+ throw new DirectRuntimeBrowserSurfaceHostError("cancelled", "Browser Surface open was cancelled");
68
+ }
69
+ let attachment;
70
+ attachment = new CoordinatedSurfaceAttachment({
71
+ open: frame,
72
+ source,
73
+ signal: context.signal,
74
+ frameAcknowledgeTimeoutMs: this.frameAcknowledgeTimeoutMs,
75
+ maxPendingEvents: this.maxPendingEvents,
76
+ maxPendingEventBytes: this.maxPendingEventBytes,
77
+ onClose: () => {
78
+ if (this.attachments.get(pageKey) === attachment)
79
+ this.attachments.delete(pageKey);
80
+ },
81
+ });
82
+ this.attachments.set(pageKey, attachment);
83
+ attachment.start();
84
+ return attachment;
85
+ });
86
+ }
87
+ exclusivePage(pageKey, operation) {
88
+ const previous = this.pageQueues.get(pageKey) ?? Promise.resolve();
89
+ const result = previous.then(operation, operation);
90
+ const completion = result.then(() => undefined, () => undefined);
91
+ this.pageQueues.set(pageKey, completion);
92
+ void completion.finally(() => {
93
+ if (this.pageQueues.get(pageKey) === completion)
94
+ this.pageQueues.delete(pageKey);
95
+ });
96
+ return result;
97
+ }
98
+ }
99
+ class CoordinatedSurfaceAttachment {
100
+ attachment;
101
+ ready;
102
+ events;
103
+ eventQueue;
104
+ abortHandler;
105
+ frameSequence = 0;
106
+ lastInputSequence = -1;
107
+ pendingInputCount = 0;
108
+ inputTail = Promise.resolve();
109
+ inputEpoch = 0;
110
+ acceptingInput = false;
111
+ inputResetRequired = false;
112
+ pendingFrame;
113
+ driver;
114
+ physicalDriver;
115
+ driverIntent = 0;
116
+ driverTransitionTail = Promise.resolve();
117
+ closing = false;
118
+ closePromise;
119
+ constructor(attachment) {
120
+ this.attachment = attachment;
121
+ this.eventQueue = new SurfaceEventQueue({
122
+ maxPendingEvents: attachment.maxPendingEvents,
123
+ maxPendingBytes: attachment.maxPendingEventBytes,
124
+ });
125
+ const viewport = attachment.source.getViewport();
126
+ this.ready = {
127
+ type: "browser.surface.ready",
128
+ subscriptionId: attachment.open.subscriptionId,
129
+ browserGeneration: attachment.open.browserGeneration,
130
+ viewportRevision: viewport.revision,
131
+ sourceWidth: viewport.width,
132
+ sourceHeight: viewport.height,
133
+ deviceScaleFactor: viewport.deviceScaleFactor,
134
+ format: attachment.open.format,
135
+ grantedRole: attachment.open.requestedRole,
136
+ };
137
+ this.driver = attachment.source.setHumanDriver ? "desktop" : "none";
138
+ this.physicalDriver = this.driver;
139
+ this.events = this.eventQueue;
140
+ this.abortHandler = () => {
141
+ void this.finish("cancelled", "Browser Surface connection was cancelled", true);
142
+ };
143
+ attachment.signal.addEventListener("abort", this.abortHandler, { once: true });
144
+ }
145
+ start() {
146
+ try {
147
+ this.emitDriverChanged();
148
+ }
149
+ catch (error) {
150
+ const mapped = mapSurfaceHostError(error, "Browser Surface event queue is full");
151
+ void this.finish(mapped.reason, mapped.message, true);
152
+ return;
153
+ }
154
+ void this.pumpFrames();
155
+ }
156
+ async handle(frame) {
157
+ if (this.closing)
158
+ return;
159
+ if (frame.subscriptionId !== this.attachment.open.subscriptionId) {
160
+ throw new DirectRuntimeBrowserSurfaceHostError("protocol_error", "Browser Surface subscription does not match");
161
+ }
162
+ switch (frame.type) {
163
+ case "browser.surface.frame.ack":
164
+ this.acknowledgeFrame(frame.frameSequence);
165
+ return;
166
+ case "browser.surface.driver.take":
167
+ this.requireGeneration(frame.browserGeneration);
168
+ this.requireControlRole();
169
+ if (!this.matchesViewport(frame.viewportRevision))
170
+ return;
171
+ await this.takeDriver();
172
+ return;
173
+ case "browser.surface.driver.release":
174
+ this.requireGeneration(frame.browserGeneration);
175
+ await this.releaseDriver();
176
+ return;
177
+ case "browser.surface.input":
178
+ this.requireGeneration(frame.browserGeneration);
179
+ this.requireControlRole();
180
+ if (this.driver !== "stream" || !this.acceptingInput) {
181
+ throw new DirectRuntimeBrowserSurfaceHostError("unauthorized", "Browser Surface does not own the human driver");
182
+ }
183
+ if (!this.matchesViewport(frame.viewportRevision))
184
+ return;
185
+ if (frame.inputSequence <= this.lastInputSequence) {
186
+ throw new DirectRuntimeBrowserSurfaceHostError("protocol_error", "Browser Surface input sequence must increase");
187
+ }
188
+ if (this.pendingInputCount >= RUNTIME_BROWSER_SURFACE_MAX_RELIABLE_INPUT_QUEUE) {
189
+ throw new DirectRuntimeBrowserSurfaceHostError("capacity", "Browser Surface input queue is full");
190
+ }
191
+ this.lastInputSequence = frame.inputSequence;
192
+ this.pendingInputCount += 1;
193
+ const inputEpoch = this.inputEpoch;
194
+ try {
195
+ const operation = this.inputTail.then(async () => {
196
+ if (this.closing ||
197
+ !this.acceptingInput ||
198
+ this.driver !== "stream" ||
199
+ inputEpoch !== this.inputEpoch)
200
+ return;
201
+ await this.attachment.source.dispatchTrustedInput({
202
+ browserGeneration: frame.browserGeneration,
203
+ viewportRevision: frame.viewportRevision,
204
+ event: frame.event,
205
+ });
206
+ });
207
+ this.inputTail = operation.then(() => undefined, () => undefined);
208
+ await operation;
209
+ }
210
+ catch (error) {
211
+ throw mapSurfaceHostError(error, "Browser Surface input failed");
212
+ }
213
+ finally {
214
+ this.pendingInputCount -= 1;
215
+ }
216
+ }
217
+ }
218
+ close(_reason) {
219
+ return this.finish("cancelled", undefined, false);
220
+ }
221
+ replace() {
222
+ return this.finish("replaced", "Browser Surface was opened elsewhere", true);
223
+ }
224
+ async pumpFrames() {
225
+ try {
226
+ for await (const frame of this.attachment.source.frames) {
227
+ if (this.closing) {
228
+ await frame.acknowledge();
229
+ return;
230
+ }
231
+ if (frame.browserGeneration !== this.attachment.open.browserGeneration) {
232
+ await frame.acknowledge();
233
+ await this.finish("generation_changed", "Browser generation changed", true);
234
+ return;
235
+ }
236
+ if (frame.format !== this.attachment.open.format) {
237
+ await frame.acknowledge();
238
+ throw new Error("Browser Host changed the Surface image format");
239
+ }
240
+ const sequence = ++this.frameSequence;
241
+ const released = new Promise((resolve) => {
242
+ const timer = setTimeout(() => {
243
+ void this.finish("idle_timeout", "Browser Surface frame was not acknowledged", true);
244
+ }, this.attachment.frameAcknowledgeTimeoutMs);
245
+ timer.unref?.();
246
+ this.pendingFrame = { sequence, release: resolve, timer };
247
+ });
248
+ const image = {
249
+ subscriptionId: this.attachment.open.subscriptionId,
250
+ frameSequence: sequence,
251
+ viewportRevision: frame.viewportRevision,
252
+ timestampMilliseconds: frame.timestampMilliseconds,
253
+ width: frame.width,
254
+ height: frame.height,
255
+ format: frame.format,
256
+ encodedBytes: frame.encodedBytes,
257
+ };
258
+ if (!this.eventQueue.push(image)) {
259
+ const pending = this.pendingFrame;
260
+ this.pendingFrame = undefined;
261
+ if (pending) {
262
+ clearTimeout(pending.timer);
263
+ pending.release();
264
+ }
265
+ await frame.acknowledge();
266
+ await this.finish("capacity", "Browser Surface event queue is full", true);
267
+ return;
268
+ }
269
+ await released;
270
+ await frame.acknowledge();
271
+ if (this.closing)
272
+ return;
273
+ }
274
+ if (!this.closing) {
275
+ await this.finish("browser_closed", "Browser Surface source ended", true);
276
+ }
277
+ }
278
+ catch (error) {
279
+ if (!this.closing) {
280
+ const mapped = mapSurfaceHostError(error, "Browser Surface source failed");
281
+ await this.finish(mapped.reason, mapped.message, true);
282
+ }
283
+ }
284
+ }
285
+ acknowledgeFrame(sequence) {
286
+ const pending = this.pendingFrame;
287
+ if (!pending || pending.sequence !== sequence) {
288
+ throw new DirectRuntimeBrowserSurfaceHostError("protocol_error", "Browser Surface frame acknowledgement is stale or unknown");
289
+ }
290
+ clearTimeout(pending.timer);
291
+ this.pendingFrame = undefined;
292
+ pending.release();
293
+ }
294
+ requireGeneration(generation) {
295
+ if (generation !== this.attachment.open.browserGeneration) {
296
+ throw new DirectRuntimeBrowserSurfaceHostError("generation_changed", "Browser generation changed");
297
+ }
298
+ }
299
+ matchesViewport(revision) {
300
+ return revision === this.attachment.source.getViewport().revision;
301
+ }
302
+ requireControlRole() {
303
+ if (this.ready.grantedRole !== "control") {
304
+ throw new DirectRuntimeBrowserSurfaceHostError("unauthorized", "Browser Surface is view-only");
305
+ }
306
+ }
307
+ emitDriverChanged() {
308
+ const event = {
309
+ type: "browser.surface.driver.changed",
310
+ subscriptionId: this.attachment.open.subscriptionId,
311
+ browserGeneration: this.attachment.open.browserGeneration,
312
+ driver: this.driver,
313
+ ...(this.driver === "stream"
314
+ ? { driverSubscriptionId: this.attachment.open.subscriptionId }
315
+ : {}),
316
+ };
317
+ if (!this.eventQueue.push(event)) {
318
+ throw new DirectRuntimeBrowserSurfaceHostError("capacity", "Browser Surface event queue is full");
319
+ }
320
+ }
321
+ takeDriver() {
322
+ const intent = ++this.driverIntent;
323
+ const operation = this.enqueueDriverTransition(async () => {
324
+ if (this.closing || intent !== this.driverIntent)
325
+ return;
326
+ if (this.inputResetRequired) {
327
+ await this.inputTail;
328
+ await this.attachment.source.resetTrustedInput?.();
329
+ this.inputResetRequired = false;
330
+ }
331
+ if (this.closing || intent !== this.driverIntent)
332
+ return;
333
+ if (this.physicalDriver !== "stream") {
334
+ await this.attachment.source.setHumanDriver?.("stream");
335
+ this.physicalDriver = "stream";
336
+ }
337
+ if (this.closing || intent !== this.driverIntent)
338
+ return;
339
+ const changed = this.driver !== "stream";
340
+ this.inputEpoch += 1;
341
+ this.acceptingInput = true;
342
+ this.driver = "stream";
343
+ if (changed)
344
+ this.emitDriverChanged();
345
+ });
346
+ return operation.catch((error) => {
347
+ throw mapSurfaceHostError(error, "Browser Surface driver handoff failed");
348
+ });
349
+ }
350
+ releaseDriver() {
351
+ this.fenceInput();
352
+ this.inputResetRequired = true;
353
+ const intent = ++this.driverIntent;
354
+ const operation = this.enqueueDriverTransition(async () => {
355
+ await this.inputTail;
356
+ await this.attachment.source.resetTrustedInput?.();
357
+ this.inputResetRequired = false;
358
+ if (this.closing || intent !== this.driverIntent)
359
+ return;
360
+ const restingDriver = this.attachment.source.setHumanDriver ? "desktop" : "none";
361
+ if (this.physicalDriver !== restingDriver) {
362
+ await this.attachment.source.setHumanDriver?.("desktop");
363
+ this.physicalDriver = restingDriver;
364
+ }
365
+ if (this.closing || intent !== this.driverIntent)
366
+ return;
367
+ const changed = this.driver !== restingDriver;
368
+ this.driver = restingDriver;
369
+ if (changed)
370
+ this.emitDriverChanged();
371
+ });
372
+ return operation.catch((error) => {
373
+ throw mapSurfaceHostError(error, "Browser Surface driver release failed");
374
+ });
375
+ }
376
+ enqueueDriverTransition(operation) {
377
+ const result = this.driverTransitionTail.then(operation, operation);
378
+ this.driverTransitionTail = result.then(() => undefined, () => undefined);
379
+ return result;
380
+ }
381
+ fenceInput() {
382
+ this.acceptingInput = false;
383
+ this.inputEpoch += 1;
384
+ }
385
+ finish(reason, message, notify) {
386
+ if (this.closePromise)
387
+ return this.closePromise;
388
+ this.closing = true;
389
+ this.fenceInput();
390
+ this.inputResetRequired = true;
391
+ this.driverIntent += 1;
392
+ this.attachment.signal.removeEventListener("abort", this.abortHandler);
393
+ const pending = this.pendingFrame;
394
+ this.pendingFrame = undefined;
395
+ if (pending) {
396
+ clearTimeout(pending.timer);
397
+ pending.release();
398
+ }
399
+ this.eventQueue.close(notify
400
+ ? {
401
+ type: "browser.surface.closed",
402
+ subscriptionId: this.attachment.open.subscriptionId,
403
+ reason,
404
+ ...(message === undefined ? {} : { message: message.slice(0, 1_024) }),
405
+ }
406
+ : undefined);
407
+ const restingDriver = this.attachment.source.setHumanDriver ? "desktop" : "none";
408
+ const releaseDriver = this.enqueueDriverTransition(async () => {
409
+ await this.inputTail;
410
+ try {
411
+ await this.attachment.source.resetTrustedInput?.();
412
+ this.inputResetRequired = false;
413
+ }
414
+ catch {
415
+ // Closing remains fail-safe: capture must still be released if the
416
+ // renderer no longer accepts synthesized key/button releases.
417
+ }
418
+ if (this.physicalDriver !== restingDriver) {
419
+ try {
420
+ await this.attachment.source.setHumanDriver?.("desktop");
421
+ this.physicalDriver = restingDriver;
422
+ }
423
+ catch {
424
+ // Closing remains fail-safe: capture is released even if Host handoff fails.
425
+ }
426
+ }
427
+ this.driver = restingDriver;
428
+ });
429
+ this.closePromise = releaseDriver.then(() => this.attachment.source.close())
430
+ .catch(() => undefined)
431
+ .finally(this.attachment.onClose);
432
+ return this.closePromise;
433
+ }
434
+ }
435
+ class SurfaceEventQueue {
436
+ options;
437
+ items = [];
438
+ queuedBytes = 0;
439
+ waiter;
440
+ ended = false;
441
+ constructor(options) {
442
+ this.options = options;
443
+ }
444
+ push(event) {
445
+ if (this.ended)
446
+ return false;
447
+ if (this.waiter) {
448
+ const waiter = this.waiter;
449
+ this.waiter = undefined;
450
+ waiter({ done: false, value: event });
451
+ return true;
452
+ }
453
+ const bytes = surfaceEventBytes(event);
454
+ const tail = this.items.at(-1);
455
+ if (tail &&
456
+ isDriverChanged(tail.event) &&
457
+ isDriverChanged(event)) {
458
+ const nextBytes = this.queuedBytes - tail.bytes + bytes;
459
+ if (nextBytes > this.options.maxPendingBytes)
460
+ return false;
461
+ tail.event = event;
462
+ tail.bytes = bytes;
463
+ this.queuedBytes = nextBytes;
464
+ return true;
465
+ }
466
+ if (this.items.length >= this.options.maxPendingEvents ||
467
+ this.queuedBytes + bytes > this.options.maxPendingBytes)
468
+ return false;
469
+ this.items.push({ event, bytes });
470
+ this.queuedBytes += bytes;
471
+ return true;
472
+ }
473
+ close(finalEvent) {
474
+ if (this.ended)
475
+ return;
476
+ this.ended = true;
477
+ this.items.length = 0;
478
+ this.queuedBytes = 0;
479
+ if (finalEvent) {
480
+ if (this.waiter)
481
+ this.waiter({ done: false, value: finalEvent });
482
+ else {
483
+ const bytes = surfaceEventBytes(finalEvent);
484
+ this.items.push({ event: finalEvent, bytes });
485
+ this.queuedBytes = bytes;
486
+ }
487
+ }
488
+ else {
489
+ this.waiter?.({ done: true, value: undefined });
490
+ }
491
+ this.waiter = undefined;
492
+ }
493
+ [Symbol.asyncIterator]() {
494
+ return { next: () => this.next() };
495
+ }
496
+ next() {
497
+ const pending = this.items.shift();
498
+ if (pending) {
499
+ this.queuedBytes -= pending.bytes;
500
+ return Promise.resolve({ done: false, value: pending.event });
501
+ }
502
+ if (this.ended)
503
+ return Promise.resolve({ done: true, value: undefined });
504
+ if (this.waiter)
505
+ return Promise.reject(new Error("Browser Surface event read is already pending"));
506
+ return new Promise((resolve) => {
507
+ this.waiter = resolve;
508
+ });
509
+ }
510
+ }
511
+ function isDriverChanged(event) {
512
+ return "type" in event && event.type === "browser.surface.driver.changed";
513
+ }
514
+ function surfaceEventBytes(event) {
515
+ return "encodedBytes" in event
516
+ ? event.encodedBytes.byteLength + 128
517
+ : Buffer.byteLength(JSON.stringify(event), "utf8");
518
+ }
519
+ function positiveLimit(value, fallback, field) {
520
+ const resolved = value ?? fallback;
521
+ if (!Number.isSafeInteger(resolved) || resolved < 1) {
522
+ throw new Error(`${field} must be a positive safe integer`);
523
+ }
524
+ return resolved;
525
+ }
526
+ function mapSurfaceOpenError(error) {
527
+ if (error instanceof SessionBrowserSurfaceUnsupportedError) {
528
+ return new DirectRuntimeBrowserSurfaceHostError("unsupported", error.message, { cause: error });
529
+ }
530
+ if (error instanceof SessionBrowserServiceError) {
531
+ switch (error.code) {
532
+ case "invalid_request":
533
+ return new DirectRuntimeBrowserSurfaceHostError("protocol_error", error.message, { cause: error });
534
+ case "not_found":
535
+ return new DirectRuntimeBrowserSurfaceHostError("page_closed", error.message, { cause: error });
536
+ case "generation_mismatch":
537
+ return new DirectRuntimeBrowserSurfaceHostError("generation_changed", error.message, { cause: error });
538
+ case "capacity":
539
+ return new DirectRuntimeBrowserSurfaceHostError("capacity", error.message, { cause: error });
540
+ case "unavailable":
541
+ case "shutting_down":
542
+ return new DirectRuntimeBrowserSurfaceHostError("browser_closed", error.message, { cause: error });
543
+ case "host_failure":
544
+ case "outcome_unknown":
545
+ return new DirectRuntimeBrowserSurfaceHostError("internal_error", error.message, { cause: error });
546
+ }
547
+ }
548
+ return mapSurfaceHostError(error, "Browser Surface could not open");
549
+ }
550
+ function mapSurfaceHostError(error, fallbackMessage) {
551
+ if (error instanceof DirectRuntimeBrowserSurfaceHostError)
552
+ return error;
553
+ if (error instanceof SessionBrowserHostError) {
554
+ const reason = error.code === "page_not_found"
555
+ ? "page_closed"
556
+ : error.code === "capacity"
557
+ ? "capacity"
558
+ : "browser_closed";
559
+ return new DirectRuntimeBrowserSurfaceHostError(reason, error.message, { cause: error });
560
+ }
561
+ const message = error instanceof Error ? error.message : fallbackMessage;
562
+ return new DirectRuntimeBrowserSurfaceHostError("internal_error", message, { cause: error });
563
+ }