@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.163",
3
+ "version": "0.2.164",
4
4
  "description": "First-party ReMCP local device runtime: filesystem, terminal, computer-use, browser, diagnostics and document tools over MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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
- list = await requestJson('/json/list', endpoint, { timeout_ms: 1500 });
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),