surf-cli 2.7.2 → 2.9.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.
Files changed (45) hide show
  1. package/README.md +208 -13
  2. package/dist/content/index.js +116 -0
  3. package/dist/content/index.js.map +1 -0
  4. package/dist/manifest.json +2 -11
  5. package/dist/options/options.js +3 -3
  6. package/dist/options/options.js.map +1 -1
  7. package/dist/service-worker/index.js +261 -61
  8. package/dist/service-worker/index.js.map +1 -1
  9. package/native/abort.cjs +65 -0
  10. package/native/ai-queue.cjs +64 -0
  11. package/native/aistudio-build.cjs +21 -13
  12. package/native/aistudio-client.cjs +40 -20
  13. package/native/browser-lock.cjs +169 -0
  14. package/native/chatgpt-client.cjs +63 -30
  15. package/native/cli.cjs +947 -460
  16. package/native/client-transport.cjs +168 -0
  17. package/native/config.cjs +2 -2
  18. package/native/do-executor.cjs +25 -51
  19. package/native/do-parser.cjs +12 -0
  20. package/native/doctor.cjs +633 -0
  21. package/native/endpoint.cjs +174 -0
  22. package/native/file-transfer.cjs +734 -0
  23. package/native/gemini-client.cjs +244 -88
  24. package/native/grok-client.cjs +321 -212
  25. package/native/host-helpers.cjs +88 -16
  26. package/native/host-sessions.cjs +283 -0
  27. package/native/host.cjs +811 -616
  28. package/native/listener.cjs +20 -0
  29. package/native/mcp-server.cjs +60 -62
  30. package/native/network-export.cjs +113 -0
  31. package/native/perplexity-client.cjs +46 -17
  32. package/native/remote-auth.cjs +279 -0
  33. package/native/remote-transport.cjs +337 -0
  34. package/native/request-pending.cjs +148 -0
  35. package/native/socket-path.cjs +46 -0
  36. package/package.json +11 -9
  37. package/scripts/install-native-host.cjs +184 -51
  38. package/scripts/uninstall-native-host.cjs +93 -15
  39. package/skills/README.md +11 -5
  40. package/skills/deep-x-research/SKILL.md +106 -0
  41. package/skills/surf/SKILL.md +77 -22
  42. package/dist/content/accessibility-tree.js +0 -11
  43. package/dist/content/accessibility-tree.js.map +0 -1
  44. package/dist/content/visual-indicator.js +0 -111
  45. package/dist/content/visual-indicator.js.map +0 -1
@@ -2,6 +2,14 @@ const fs = require("fs");
2
2
  const networkFormatters = require("./formatters/network.cjs");
3
3
  const networkStore = require("./network-store.cjs");
4
4
 
5
+ function buildProviderUploadMessage(provider, tabId, filePaths, id) {
6
+ const normalizedProvider = String(provider || "").toLowerCase();
7
+ if (!["chatgpt", "gemini"].includes(normalizedProvider)) {
8
+ throw new Error(`Unsupported upload provider: ${provider}`);
9
+ }
10
+ return { type: "AI_UPLOAD_FILE_TO_TAB", provider: normalizedProvider, tabId, filePaths, id };
11
+ }
12
+
5
13
  function normalizeModelString(model) {
6
14
  return String(model || "").trim().toLowerCase();
7
15
  }
