@remcp/runtime 0.2.163 → 0.2.164
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/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Coalesce *only overlapping* read-only CDP /json/list transport requests.
|
|
2
|
+
// Raw tab metadata is held only until request completion; each caller must
|
|
3
|
+
// re-validate all target URLs/DNS independently before returning any tabs.
|
|
4
|
+
const active = new Map();
|
|
5
|
+
const MAX_ENDPOINTS = 8;
|
|
6
|
+
|
|
7
|
+
export async function shareBrowserDiscovery(origin, load) {
|
|
8
|
+
const pending = active.get(origin);
|
|
9
|
+
if (pending) return pending;
|
|
10
|
+
if (active.size >= MAX_ENDPOINTS) return load();
|
|
11
|
+
const work = Promise.resolve().then(load);
|
|
12
|
+
active.set(origin, work);
|
|
13
|
+
try { return await work; }
|
|
14
|
+
finally {
|
|
15
|
+
if (active.get(origin) === work) active.delete(origin);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -6,6 +6,7 @@ import { lookup } from 'node:dns/promises';
|
|
|
6
6
|
import { closeSync, constants as fsConstants, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readdirSync } from 'node:fs';
|
|
7
7
|
import { resolveSafePath } from '../util.mjs';
|
|
8
8
|
import { preserveInterruptedBrowserSession } from './browser-session-backup.mjs';
|
|
9
|
+
import { shareBrowserDiscovery } from './browser-discovery-flight.mjs';
|
|
9
10
|
import { clamp, commandExists, jsonResult, optionalString, requireEnum, spawnDetached, unavailable } from './common.mjs';
|
|
10
11
|
|
|
11
12
|
|
|
@@ -436,14 +437,16 @@ export async function filterBrowserTargets(list, { validate = assertResolvedPage
|
|
|
436
437
|
return targets;
|
|
437
438
|
}
|
|
438
439
|
|
|
439
|
-
export async function listBrowserTargets(endpoint, { targetId = null } = {}) {
|
|
440
|
+
export async function listBrowserTargets(endpoint, { targetId = null, readOnlySingleFlight = false, validate = assertResolvedPageUrl } = {}) {
|
|
440
441
|
assertBrowserRemoteEnabled();
|
|
441
442
|
// Reading tabs and page semantics must not launch a separate Chromium/profile.
|
|
442
443
|
// Only an explicit browser_navigate(action=new_tab) can bootstrap an isolated browser.
|
|
443
444
|
const base = endpointUrl(endpoint); // keeps the CDP address loopback-only
|
|
444
445
|
let list;
|
|
445
446
|
try {
|
|
446
|
-
|
|
447
|
+
const load = () => requestJson('/json/list', endpoint, { timeout_ms: 1500 });
|
|
448
|
+
list = readOnlySingleFlight && !base.username && !base.password
|
|
449
|
+
? await shareBrowserDiscovery(base.origin, load) : await load();
|
|
447
450
|
} catch (error) {
|
|
448
451
|
const message = String(error?.message || '');
|
|
449
452
|
// Keep HTTP/policy/oversize/malformed-response errors distinct from an unavailable CDP.
|
|
@@ -453,7 +456,7 @@ export async function listBrowserTargets(endpoint, { targetId = null } = {}) {
|
|
|
453
456
|
throw new Error(`${label} is unavailable at ${base.origin}. Read-only browser_tabs and browser_snapshot do not launch a browser. Use ui_snapshot and ui_find for the existing browser window; do not retry this CDP endpoint unchanged. Only browser_navigate(action=new_tab), when explicitly requested, may start a local isolated browser.`);
|
|
454
457
|
}
|
|
455
458
|
// Retain the current bounded concurrent DNS/security checks for up to 1000 tabs.
|
|
456
|
-
return filterBrowserTargets(list, { targetId });
|
|
459
|
+
return filterBrowserTargets(list, { targetId, validate });
|
|
457
460
|
}
|
|
458
461
|
|
|
459
462
|
export function validatedTargetWebSocketUrl(target, endpoint) {
|
|
@@ -14,7 +14,7 @@ import { assertResolvedBrowserUrl, assertResolvedPageUrl, boundedText, endpointU
|
|
|
14
14
|
import { createGuardedNewTab, evaluate, withTarget } from './browser-session.mjs';
|
|
15
15
|
|
|
16
16
|
export async function browserTabs(args = {}) {
|
|
17
|
-
const targets = await listBrowserTargets(args.endpoint);
|
|
17
|
+
const targets = await listBrowserTargets(args.endpoint, { readOnlySingleFlight:true });
|
|
18
18
|
const pages = targets.filter(item => item.type === 'page').slice(0, 500).map(item => ({
|
|
19
19
|
id: boundedText(item.id, 256),
|
|
20
20
|
title: boundedText(item.title, 1000),
|