@@ -12,7 +20,7 @@ function normalizeModelString(model) {
12
20
  * @param {Function} log - Logging function (defaults to no-op for testing)
13
21
  * @returns {Array} Array of content objects with type and text/data
14
22
  */
15
- function formatToolContent(result, log = () => {}) {
23
+ function formatToolContent(result, log = () => {}, options = {}) {
16
24
  const text = (s) => [{ type: "text", text: s }];
17
25
 
18
26
  if (!result) return text("OK");
@@ -342,6 +350,19 @@ function formatToolContent(result, log = () => {}) {
342
350
  }
343
351
  return text(output);
344
352
  }
353
+
354
+ if (
355
+ result.scrollTop !== undefined &&
356
+ result.scrollHeight !== undefined &&
357
+ result.scrollPercentage === undefined
358
+ ) {
359
+ return text(`Scrolled to Y:${result.scrollTop} (page height: ${result.scrollHeight})`);
360
+ }
361
+
362
+ if (result.scrollY !== undefined) {
363
+ const pageHeight = result.pageHeight !== undefined ? ` (page height: ${result.pageHeight})` : "";
364
+ return text(`Scrolled to Y:${result.scrollY}${pageHeight}`);
365
+ }
345
366
 
346
367
  if (result.success && result.name && result.tabId !== undefined) {
347
368
  return text(`Registered tab ${result.tabId} as "${result.name}"`);
@@ -369,6 +390,7 @@ function formatToolContent(result, log = () => {}) {
369
390
 
370
391
  if (result.autoScreenshot) {
371
392
  const { path: ssPath, width, height } = result.autoScreenshot;
393
+ if (options.suppressImages) return text(`OK\nScreenshot saved: ${ssPath}`);
372
394
  try {
373
395
  const imgData = fs.readFileSync(ssPath);
374
396
  const base64 = imgData.toString("base64");
@@ -450,11 +472,16 @@ function mapComputerAction(args, tabId) {
450
472
  if (ref) return { type: "CLICK_REF", ref, button: "triple", ...baseMsg };
451
473
  return { type: "EXECUTE_TRIPLE_CLICK", x: coordinate?.[0], y: coordinate?.[1], modifiers, ...baseMsg };
452
474
 
453
- case "type":
475
+ case "type": {
454
476
  if (ref) {
455
477
  return { type: "FORM_FILL", data: [{ ref, value: text }], ...baseMsg };
456
478
  }
479
+ const typeSelector = a.selector || a.into;
480
+ if (typeSelector) {
481
+ return { type: "SMART_TYPE", selector: typeSelector, text, clear: a.clear ?? true, submit: a.submit ?? false, ...baseMsg };
482
+ }
457
483
  return { type: "EXECUTE_TYPE", text, ...baseMsg };
484
+ }
458
485
 
459
486
  case "key": {
460
487
  const keyValue = a.key || text;
@@ -478,14 +505,15 @@ function mapComputerAction(args, tabId) {
478
505
  return { type: "FIND_AND_TYPE", text, submit: a.submit ?? false, submitKey: a.submitKey || "Enter", ...baseMsg };
479
506
 
480
507
  case "scroll": {
481
- const amount = (scroll_amount || 3) * 100;
508
+ const direction = a.direction || scroll_direction;
509
+ const amount = a.scroll_pixels ?? ((a.amount ?? scroll_amount ?? 3) * 100);
482
510
  const deltas = {
483
511
  up: { deltaX: 0, deltaY: -amount },
484
512
  down: { deltaX: 0, deltaY: amount },
485
513
  left: { deltaX: -amount, deltaY: 0 },
486
514
  right: { deltaX: amount, deltaY: 0 },
487
515
  };
488
- const { deltaX, deltaY } = deltas[scroll_direction] || { deltaX: 0, deltaY: 0 };
516
+ const { deltaX, deltaY } = deltas[direction] || { deltaX: 0, deltaY: 0 };
489
517
  return { type: "EXECUTE_SCROLL", deltaX, deltaY, x: coordinate?.[0], y: coordinate?.[1], ...baseMsg };
490
518
  }
491
519
 
@@ -575,7 +603,7 @@ function mapToolToMessage(tool, args, tabId) {
575
603
  type: "EXECUTE_SCREENSHOT",
576
604
  savePath: a.savePath || a.output, // Accept both savePath (CLI) and output (MCP)
577
605
  annotate: a.annotate || false,
578
- fullpage: a.fullpage || false,
606
+ fullpage: a.fullpage || a["full-page"] || false,
579
607
  maxHeight: a["max-height"] || 4000,
580
608
  fullRes: a.full || false,
581
609
  maxSize: a["max-size"] || 1200,
@@ -583,6 +611,31 @@ function mapToolToMessage(tool, args, tabId) {
583
611
  };
584
612
  case "javascript_tool":
585
613
  return { type: "EXECUTE_JAVASCRIPT", code: a.code, ...baseMsg };
614
+ case "animate-audit": {
615
+ if (!a.selector || typeof a.selector !== "string") throw new Error("selector required");
616
+ if (typeof a.duration === "boolean") throw new Error("duration must be a number");
617
+ if (typeof a.fps === "boolean") throw new Error("fps must be a number");
618
+ const durationMs = a.duration !== undefined ? Number(a.duration) : 2000;
619
+ const fps = a.fps !== undefined ? Number(a.fps) : 10;
620
+ if (!Number.isFinite(durationMs) || durationMs < 100 || durationMs > 10000) {
621
+ throw new Error("duration must be between 100 and 10000 ms");
622
+ }
623
+ if (!Number.isFinite(fps) || fps < 1 || fps > 30) {
624
+ throw new Error("fps must be between 1 and 30");
625
+ }
626
+ return { type: "ANIMATE_AUDIT", selector: a.selector, durationMs, fps, ...baseMsg };
627
+ }
628
+ case "perf-audit": {
629
+ if (typeof a.duration === "boolean") throw new Error("duration must be a number");
630
+ if (a.trigger !== undefined && typeof a.trigger !== "string") {
631
+ throw new Error("trigger must be action:target");
632
+ }
633
+ const durationMs = a.duration !== undefined ? Number(a.duration) : 3000;
634
+ if (!Number.isFinite(durationMs) || durationMs < 100 || durationMs > 10000) {
635
+ throw new Error("duration must be between 100 and 10000 ms");
636
+ }
637
+ return { type: "PERF_AUDIT", durationMs, trigger: a.trigger, ...baseMsg };
638
+ }
586
639
  case "wait_for_element":
587
640
  return {
588
641
  type: "WAIT_FOR_ELEMENT",
@@ -679,7 +732,6 @@ function mapToolToMessage(tool, args, tabId) {
679
732
  type: "EXPORT_NETWORK_REQUESTS",
680
733
  har: a.har,
681
734
  jsonl: a.jsonl,
682
- output: a.output,
683
735
  ...baseMsg
684
736
  };
685
737
 
@@ -751,6 +803,12 @@ function mapToolToMessage(tool, args, tabId) {
751
803
  }
752
804
  return { type: "CLOSE_TAB", tabId: id, tabIds: ids };
753
805
  }
806
+ case "tab.move": {
807
+ const id = a.id || a.tab_id || a.tabId;
808
+ const ids = a.ids || a.tab_ids || a.tabIds;
809
+ const windowId = a["to-window"] || a.toWindow || a.window_id || a.windowId;
810
+ return { type: "TAB_MOVE", tabId: id, tabIds: ids, windowId, index: a.index };
811
+ }
754
812
  case "tab.name":
755
813
  return { type: "TABS_REGISTER", name: a.name, ...baseMsg };
756
814
  case "tab.unname":
@@ -843,18 +901,32 @@ function mapToolToMessage(tool, args, tabId) {
843
901
  case "upload":
844
902
  const files = a.files ? (typeof a.files === "string" ? a.files.split(",").map(f => f.trim()) : a.files) : [];
845
903
  return { type: "UPLOAD_FILE", ref: a.ref, files, ...baseMsg };
846
- case "page.read":
847
- return {
848
- type: "READ_PAGE",
849
- options: {
850
- filter: a.filter || "interactive",
851
- refId: a.ref,
904
+ case "page.read": {
905
+ let maxBytes;
906
+ if (a["max-bytes"] !== undefined) {
907
+ const raw = String(a["max-bytes"]).trim();
908
+ if (!/^\d+$/.test(raw) || raw === "0") {
909
+ throw new Error("max-bytes must be a positive integer");
910
+ }
911
+ maxBytes = parseInt(raw, 10);
912
+ if (!Number.isFinite(maxBytes) || maxBytes <= 0) {
913
+ throw new Error("max-bytes must be a positive integer");
914
+ }
915
+ }
916
+ return {
917
+ type: "READ_PAGE",
918
+ options: {
919
+ filter: a.filter || "interactive",
920
+ refId: a.ref,
852
921
  includeText: a["no-text"] !== true,
853
922
  depth: a.depth !== undefined ? parseInt(a.depth, 10) : undefined,
854
923
  compact: a.compact || false,
855
- },
856
- ...baseMsg
924
+ maxBytes,
925
+ forceFullSnapshot: a.compact === true || maxBytes !== undefined,
926
+ },
927
+ ...baseMsg
857
928
  };
929
+ }
858
930
  case "page.text":
859
931
  return { type: "GET_PAGE_TEXT", ...baseMsg };
860
932
  case "page.state":
@@ -1034,7 +1106,7 @@ function mapToolToMessage(tool, args, tabId) {
1034
1106
  return {
1035
1107
  type: "GEMINI_QUERY",
1036
1108
  query: a.query,
1037
- model: a.model || "gemini-3-pro",
1109
+ model: a.model || "gemini-3.1-pro",
1038
1110
  withPage: a["with-page"],
1039
1111
  file: a.file,
1040
1112
  generateImage: a["generate-image"],
@@ -1132,4 +1204,4 @@ function mapToolToMessage(tool, args, tabId) {
1132
1204
  }
1133
1205
  }
1134
1206
 
1135
- module.exports = { mapToolToMessage, mapComputerAction, formatToolContent };
1207
+ module.exports = { mapToolToMessage, mapComputerAction, formatToolContent, buildProviderUploadMessage };
@@ -0,0 +1,283 @@
1
+ const { abortError } = require("./abort.cjs");
2
+
3
+ const MAX_CONNECTIONS = 32;
4
+ const MAX_REMOTE_CONNECTIONS = 16;
5
+ const MAX_PRINCIPAL_CONNECTIONS = 4;
6
+ const MAX_WAITERS = 16;
7
+ const MAX_STREAMS_PER_PRINCIPAL = 2;
8
+ const MAX_REMOTE_STREAMS = 4;
9
+ const MAX_STREAMS = 4;
10
+ const REQUEST_ID_LIMIT = 128;
11
+ const TOOL_NAME_LIMIT = 128;
12
+ const LEASE_IDLE_MS = 5000;
13
+ const AUTHENTICATED_IDLE_MS = 15000;
14
+ const QUEUE_TIMEOUT_MS = 60000;
15
+ const DEFAULT_DEADLINE_MS = 60000;
16
+ const MAX_DEADLINE_MS = 50 * 60 * 1000;
17
+ const CLEANUP_GRACE_MS = 60000;
18
+ const PROVIDER_DEFAULT_TIMEOUT_SECONDS = {
19
+ ai: 300,
20
+ aistudio: 300,
21
+ "aistudio.build": 600,
22
+ chatgpt: 2700,
23
+ gemini: 300,
24
+ grok: 300,
25
+ perplexity: 120,
26
+ };
27
+
28
+ function resolveRequestDeadlineMs(tool, args = {}) {
29
+ const defaultSeconds = PROVIDER_DEFAULT_TIMEOUT_SECONDS[tool];
30
+ if (defaultSeconds === undefined) return DEFAULT_DEADLINE_MS;
31
+ const requestedSeconds = Number(
32
+ args && typeof args === "object" && !Array.isArray(args) ? args.timeout : undefined,
33
+ );
34
+ const seconds = Number.isFinite(requestedSeconds) && requestedSeconds > 0
35
+ ? requestedSeconds
36
+ : defaultSeconds;
37
+ return Math.min(seconds * 1000 + CLEANUP_GRACE_MS, MAX_DEADLINE_MS);
38
+ }
39
+
40
+ class HostSessionManager {
41
+ constructor({ audit = () => {}, onTimeout = () => {} } = {}) {
42
+ this.audit = audit;
43
+ this.onTimeout = onTimeout;
44
+ this.contexts = new Set();
45
+ this.principalCounts = new Map();
46
+ this.waiters = [];
47
+ this.leaseOwner = null;
48
+ this.remoteConnections = 0;
49
+ this.streams = new Set();
50
+ }
51
+
52
+ admit(socket, isRemote) {
53
+ if (this.contexts.size >= MAX_CONNECTIONS) throw new Error("maximum client connections reached");
54
+ if (isRemote && this.remoteConnections >= MAX_REMOTE_CONNECTIONS) throw new Error("maximum remote connections reached");
55
+ const context = {
56
+ socket,
57
+ isRemote,
58
+ principal: null,
59
+ closed: false,
60
+ activeRequest: null,
61
+ seenRequestIds: new Set(),
62
+ idleTimer: null,
63
+ workTimer: null,
64
+ admitted: true,
65
+ stream: false,
66
+ };
67
+ this.contexts.add(context);
68
+ if (isRemote) this.remoteConnections += 1;
69
+ this.audit({ event: "connection", context, outcome: "accepted" });
70
+ return context;
71
+ }
72
+
73
+ authenticate(context, principal) {
74
+ if (context.closed) throw new Error("connection is closed");
75
+ const count = this.principalCounts.get(principal.clientId) || 0;
76
+ if (count >= MAX_PRINCIPAL_CONNECTIONS) throw new Error("maximum connections for remote principal reached");
77
+ context.principal = principal;
78
+ this.principalCounts.set(principal.clientId, count + 1);
79
+ context.workTimer = setTimeout(() => {
80
+ if (!context.closed && !context.activeRequest) {
81
+ this.audit({ event: "connection", context, outcome: "authenticated-idle-timeout" });
82
+ context.socket.destroy();
83
+ }
84
+ }, AUTHENTICATED_IDLE_MS);
85
+ this.audit({ event: "authentication", context, outcome: "success" });
86
+ }
87
+
88
+ touch(context) {
89
+ if (!context || context.closed || !context.isRemote || context.activeRequest) return;
90
+ if (context.workTimer) clearTimeout(context.workTimer);
91
+ context.workTimer = setTimeout(() => {
92
+ if (!context.closed && !context.activeRequest) {
93
+ this.audit({ event: "connection", context, outcome: "authenticated-idle-timeout" });
94
+ context.socket.destroy();
95
+ }
96
+ }, AUTHENTICATED_IDLE_MS);
97
+ }
98
+
99
+ canStartStream(context) {
100
+ if (context.closed || context.stream || context.activeRequest || this.leaseOwner === context) return false;
101
+ const principalId = context.principal?.clientId || "local";
102
+ const principalStreams = [...this.streams].filter((entry) => entry.principalId === principalId).length;
103
+ if (principalStreams >= MAX_STREAMS_PER_PRINCIPAL) return false;
104
+ if (this.streams.size >= MAX_STREAMS) return false;
105
+ if (context.isRemote && [...this.streams].filter((entry) => entry.isRemote).length >= MAX_REMOTE_STREAMS) return false;
106
+ if (context.workTimer) {
107
+ clearTimeout(context.workTimer);
108
+ context.workTimer = null;
109
+ }
110
+ context.stream = true;
111
+ this.streams.add({ context, principalId, isRemote: context.isRemote });
112
+ return true;
113
+ }
114
+
115
+ stopStream(context) {
116
+ for (const entry of this.streams) if (entry.context === context) this.streams.delete(entry);
117
+ context.stream = false;
118
+ }
119
+
120
+ beginRequest(context, { id, tool, deadlineMs }) {
121
+ if (context.closed) return Promise.reject(new Error("connection is closed"));
122
+ if (context.workTimer) {
123
+ clearTimeout(context.workTimer);
124
+ context.workTimer = null;
125
+ }
126
+ if (context.stream) return Promise.reject(new Error("stream connection cannot execute tools"));
127
+ if (typeof id !== "string" || !id) return Promise.reject(new Error("request id is required"));
128
+ if (id.length > REQUEST_ID_LIMIT) return Promise.reject(new Error("request ID is too long"));
129
+ if (typeof tool !== "string" || !tool) return Promise.reject(new Error("tool name is required"));
130
+ if (tool.length > TOOL_NAME_LIMIT) return Promise.reject(new Error("tool name is too long"));
131
+ if (context.seenRequestIds.has(id)) return Promise.reject(new Error("duplicate request id"));
132
+ if (context.seenRequestIds.size >= REQUEST_ID_LIMIT) return Promise.reject(new Error("request ID limit reached"));
133
+ if (context.activeRequest) return Promise.reject(new Error("one in-flight request per connection"));
134
+ context.seenRequestIds.add(id);
135
+ const controller = new AbortController();
136
+ const request = {
137
+ id,
138
+ tool,
139
+ startedAt: Date.now(),
140
+ deadlineMs: Math.min(Math.max(deadlineMs || DEFAULT_DEADLINE_MS, 1), MAX_DEADLINE_MS),
141
+ queued: true,
142
+ settled: false,
143
+ controller,
144
+ signal: controller.signal,
145
+ tombstoned: false,
146
+ };
147
+ context.activeRequest = request;
148
+ const grant = () => {
149
+ if (context.closed) return Promise.reject(new Error("connection closed while waiting for browser lease"));
150
+ request.queued = false;
151
+ request.timer = setTimeout(() => this.onRequestTimeout(context, request), request.deadlineMs);
152
+ this.leaseOwner = context;
153
+ this.audit({ event: "lease", context, request, outcome: "acquired" });
154
+ return Promise.resolve(request);
155
+ };
156
+ if (!this.leaseOwner || this.leaseOwner === context) {
157
+ if (context.idleTimer) clearTimeout(context.idleTimer);
158
+ context.idleTimer = null;
159
+ return grant();
160
+ }
161
+ if (this.waiters.length >= MAX_WAITERS) {
162
+ context.activeRequest = null;
163
+ return Promise.reject(new Error("browser lease queue is full"));
164
+ }
165
+ return new Promise((resolve, reject) => {
166
+ const waiter = { context, request, resolve, reject };
167
+ waiter.timer = setTimeout(() => {
168
+ const index = this.waiters.indexOf(waiter);
169
+ if (index !== -1) this.waiters.splice(index, 1);
170
+ if (context.activeRequest === request) context.activeRequest = null;
171
+ this.audit({ event: "lease", context, request, outcome: "queue-timeout" });
172
+ reject(new Error("timed out waiting for browser lease"));
173
+ }, QUEUE_TIMEOUT_MS);
174
+ this.waiters.push(waiter);
175
+ this.audit({ event: "lease", context, request, outcome: "queued" });
176
+ }).then(() => grant());
177
+ }
178
+
179
+ onRequestTimeout(context, request) {
180
+ if (context.activeRequest !== request || request.settled) return;
181
+ request.tombstoned = true;
182
+ if (!request.signal.aborted) request.controller.abort(abortError(null, "Request timed out"));
183
+ this.audit({ event: "request", context, request, outcome: "hard-timeout" });
184
+ this.onTimeout(context, request);
185
+ }
186
+
187
+ canRespond(context, id) {
188
+ return Boolean(context && context.activeRequest && context.activeRequest.id === id && !context.activeRequest.settled);
189
+ }
190
+
191
+ complete(context, id, outcome = "completed") {
192
+ const request = context?.activeRequest;
193
+ if (!request || request.id !== id || request.settled) return;
194
+ request.settled = true;
195
+ if (request.timer) clearTimeout(request.timer);
196
+ if (request.abortCleanup) request.abortCleanup();
197
+ context.activeRequest = null;
198
+ this.audit({ event: "request", context, request, outcome, elapsedMs: Date.now() - request.startedAt });
199
+ if (this.leaseOwner === context) {
200
+ if (context.closed) this.releaseLease(context);
201
+ else {
202
+ if (context.idleTimer) clearTimeout(context.idleTimer);
203
+ context.idleTimer = setTimeout(() => {
204
+ if (!context.activeRequest && this.leaseOwner === context) this.releaseLease(context);
205
+ }, LEASE_IDLE_MS);
206
+ }
207
+ }
208
+ }
209
+
210
+ releaseLease(context) {
211
+ if (this.leaseOwner !== context) return;
212
+ if (context.idleTimer) clearTimeout(context.idleTimer);
213
+ context.idleTimer = null;
214
+ this.leaseOwner = null;
215
+ this.audit({ event: "lease", context, outcome: "released" });
216
+ while (this.waiters.length) {
217
+ const waiter = this.waiters.shift();
218
+ if (!waiter || waiter.context.closed) continue;
219
+ clearTimeout(waiter.timer);
220
+ this.leaseOwner = waiter.context;
221
+ waiter.resolve();
222
+ return;
223
+ }
224
+ }
225
+
226
+ close(context) {
227
+ if (!context || context.closed) return;
228
+ context.closed = true;
229
+ if (context.workTimer) clearTimeout(context.workTimer);
230
+ context.workTimer = null;
231
+ this.stopStream(context);
232
+ if (context.principal) {
233
+ const count = this.principalCounts.get(context.principal.clientId) || 1;
234
+ if (count <= 1) this.principalCounts.delete(context.principal.clientId);
235
+ else this.principalCounts.set(context.principal.clientId, count - 1);
236
+ }
237
+ for (let index = this.waiters.length - 1; index >= 0; index -= 1) {
238
+ const waiter = this.waiters[index];
239
+ if (waiter.context !== context) continue;
240
+ this.waiters.splice(index, 1);
241
+ clearTimeout(waiter.timer);
242
+ waiter.reject(new Error("connection closed while waiting for browser lease"));
243
+ }
244
+ if (context.activeRequest?.queued) {
245
+ const request = context.activeRequest;
246
+ context.activeRequest = null;
247
+ if (!request.signal.aborted) request.controller.abort(abortError(null, "Request cancelled: queued client disconnected"));
248
+ this.audit({ event: "request", context, request, outcome: "queue-canceled" });
249
+ } else if (context.activeRequest) {
250
+ const request = context.activeRequest;
251
+ request.abandoned = true;
252
+ request.tombstoned = true;
253
+ if (!request.signal.aborted) request.controller.abort(abortError(null, "Request cancelled: client disconnected"));
254
+ this.audit({ event: "request", context, request, outcome: "abort-requested" });
255
+ this.audit({ event: "request", context, request, outcome: "abandoned" });
256
+ } else if (this.leaseOwner === context) {
257
+ this.releaseLease(context);
258
+ }
259
+ this.contexts.delete(context);
260
+ if (context.isRemote) this.remoteConnections -= 1;
261
+ this.audit({ event: "connection", context, outcome: "closed" });
262
+ }
263
+ }
264
+
265
+ module.exports = {
266
+ AUTHENTICATED_IDLE_MS,
267
+ DEFAULT_DEADLINE_MS,
268
+ HostSessionManager,
269
+ MAX_DEADLINE_MS,
270
+ PROVIDER_DEFAULT_TIMEOUT_SECONDS,
271
+ resolveRequestDeadlineMs,
272
+ LEASE_IDLE_MS,
273
+ MAX_CONNECTIONS,
274
+ MAX_PRINCIPAL_CONNECTIONS,
275
+ MAX_REMOTE_CONNECTIONS,
276
+ MAX_REMOTE_STREAMS,
277
+ MAX_STREAMS,
278
+ MAX_STREAMS_PER_PRINCIPAL,
279
+ TOOL_NAME_LIMIT,
280
+ MAX_WAITERS,
281
+ QUEUE_TIMEOUT_MS,
282
+ REQUEST_ID_LIMIT,
283
+ };