viyv-browser-mcp 0.10.0 → 0.10.2
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/dist/index.js +552 -178
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11762,7 +11762,15 @@ var RECONNECT = {
|
|
|
11762
11762
|
/** Max delay (ms) */
|
|
11763
11763
|
MAX_DELAY: 3e4,
|
|
11764
11764
|
/** Backoff multiplier */
|
|
11765
|
-
MULTIPLIER: 2
|
|
11765
|
+
MULTIPLIER: 2,
|
|
11766
|
+
/** Symmetric jitter ratio applied to each reconnect delay. ±30% spreads synchronized retries. */
|
|
11767
|
+
JITTER_RATIO: 0.3
|
|
11768
|
+
};
|
|
11769
|
+
var BRIDGE_READINESS = {
|
|
11770
|
+
/** Max time a pre-handshake tool_call waits for `bridge_status { connected:true }` */
|
|
11771
|
+
TIMEOUT_MS: 8e3,
|
|
11772
|
+
/** Max number of tool_calls queued while the bridge client is below `ready` state */
|
|
11773
|
+
PENDING_QUEUE_MAX: 10
|
|
11766
11774
|
};
|
|
11767
11775
|
var BRIDGE = {
|
|
11768
11776
|
/** TCP port for Native Host bridge (TCP server) */
|
|
@@ -11803,6 +11811,35 @@ function matchSubscription(sub, event) {
|
|
|
11803
11811
|
return matchEventType(sub.eventTypes, event.eventType) && matchUrlPattern(sub.urlPattern, event.url);
|
|
11804
11812
|
}
|
|
11805
11813
|
|
|
11814
|
+
// ../shared/dist/util/jitter.js
|
|
11815
|
+
function applyJitter(baseMs, ratio, random = Math.random) {
|
|
11816
|
+
if (!Number.isFinite(baseMs) || baseMs < 0)
|
|
11817
|
+
return 0;
|
|
11818
|
+
if (ratio <= 0)
|
|
11819
|
+
return baseMs;
|
|
11820
|
+
const clampedRatio = Math.min(ratio, 1);
|
|
11821
|
+
const offset = (random() * 2 - 1) * clampedRatio * baseMs;
|
|
11822
|
+
const result = baseMs + offset;
|
|
11823
|
+
return Math.max(0, Math.round(result));
|
|
11824
|
+
}
|
|
11825
|
+
|
|
11826
|
+
// ../shared/dist/util/exit-reason.js
|
|
11827
|
+
function shouldBroadcastClosing(reason) {
|
|
11828
|
+
switch (reason) {
|
|
11829
|
+
case "chrome-sigterm":
|
|
11830
|
+
case "sigint":
|
|
11831
|
+
return false;
|
|
11832
|
+
case "all-empty":
|
|
11833
|
+
case "relay-exhausted":
|
|
11834
|
+
case "shutdown-request":
|
|
11835
|
+
return true;
|
|
11836
|
+
default: {
|
|
11837
|
+
const _exhaustive = reason;
|
|
11838
|
+
return _exhaustive;
|
|
11839
|
+
}
|
|
11840
|
+
}
|
|
11841
|
+
}
|
|
11842
|
+
|
|
11806
11843
|
// src/native-host-updater.ts
|
|
11807
11844
|
import {
|
|
11808
11845
|
chmodSync,
|
|
@@ -11817,7 +11854,7 @@ import { homedir } from "os";
|
|
|
11817
11854
|
import { resolve } from "path";
|
|
11818
11855
|
import { fileURLToPath } from "url";
|
|
11819
11856
|
var PKG_VERSION = (() => {
|
|
11820
|
-
if (true) return "0.10.
|
|
11857
|
+
if (true) return "0.10.2";
|
|
11821
11858
|
try {
|
|
11822
11859
|
const here = fileURLToPath(import.meta.url);
|
|
11823
11860
|
const pkgPath = resolve(here, "..", "..", "package.json");
|
|
@@ -12460,7 +12497,7 @@ function startBridge(options) {
|
|
|
12460
12497
|
const DRAIN_TIMEOUT = 5e3;
|
|
12461
12498
|
const drainAndExit = () => {
|
|
12462
12499
|
if (requestOrigin.size === 0) {
|
|
12463
|
-
gracefulExit("
|
|
12500
|
+
gracefulExit("shutdown-request", "version update");
|
|
12464
12501
|
return;
|
|
12465
12502
|
}
|
|
12466
12503
|
process.stderr.write(
|
|
@@ -12471,7 +12508,7 @@ function startBridge(options) {
|
|
|
12471
12508
|
const check2 = setInterval(() => {
|
|
12472
12509
|
if (requestOrigin.size === 0 || Date.now() - start > DRAIN_TIMEOUT) {
|
|
12473
12510
|
clearInterval(check2);
|
|
12474
|
-
gracefulExit("
|
|
12511
|
+
gracefulExit("shutdown-request", "version update");
|
|
12475
12512
|
}
|
|
12476
12513
|
}, 200);
|
|
12477
12514
|
};
|
|
@@ -12536,6 +12573,7 @@ function startBridge(options) {
|
|
|
12536
12573
|
} else if (type === "browser_event") {
|
|
12537
12574
|
const agentId = message2.agentId;
|
|
12538
12575
|
if (agentId) broadcastToAgent(agentId, message2);
|
|
12576
|
+
} else if (type === "sw_keepalive") {
|
|
12539
12577
|
} else {
|
|
12540
12578
|
process.stderr.write(`${LOG_PREFIX3} Unknown message type from Chrome: ${type}, dropping
|
|
12541
12579
|
`);
|
|
@@ -12565,19 +12603,22 @@ function startBridge(options) {
|
|
|
12565
12603
|
let primaryStdinClosed = false;
|
|
12566
12604
|
const ORPHAN_TIMEOUT = 6e4;
|
|
12567
12605
|
let orphanTimer = null;
|
|
12568
|
-
function gracefulExit(reason) {
|
|
12569
|
-
|
|
12606
|
+
function gracefulExit(reason, detail) {
|
|
12607
|
+
const suffix = detail ? ` (${detail})` : "";
|
|
12608
|
+
process.stderr.write(`${LOG_PREFIX3} exiting reason=${reason}${suffix}
|
|
12570
12609
|
`);
|
|
12571
12610
|
if (orphanTimer) {
|
|
12572
12611
|
clearTimeout(orphanTimer);
|
|
12573
12612
|
orphanTimer = null;
|
|
12574
12613
|
}
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12614
|
+
if (shouldBroadcastClosing(reason)) {
|
|
12615
|
+
const closingMsg = JSON.stringify({ type: "bridge_closing", timestamp: Date.now() });
|
|
12616
|
+
for (const [, conn] of mcpConnections) {
|
|
12617
|
+
try {
|
|
12618
|
+
conn.socket.write(`${closingMsg}
|
|
12579
12619
|
`);
|
|
12580
|
-
|
|
12620
|
+
} catch {
|
|
12621
|
+
}
|
|
12581
12622
|
}
|
|
12582
12623
|
}
|
|
12583
12624
|
for (const [, conn] of mcpConnections) {
|
|
@@ -12603,7 +12644,7 @@ function startBridge(options) {
|
|
|
12603
12644
|
return;
|
|
12604
12645
|
}
|
|
12605
12646
|
if (chromeConnections.size === 0 && mcpConnections.size === 0) {
|
|
12606
|
-
gracefulExit("
|
|
12647
|
+
gracefulExit("all-empty", "no connections remaining");
|
|
12607
12648
|
}
|
|
12608
12649
|
if (chromeConnections.size === 0 && mcpConnections.size > 0 && !orphanTimer) {
|
|
12609
12650
|
process.stderr.write(
|
|
@@ -12819,8 +12860,8 @@ function startBridge(options) {
|
|
|
12819
12860
|
onError: (e) => onError?.(e)
|
|
12820
12861
|
});
|
|
12821
12862
|
notifyChromeConnected(false);
|
|
12822
|
-
process.on("SIGINT", () => gracefulExit("
|
|
12823
|
-
process.on("SIGTERM", () => gracefulExit("
|
|
12863
|
+
process.on("SIGINT", () => gracefulExit("sigint"));
|
|
12864
|
+
process.on("SIGTERM", () => gracefulExit("chrome-sigterm"));
|
|
12824
12865
|
process.stdin.on("end", () => {
|
|
12825
12866
|
process.stderr.write(`${LOG_PREFIX3} Primary Chrome stdin closed
|
|
12826
12867
|
`);
|
|
@@ -12835,7 +12876,6 @@ function startBridge(options) {
|
|
|
12835
12876
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
12836
12877
|
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
12837
12878
|
import http from "http";
|
|
12838
|
-
import { createConnection as createConnection2 } from "net";
|
|
12839
12879
|
import { dirname as dirname2 } from "path";
|
|
12840
12880
|
|
|
12841
12881
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
@@ -28249,6 +28289,394 @@ var StreamableHTTPServerTransport = class {
|
|
|
28249
28289
|
}
|
|
28250
28290
|
};
|
|
28251
28291
|
|
|
28292
|
+
// src/native-host/bridge-client/index.ts
|
|
28293
|
+
import { createConnection as createConnection2 } from "net";
|
|
28294
|
+
|
|
28295
|
+
// src/native-host/bridge-client/connection-state.ts
|
|
28296
|
+
function transition(state, event) {
|
|
28297
|
+
if (event.kind === "stop") return "disconnected";
|
|
28298
|
+
switch (state) {
|
|
28299
|
+
case "disconnected":
|
|
28300
|
+
if (event.kind === "start") return "connecting";
|
|
28301
|
+
return state;
|
|
28302
|
+
case "connecting":
|
|
28303
|
+
if (event.kind === "socket-connected") return "handshaking";
|
|
28304
|
+
if (event.kind === "socket-closed") return "disconnected";
|
|
28305
|
+
return state;
|
|
28306
|
+
case "handshaking":
|
|
28307
|
+
if (event.kind === "bridge-status-true") return "ready";
|
|
28308
|
+
if (event.kind === "bridge-status-false") return "handshaking";
|
|
28309
|
+
if (event.kind === "bridge-closing") return "draining";
|
|
28310
|
+
if (event.kind === "socket-closed") return "disconnected";
|
|
28311
|
+
return state;
|
|
28312
|
+
case "ready":
|
|
28313
|
+
if (event.kind === "bridge-status-false") return "handshaking";
|
|
28314
|
+
if (event.kind === "bridge-closing") return "draining";
|
|
28315
|
+
if (event.kind === "socket-closed") return "disconnected";
|
|
28316
|
+
return state;
|
|
28317
|
+
case "draining":
|
|
28318
|
+
if (event.kind === "socket-closed") return "disconnected";
|
|
28319
|
+
return state;
|
|
28320
|
+
default: {
|
|
28321
|
+
const _exhaustive = state;
|
|
28322
|
+
return _exhaustive;
|
|
28323
|
+
}
|
|
28324
|
+
}
|
|
28325
|
+
}
|
|
28326
|
+
function isReady(state) {
|
|
28327
|
+
return state === "ready";
|
|
28328
|
+
}
|
|
28329
|
+
|
|
28330
|
+
// src/native-host/bridge-client/pending-queue.ts
|
|
28331
|
+
var PendingQueue = class {
|
|
28332
|
+
constructor(max, ttlMs, timers) {
|
|
28333
|
+
this.max = max;
|
|
28334
|
+
this.ttlMs = ttlMs;
|
|
28335
|
+
this.timers = timers;
|
|
28336
|
+
if (max < 1) throw new Error("PendingQueue max must be >= 1");
|
|
28337
|
+
if (ttlMs < 0) throw new Error("PendingQueue ttlMs must be >= 0");
|
|
28338
|
+
}
|
|
28339
|
+
entries = [];
|
|
28340
|
+
/** Try to enqueue. Returns `true` on success, `false` when rejected (onReject already fired). */
|
|
28341
|
+
enqueue(entry) {
|
|
28342
|
+
if (this.entries.length >= this.max) {
|
|
28343
|
+
entry.onReject("queue-full");
|
|
28344
|
+
return false;
|
|
28345
|
+
}
|
|
28346
|
+
const stored = { ...entry, timer: null };
|
|
28347
|
+
stored.timer = this.timers.setTimer(() => this.expire(stored), this.ttlMs);
|
|
28348
|
+
this.entries.push(stored);
|
|
28349
|
+
return true;
|
|
28350
|
+
}
|
|
28351
|
+
/** Flush every queued entry in FIFO order. */
|
|
28352
|
+
flushAll() {
|
|
28353
|
+
const snapshot = this.entries.splice(0, this.entries.length);
|
|
28354
|
+
for (const entry of snapshot) {
|
|
28355
|
+
this.timers.clearTimer(entry.timer);
|
|
28356
|
+
entry.onFlush(entry.payload);
|
|
28357
|
+
}
|
|
28358
|
+
}
|
|
28359
|
+
/** Reject every queued entry with the given reason. Used on `stop()`. */
|
|
28360
|
+
rejectAll(reason) {
|
|
28361
|
+
const snapshot = this.entries.splice(0, this.entries.length);
|
|
28362
|
+
for (const entry of snapshot) {
|
|
28363
|
+
this.timers.clearTimer(entry.timer);
|
|
28364
|
+
entry.onReject(reason);
|
|
28365
|
+
}
|
|
28366
|
+
}
|
|
28367
|
+
size() {
|
|
28368
|
+
return this.entries.length;
|
|
28369
|
+
}
|
|
28370
|
+
expire(entry) {
|
|
28371
|
+
const idx = this.entries.indexOf(entry);
|
|
28372
|
+
if (idx === -1) return;
|
|
28373
|
+
this.entries.splice(idx, 1);
|
|
28374
|
+
entry.onReject("timeout");
|
|
28375
|
+
}
|
|
28376
|
+
};
|
|
28377
|
+
|
|
28378
|
+
// src/native-host/bridge-client/index.ts
|
|
28379
|
+
var LOG_PREFIX4 = "[viyv-browser:mcp:bridge]";
|
|
28380
|
+
var METRIC_PREFIX = "[viyv-browser:mcp:metric]";
|
|
28381
|
+
var LINE_TERMINATOR = "\n";
|
|
28382
|
+
var BRIDGE_RECONNECT_CAP_MS = 3e3;
|
|
28383
|
+
var defaultTimers = {
|
|
28384
|
+
setTimer: (fn, ms) => setTimeout(fn, ms),
|
|
28385
|
+
clearTimer: (h) => clearTimeout(h)
|
|
28386
|
+
};
|
|
28387
|
+
var defaultSchedule = (fn, ms) => {
|
|
28388
|
+
const h = setTimeout(fn, ms);
|
|
28389
|
+
return () => clearTimeout(h);
|
|
28390
|
+
};
|
|
28391
|
+
var BridgeClient = class {
|
|
28392
|
+
constructor(options) {
|
|
28393
|
+
this.options = options;
|
|
28394
|
+
this.log = options.log ?? ((line) => process.stderr.write(`${line}
|
|
28395
|
+
`));
|
|
28396
|
+
this.metric = options.metric ?? ((line) => process.stderr.write(`${line}
|
|
28397
|
+
`));
|
|
28398
|
+
this.now = options.now ?? (() => Date.now());
|
|
28399
|
+
this.schedule = options.schedule ?? defaultSchedule;
|
|
28400
|
+
this.random = options.random ?? Math.random;
|
|
28401
|
+
this.queue = new PendingQueue(
|
|
28402
|
+
BRIDGE_READINESS.PENDING_QUEUE_MAX,
|
|
28403
|
+
BRIDGE_READINESS.TIMEOUT_MS,
|
|
28404
|
+
options.timers ?? defaultTimers
|
|
28405
|
+
);
|
|
28406
|
+
}
|
|
28407
|
+
state = "disconnected";
|
|
28408
|
+
socket = null;
|
|
28409
|
+
reconnectAttempt = 0;
|
|
28410
|
+
cancelReconnect = null;
|
|
28411
|
+
messageHandlers = /* @__PURE__ */ new Set();
|
|
28412
|
+
stateHandlers = /* @__PURE__ */ new Set();
|
|
28413
|
+
queue;
|
|
28414
|
+
/** ms since epoch when the current handshake started; `null` when not measuring. */
|
|
28415
|
+
handshakeStartMs = null;
|
|
28416
|
+
started = false;
|
|
28417
|
+
log;
|
|
28418
|
+
metric;
|
|
28419
|
+
now;
|
|
28420
|
+
schedule;
|
|
28421
|
+
random;
|
|
28422
|
+
start() {
|
|
28423
|
+
if (this.started) return;
|
|
28424
|
+
this.started = true;
|
|
28425
|
+
this.apply({ kind: "start" });
|
|
28426
|
+
this.connect();
|
|
28427
|
+
}
|
|
28428
|
+
stop() {
|
|
28429
|
+
this.started = false;
|
|
28430
|
+
this.cancelReconnect?.();
|
|
28431
|
+
this.cancelReconnect = null;
|
|
28432
|
+
this.queue.rejectAll("stopped");
|
|
28433
|
+
if (this.socket && !this.socket.destroyed) {
|
|
28434
|
+
this.socket.destroy();
|
|
28435
|
+
}
|
|
28436
|
+
this.socket = null;
|
|
28437
|
+
this.apply({ kind: "stop" });
|
|
28438
|
+
}
|
|
28439
|
+
/**
|
|
28440
|
+
* Drop the current socket and reconnect immediately. Used by switch_browser
|
|
28441
|
+
* to pivot to a fresh Chrome. Pending tool_calls are drained with the
|
|
28442
|
+
* SWITCH_IN_PROGRESS error by the caller before this is invoked.
|
|
28443
|
+
*/
|
|
28444
|
+
forceReconnect() {
|
|
28445
|
+
if (this.socket && !this.socket.destroyed) {
|
|
28446
|
+
this.socket.destroy();
|
|
28447
|
+
}
|
|
28448
|
+
this.socket = null;
|
|
28449
|
+
this.apply({ kind: "socket-closed" });
|
|
28450
|
+
if (this.started) {
|
|
28451
|
+
this.reconnectAttempt = 0;
|
|
28452
|
+
this.scheduleReconnect();
|
|
28453
|
+
}
|
|
28454
|
+
}
|
|
28455
|
+
getState() {
|
|
28456
|
+
return this.state;
|
|
28457
|
+
}
|
|
28458
|
+
isReady() {
|
|
28459
|
+
return isReady(this.state);
|
|
28460
|
+
}
|
|
28461
|
+
/**
|
|
28462
|
+
* Return the socket when it is TCP-live (handshaking, ready, or draining).
|
|
28463
|
+
* Ancillary writes like session_init / session_close tolerate writes during
|
|
28464
|
+
* handshake — the bridge buffers them until Chrome is reachable.
|
|
28465
|
+
* Returns null when the socket is not safe to write to.
|
|
28466
|
+
*/
|
|
28467
|
+
getSocket() {
|
|
28468
|
+
if (!this.socket || this.socket.destroyed) return null;
|
|
28469
|
+
if (this.state === "handshaking" || this.state === "ready" || this.state === "draining") {
|
|
28470
|
+
return this.socket;
|
|
28471
|
+
}
|
|
28472
|
+
return null;
|
|
28473
|
+
}
|
|
28474
|
+
onMessage(fn) {
|
|
28475
|
+
this.messageHandlers.add(fn);
|
|
28476
|
+
return () => this.messageHandlers.delete(fn);
|
|
28477
|
+
}
|
|
28478
|
+
onStateChange(fn) {
|
|
28479
|
+
this.stateHandlers.add(fn);
|
|
28480
|
+
return () => this.stateHandlers.delete(fn);
|
|
28481
|
+
}
|
|
28482
|
+
/**
|
|
28483
|
+
* Send a tool_call line. If the bridge is `ready`, writes immediately.
|
|
28484
|
+
* Otherwise enqueues until readiness, failing with `queue-full` if capacity
|
|
28485
|
+
* is exhausted or `timeout` if the handshake takes too long.
|
|
28486
|
+
*/
|
|
28487
|
+
enqueueToolCall(line) {
|
|
28488
|
+
return new Promise((resolve3, reject) => {
|
|
28489
|
+
if (this.state === "ready" && this.socket && !this.socket.destroyed) {
|
|
28490
|
+
try {
|
|
28491
|
+
this.socket.write(line.endsWith(LINE_TERMINATOR) ? line : line + LINE_TERMINATOR);
|
|
28492
|
+
resolve3();
|
|
28493
|
+
} catch (err) {
|
|
28494
|
+
reject({ reason: "write-failed", detail: err instanceof Error ? err.message : String(err) });
|
|
28495
|
+
}
|
|
28496
|
+
return;
|
|
28497
|
+
}
|
|
28498
|
+
const enqueued = this.queue.enqueue({
|
|
28499
|
+
payload: {
|
|
28500
|
+
line: line.endsWith(LINE_TERMINATOR) ? line : line + LINE_TERMINATOR,
|
|
28501
|
+
resolve: resolve3,
|
|
28502
|
+
reject
|
|
28503
|
+
},
|
|
28504
|
+
onFlush: (entry) => {
|
|
28505
|
+
if (this.socket && !this.socket.destroyed) {
|
|
28506
|
+
try {
|
|
28507
|
+
this.socket.write(entry.line);
|
|
28508
|
+
entry.resolve();
|
|
28509
|
+
} catch (err) {
|
|
28510
|
+
entry.reject({
|
|
28511
|
+
reason: "write-failed",
|
|
28512
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
28513
|
+
});
|
|
28514
|
+
}
|
|
28515
|
+
} else {
|
|
28516
|
+
entry.reject({ reason: "write-failed", detail: "socket unavailable at flush" });
|
|
28517
|
+
}
|
|
28518
|
+
},
|
|
28519
|
+
onReject: (reason) => {
|
|
28520
|
+
reject({ reason });
|
|
28521
|
+
}
|
|
28522
|
+
});
|
|
28523
|
+
if (enqueued) {
|
|
28524
|
+
this.emitMetric("queue.enqueue", { depth: this.queue.size() });
|
|
28525
|
+
}
|
|
28526
|
+
});
|
|
28527
|
+
}
|
|
28528
|
+
connect() {
|
|
28529
|
+
if (!this.started) return;
|
|
28530
|
+
this.cancelReconnect = null;
|
|
28531
|
+
this.apply({ kind: "start" });
|
|
28532
|
+
if (this.state !== "connecting") {
|
|
28533
|
+
this.forceState("connecting");
|
|
28534
|
+
}
|
|
28535
|
+
const sock = createConnection2({ host: this.options.host, port: this.options.port });
|
|
28536
|
+
this.socket = sock;
|
|
28537
|
+
sock.on("connect", () => {
|
|
28538
|
+
this.handshakeStartMs = this.now();
|
|
28539
|
+
sock.setKeepAlive(true, 3e4);
|
|
28540
|
+
this.reconnectAttempt = 0;
|
|
28541
|
+
this.apply({ kind: "socket-connected" });
|
|
28542
|
+
});
|
|
28543
|
+
sock.on("error", (err) => {
|
|
28544
|
+
this.log(`${LOG_PREFIX4} socket error: ${err.message}`);
|
|
28545
|
+
});
|
|
28546
|
+
sock.on("close", () => {
|
|
28547
|
+
const wasReady = this.state === "ready";
|
|
28548
|
+
if (this.socket === sock) this.socket = null;
|
|
28549
|
+
this.apply({ kind: "socket-closed" });
|
|
28550
|
+
if (wasReady) this.log(`${LOG_PREFIX4} bridge disconnected`);
|
|
28551
|
+
if (this.started) this.scheduleReconnect();
|
|
28552
|
+
});
|
|
28553
|
+
let lineBuffer = "";
|
|
28554
|
+
sock.on("data", (data) => {
|
|
28555
|
+
lineBuffer += data.toString("utf-8");
|
|
28556
|
+
const lines = lineBuffer.split(LINE_TERMINATOR);
|
|
28557
|
+
lineBuffer = lines.pop() ?? "";
|
|
28558
|
+
for (const line of lines) {
|
|
28559
|
+
if (!line) continue;
|
|
28560
|
+
try {
|
|
28561
|
+
const parsed = JSON.parse(line);
|
|
28562
|
+
this.handleIncoming(parsed);
|
|
28563
|
+
} catch (err) {
|
|
28564
|
+
this.log(
|
|
28565
|
+
`${LOG_PREFIX4} parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
28566
|
+
);
|
|
28567
|
+
}
|
|
28568
|
+
}
|
|
28569
|
+
});
|
|
28570
|
+
}
|
|
28571
|
+
handleIncoming(msg) {
|
|
28572
|
+
if (msg && typeof msg === "object") {
|
|
28573
|
+
const type = msg.type;
|
|
28574
|
+
if (type === "bridge_closing") {
|
|
28575
|
+
this.log(`${LOG_PREFIX4} received bridge_closing, will reconnect`);
|
|
28576
|
+
this.apply({ kind: "bridge-closing" });
|
|
28577
|
+
if (this.socket && !this.socket.destroyed) this.socket.destroy();
|
|
28578
|
+
return;
|
|
28579
|
+
}
|
|
28580
|
+
if (type === "bridge_status") {
|
|
28581
|
+
const connected = msg.connected === true;
|
|
28582
|
+
this.apply({ kind: connected ? "bridge-status-true" : "bridge-status-false" });
|
|
28583
|
+
}
|
|
28584
|
+
}
|
|
28585
|
+
for (const h of this.messageHandlers) {
|
|
28586
|
+
try {
|
|
28587
|
+
h(msg);
|
|
28588
|
+
} catch (err) {
|
|
28589
|
+
this.log(
|
|
28590
|
+
`${LOG_PREFIX4} handler threw: ${err instanceof Error ? err.message : String(err)}`
|
|
28591
|
+
);
|
|
28592
|
+
}
|
|
28593
|
+
}
|
|
28594
|
+
}
|
|
28595
|
+
scheduleReconnect() {
|
|
28596
|
+
if (!this.started) return;
|
|
28597
|
+
if (this.cancelReconnect) return;
|
|
28598
|
+
this.reconnectAttempt++;
|
|
28599
|
+
const base = Math.min(
|
|
28600
|
+
RECONNECT.INITIAL_DELAY * RECONNECT.MULTIPLIER ** (this.reconnectAttempt - 1),
|
|
28601
|
+
BRIDGE_RECONNECT_CAP_MS
|
|
28602
|
+
);
|
|
28603
|
+
const delay = applyJitter(base, RECONNECT.JITTER_RATIO, this.random);
|
|
28604
|
+
this.emitMetric("reconnect.scheduled", { attempt: this.reconnectAttempt, delayMs: delay });
|
|
28605
|
+
this.log(`${LOG_PREFIX4} reconnect in ${delay}ms (attempt ${this.reconnectAttempt})`);
|
|
28606
|
+
this.cancelReconnect = this.schedule(() => {
|
|
28607
|
+
this.cancelReconnect = null;
|
|
28608
|
+
this.connect();
|
|
28609
|
+
}, delay);
|
|
28610
|
+
}
|
|
28611
|
+
apply(event) {
|
|
28612
|
+
const next = transition(this.state, event);
|
|
28613
|
+
if (next === this.state) return;
|
|
28614
|
+
const prev = this.state;
|
|
28615
|
+
this.state = next;
|
|
28616
|
+
this.emitMetric("state.transition", { from: prev, to: next, event: event.kind });
|
|
28617
|
+
if (next === "ready") {
|
|
28618
|
+
if (this.handshakeStartMs !== null) {
|
|
28619
|
+
this.emitMetric("handshake.complete", {
|
|
28620
|
+
elapsedMs: this.now() - this.handshakeStartMs
|
|
28621
|
+
});
|
|
28622
|
+
this.handshakeStartMs = null;
|
|
28623
|
+
}
|
|
28624
|
+
this.queue.flushAll();
|
|
28625
|
+
}
|
|
28626
|
+
for (const h of this.stateHandlers) {
|
|
28627
|
+
try {
|
|
28628
|
+
h(next, prev);
|
|
28629
|
+
} catch (err) {
|
|
28630
|
+
this.log(
|
|
28631
|
+
`${LOG_PREFIX4} state handler threw: ${err instanceof Error ? err.message : String(err)}`
|
|
28632
|
+
);
|
|
28633
|
+
}
|
|
28634
|
+
}
|
|
28635
|
+
}
|
|
28636
|
+
/** Force a specific state (used only for recovering from impossible transitions). */
|
|
28637
|
+
forceState(to) {
|
|
28638
|
+
if (this.state === to) return;
|
|
28639
|
+
const prev = this.state;
|
|
28640
|
+
this.state = to;
|
|
28641
|
+
this.emitMetric("state.force", { from: prev, to });
|
|
28642
|
+
for (const h of this.stateHandlers) {
|
|
28643
|
+
try {
|
|
28644
|
+
h(to, prev);
|
|
28645
|
+
} catch {
|
|
28646
|
+
}
|
|
28647
|
+
}
|
|
28648
|
+
}
|
|
28649
|
+
emitMetric(name, fields) {
|
|
28650
|
+
this.metric(
|
|
28651
|
+
`${METRIC_PREFIX} ${name} ${Object.entries(fields).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(" ")}`
|
|
28652
|
+
);
|
|
28653
|
+
}
|
|
28654
|
+
};
|
|
28655
|
+
function toolCallRejectionToErrorCode(rej) {
|
|
28656
|
+
switch (rej.reason) {
|
|
28657
|
+
case "queue-full":
|
|
28658
|
+
return {
|
|
28659
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
28660
|
+
message: "Bridge readiness queue is full; try again shortly"
|
|
28661
|
+
};
|
|
28662
|
+
case "timeout":
|
|
28663
|
+
return {
|
|
28664
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
28665
|
+
message: "Timed out waiting for bridge readiness"
|
|
28666
|
+
};
|
|
28667
|
+
case "stopped":
|
|
28668
|
+
return {
|
|
28669
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
28670
|
+
message: "Bridge client was stopped"
|
|
28671
|
+
};
|
|
28672
|
+
case "write-failed":
|
|
28673
|
+
return {
|
|
28674
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
28675
|
+
message: `Socket write failed: ${rej.detail}`
|
|
28676
|
+
};
|
|
28677
|
+
}
|
|
28678
|
+
}
|
|
28679
|
+
|
|
28252
28680
|
// ../shared/dist/bearer-equals.js
|
|
28253
28681
|
import { timingSafeEqual } from "crypto";
|
|
28254
28682
|
function bearerEquals(header, expectedToken) {
|
|
@@ -31488,11 +31916,12 @@ abort:
|
|
|
31488
31916
|
|
|
31489
31917
|
Supported scenario step types: navigate, action, fetch, wait, loop. Loop variable sources: static, previous_fetch, target_options (max nesting 3, max iterations 500, cartesian product across variables).
|
|
31490
31918
|
|
|
31491
|
-
HttpFetch
|
|
31919
|
+
HttpFetch runtime (F2):
|
|
31492
31920
|
- Fetch definitions may carry type_name='http_fetch' + http_config instead of DOM extraction fields. Loop-merged params resolve {{var}} in url / body_template with per-value URL-encoding.
|
|
31493
31921
|
- extractor_code (when set) is eval-equivalent in the page MAIN world \u2014 bundles must originate from the trusted authoring pipeline gated by MCP JWT auth. Extraction precedence: extractor_code > selector > raw responseText.
|
|
31922
|
+
- Response shape: results[result_key] = { rows: unknown[] } \u2014 extractor array, selector string[], or [responseText] in raw fallback. Scraper should consume result.data.rows.
|
|
31923
|
+
- HTTP 4xx/5xx are forwarded as ok:true with httpStatus populated (caller decides). Network / extractor failures surface as ok:false with prefixed error strings ('fetch failed: ...' / 'extractor threw: ...' / 'scripting failed: ...' / 'async extractor not supported in v1').
|
|
31494
31924
|
- sm_probe_inline automatically skips http_fetch (no DOM locators to probe).
|
|
31495
|
-
- Calling an http_fetch at runtime in this release returns an error; runtime dispatch lands in F2.
|
|
31496
31925
|
- ScenarioFetchStep.result_key_mode ('overwrite' | 'append' | 'concat', default 'overwrite') controls how results[result_key] accumulates across loop iterations. Runtime dispatch lands in F3; pre-F3 all modes behave as 'overwrite'.
|
|
31497
31926
|
|
|
31498
31927
|
Full reference: docs/inline-sm-execution.md`;
|
|
@@ -33774,8 +34203,11 @@ function computeToolTimeout(tool, input) {
|
|
|
33774
34203
|
|
|
33775
34204
|
// src/server.ts
|
|
33776
34205
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
33777
|
-
var
|
|
34206
|
+
var bridgeClient = null;
|
|
33778
34207
|
var bridgeUpdateInfo = null;
|
|
34208
|
+
function getBridgeSocket() {
|
|
34209
|
+
return bridgeClient?.getSocket() ?? null;
|
|
34210
|
+
}
|
|
33779
34211
|
function readJwtConfigFromEnv() {
|
|
33780
34212
|
const alg = process.env.VIYV_JWT_ALG ?? "none";
|
|
33781
34213
|
return {
|
|
@@ -33843,8 +34275,9 @@ function bridgeRegisterAgent(agentId, sessionId) {
|
|
|
33843
34275
|
bridgeAgentRefs.set(agentId, set);
|
|
33844
34276
|
}
|
|
33845
34277
|
set.add(sessionId);
|
|
33846
|
-
if (isFirst
|
|
33847
|
-
|
|
34278
|
+
if (isFirst) {
|
|
34279
|
+
const sock = getBridgeSocket();
|
|
34280
|
+
if (sock) sendSessionInit(sock, agentId);
|
|
33848
34281
|
}
|
|
33849
34282
|
}
|
|
33850
34283
|
function bridgeUnregisterAgent(agentId, sessionId) {
|
|
@@ -33853,8 +34286,9 @@ function bridgeUnregisterAgent(agentId, sessionId) {
|
|
|
33853
34286
|
set.delete(sessionId);
|
|
33854
34287
|
if (set.size === 0) {
|
|
33855
34288
|
bridgeAgentRefs.delete(agentId);
|
|
33856
|
-
|
|
33857
|
-
|
|
34289
|
+
const sock = getBridgeSocket();
|
|
34290
|
+
if (sock) {
|
|
34291
|
+
sock.write(
|
|
33858
34292
|
`${JSON.stringify({
|
|
33859
34293
|
id: randomUUID5(),
|
|
33860
34294
|
type: "session_close",
|
|
@@ -34005,7 +34439,8 @@ async function startMcpServer(agentName, options) {
|
|
|
34005
34439
|
setDefaultAgentId(agentName);
|
|
34006
34440
|
}
|
|
34007
34441
|
configuredChromeProfile = options?.chromeProfile;
|
|
34008
|
-
|
|
34442
|
+
bridgeClient = initBridgeClient();
|
|
34443
|
+
bridgeClient.start();
|
|
34009
34444
|
if (options?.transport === "sse") {
|
|
34010
34445
|
const sessions2 = /* @__PURE__ */ new Map();
|
|
34011
34446
|
const httpServer = http.createServer();
|
|
@@ -34043,7 +34478,7 @@ async function startMcpServer(agentName, options) {
|
|
|
34043
34478
|
}
|
|
34044
34479
|
httpServer.close(() => {
|
|
34045
34480
|
});
|
|
34046
|
-
|
|
34481
|
+
bridgeClient?.stop();
|
|
34047
34482
|
process.exit(0);
|
|
34048
34483
|
};
|
|
34049
34484
|
process.on("SIGINT", () => {
|
|
@@ -34118,7 +34553,7 @@ async function startMcpServer(agentName, options) {
|
|
|
34118
34553
|
}
|
|
34119
34554
|
httpServer.close(() => {
|
|
34120
34555
|
});
|
|
34121
|
-
|
|
34556
|
+
bridgeClient?.stop();
|
|
34122
34557
|
process.exit(0);
|
|
34123
34558
|
};
|
|
34124
34559
|
process.on("SIGINT", () => {
|
|
@@ -34137,13 +34572,13 @@ async function startMcpServer(agentName, options) {
|
|
|
34137
34572
|
);
|
|
34138
34573
|
process.stdin.on("end", () => {
|
|
34139
34574
|
process.stderr.write("[viyv-browser:mcp] stdin closed, shutting down\n");
|
|
34140
|
-
|
|
34575
|
+
bridgeClient?.stop();
|
|
34141
34576
|
process.exit(0);
|
|
34142
34577
|
});
|
|
34143
34578
|
process.on("SIGINT", () => process.exit(0));
|
|
34144
34579
|
process.on("SIGTERM", () => process.exit(0));
|
|
34145
34580
|
process.on("exit", () => {
|
|
34146
|
-
|
|
34581
|
+
bridgeClient?.stop();
|
|
34147
34582
|
});
|
|
34148
34583
|
}
|
|
34149
34584
|
}
|
|
@@ -34326,65 +34761,6 @@ function parseJsonBody(req) {
|
|
|
34326
34761
|
});
|
|
34327
34762
|
});
|
|
34328
34763
|
}
|
|
34329
|
-
function connectToBridge() {
|
|
34330
|
-
let retryCount = 0;
|
|
34331
|
-
let bridgeClosingReceived = false;
|
|
34332
|
-
let cleanup = null;
|
|
34333
|
-
function connect() {
|
|
34334
|
-
bridgeClosingReceived = false;
|
|
34335
|
-
const socket = createConnection2({ port: BRIDGE.TCP_PORT, host: BRIDGE.TCP_HOST });
|
|
34336
|
-
socket.on("connect", () => {
|
|
34337
|
-
retryCount = 0;
|
|
34338
|
-
cleanup = setupBridgeConnection(socket, () => {
|
|
34339
|
-
bridgeClosingReceived = true;
|
|
34340
|
-
});
|
|
34341
|
-
});
|
|
34342
|
-
socket.on("error", () => {
|
|
34343
|
-
});
|
|
34344
|
-
socket.on("close", () => {
|
|
34345
|
-
const wasConnected = cleanup !== null;
|
|
34346
|
-
cleanup?.();
|
|
34347
|
-
cleanup = null;
|
|
34348
|
-
if (wasConnected) {
|
|
34349
|
-
process.stderr.write("[viyv-browser:mcp] Bridge disconnected\n");
|
|
34350
|
-
}
|
|
34351
|
-
if (extensionSocket === socket) {
|
|
34352
|
-
extensionSocket = null;
|
|
34353
|
-
setExtensionConnected(false);
|
|
34354
|
-
}
|
|
34355
|
-
for (const [id, pending] of pendingRequests) {
|
|
34356
|
-
clearTimeout(pending.timer);
|
|
34357
|
-
pendingRequests.delete(id);
|
|
34358
|
-
pending.resolve({
|
|
34359
|
-
error: {
|
|
34360
|
-
code: "EXTENSION_NOT_CONNECTED",
|
|
34361
|
-
message: "Bridge disconnected while request was pending"
|
|
34362
|
-
}
|
|
34363
|
-
});
|
|
34364
|
-
}
|
|
34365
|
-
scheduleReconnect();
|
|
34366
|
-
});
|
|
34367
|
-
}
|
|
34368
|
-
function scheduleReconnect() {
|
|
34369
|
-
if (bridgeClosingReceived) {
|
|
34370
|
-
retryCount = 0;
|
|
34371
|
-
setTimeout(connect, RECONNECT.INITIAL_DELAY);
|
|
34372
|
-
return;
|
|
34373
|
-
}
|
|
34374
|
-
retryCount++;
|
|
34375
|
-
const BRIDGE_RECONNECT_CAP = 3e3;
|
|
34376
|
-
const delay = Math.min(
|
|
34377
|
-
RECONNECT.INITIAL_DELAY * RECONNECT.MULTIPLIER ** (retryCount - 1),
|
|
34378
|
-
BRIDGE_RECONNECT_CAP
|
|
34379
|
-
);
|
|
34380
|
-
process.stderr.write(
|
|
34381
|
-
`[viyv-browser:mcp] Reconnecting to bridge in ${delay}ms (attempt ${retryCount})
|
|
34382
|
-
`
|
|
34383
|
-
);
|
|
34384
|
-
setTimeout(connect, delay);
|
|
34385
|
-
}
|
|
34386
|
-
connect();
|
|
34387
|
-
}
|
|
34388
34764
|
function sendSessionInit(socket, agentId) {
|
|
34389
34765
|
const initMsg = {
|
|
34390
34766
|
id: randomUUID5(),
|
|
@@ -34399,65 +34775,79 @@ function sendSessionInit(socket, agentId) {
|
|
|
34399
34775
|
socket.write(`${JSON.stringify(initMsg)}
|
|
34400
34776
|
`);
|
|
34401
34777
|
}
|
|
34402
|
-
function
|
|
34403
|
-
|
|
34404
|
-
|
|
34778
|
+
function initBridgeClient() {
|
|
34779
|
+
const client = new BridgeClient({ host: BRIDGE.TCP_HOST, port: BRIDGE.TCP_PORT });
|
|
34780
|
+
let heartbeatInterval = null;
|
|
34781
|
+
client.onMessage((rawMsg) => {
|
|
34782
|
+
if (!rawMsg || typeof rawMsg !== "object") return;
|
|
34783
|
+
let parsed = rawMsg;
|
|
34784
|
+
if (parsed.type === "compressed" && typeof parsed.data === "string") {
|
|
34785
|
+
try {
|
|
34786
|
+
const decompressed = decompressPayload(parsed.data, true);
|
|
34787
|
+
parsed = JSON.parse(decompressed);
|
|
34788
|
+
} catch (err) {
|
|
34789
|
+
process.stderr.write(
|
|
34790
|
+
`[viyv-browser:mcp] Decompress error: ${err instanceof Error ? err.message : String(err)}
|
|
34405
34791
|
`
|
|
34406
|
-
|
|
34407
|
-
|
|
34408
|
-
|
|
34409
|
-
setExtensionConnected(true);
|
|
34410
|
-
const agentId = getDefaultAgentId();
|
|
34411
|
-
sendSessionInit(socket, agentId);
|
|
34412
|
-
bridgeResyncAgents(socket);
|
|
34413
|
-
const heartbeatInterval = setInterval(() => {
|
|
34414
|
-
if (socket.destroyed) {
|
|
34415
|
-
clearInterval(heartbeatInterval);
|
|
34416
|
-
return;
|
|
34792
|
+
);
|
|
34793
|
+
return;
|
|
34794
|
+
}
|
|
34417
34795
|
}
|
|
34418
|
-
|
|
34419
|
-
|
|
34420
|
-
|
|
34421
|
-
|
|
34422
|
-
|
|
34423
|
-
|
|
34424
|
-
})}
|
|
34796
|
+
handleExtensionMessage(parsed);
|
|
34797
|
+
});
|
|
34798
|
+
client.onStateChange((next, prev) => {
|
|
34799
|
+
if (next === "ready" && prev !== "ready") {
|
|
34800
|
+
process.stderr.write(
|
|
34801
|
+
`[viyv-browser:mcp] Connected to bridge at ${BRIDGE.TCP_HOST}:${BRIDGE.TCP_PORT}
|
|
34425
34802
|
`
|
|
34426
|
-
|
|
34427
|
-
|
|
34428
|
-
|
|
34429
|
-
|
|
34430
|
-
|
|
34431
|
-
|
|
34432
|
-
|
|
34433
|
-
|
|
34434
|
-
|
|
34435
|
-
|
|
34436
|
-
|
|
34437
|
-
|
|
34438
|
-
|
|
34439
|
-
|
|
34440
|
-
|
|
34441
|
-
|
|
34442
|
-
|
|
34443
|
-
|
|
34444
|
-
|
|
34445
|
-
|
|
34446
|
-
|
|
34447
|
-
|
|
34448
|
-
|
|
34449
|
-
|
|
34450
|
-
`
|
|
34803
|
+
);
|
|
34804
|
+
setExtensionConnected(true);
|
|
34805
|
+
const sock = client.getSocket();
|
|
34806
|
+
if (sock) {
|
|
34807
|
+
const agentId = getDefaultAgentId();
|
|
34808
|
+
sendSessionInit(sock, agentId);
|
|
34809
|
+
bridgeResyncAgents(sock);
|
|
34810
|
+
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
34811
|
+
heartbeatInterval = setInterval(() => {
|
|
34812
|
+
const s = client.getSocket();
|
|
34813
|
+
if (!s || s.destroyed) {
|
|
34814
|
+
if (heartbeatInterval) {
|
|
34815
|
+
clearInterval(heartbeatInterval);
|
|
34816
|
+
heartbeatInterval = null;
|
|
34817
|
+
}
|
|
34818
|
+
return;
|
|
34819
|
+
}
|
|
34820
|
+
s.write(
|
|
34821
|
+
`${JSON.stringify({
|
|
34822
|
+
id: randomUUID5(),
|
|
34823
|
+
type: "session_heartbeat",
|
|
34824
|
+
agentId,
|
|
34825
|
+
timestamp: Date.now()
|
|
34826
|
+
})}
|
|
34827
|
+
`
|
|
34828
|
+
);
|
|
34829
|
+
}, TIMEOUTS.HEARTBEAT);
|
|
34830
|
+
}
|
|
34831
|
+
}
|
|
34832
|
+
if (prev === "ready" && next !== "ready") {
|
|
34833
|
+
setExtensionConnected(false);
|
|
34834
|
+
if (heartbeatInterval) {
|
|
34835
|
+
clearInterval(heartbeatInterval);
|
|
34836
|
+
heartbeatInterval = null;
|
|
34837
|
+
}
|
|
34838
|
+
for (const [id, pending] of pendingRequests) {
|
|
34839
|
+
clearTimeout(pending.timer);
|
|
34840
|
+
pendingRequests.delete(id);
|
|
34841
|
+
pending.resolve({
|
|
34842
|
+
error: {
|
|
34843
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
34844
|
+
message: "Bridge disconnected while request was pending"
|
|
34845
|
+
}
|
|
34846
|
+
});
|
|
34451
34847
|
}
|
|
34452
34848
|
}
|
|
34453
34849
|
});
|
|
34454
|
-
|
|
34455
|
-
process.stderr.write(`[viyv-browser:mcp] Bridge socket error: ${error2.message}
|
|
34456
|
-
`);
|
|
34457
|
-
});
|
|
34458
|
-
return () => {
|
|
34459
|
-
clearInterval(heartbeatInterval);
|
|
34460
|
-
};
|
|
34850
|
+
return client;
|
|
34461
34851
|
}
|
|
34462
34852
|
function handleExtensionMessage(message2) {
|
|
34463
34853
|
if (!message2 || typeof message2 !== "object") return;
|
|
@@ -34515,9 +34905,10 @@ function handleExtensionMessage(message2) {
|
|
|
34515
34905
|
} else {
|
|
34516
34906
|
setExtensionConnected(true);
|
|
34517
34907
|
recordHeartbeat();
|
|
34518
|
-
|
|
34908
|
+
const sockForReinit = getBridgeSocket();
|
|
34909
|
+
if (sockForReinit && listSessions().length === 0) {
|
|
34519
34910
|
const agentId = getDefaultAgentId();
|
|
34520
|
-
sendSessionInit(
|
|
34911
|
+
sendSessionInit(sockForReinit, agentId);
|
|
34521
34912
|
process.stderr.write(
|
|
34522
34913
|
`[viyv-browser:mcp] Session re-initialized after Chrome reconnect: ${agentId}
|
|
34523
34914
|
`
|
|
@@ -34533,8 +34924,9 @@ function handleExtensionMessage(message2) {
|
|
|
34533
34924
|
`
|
|
34534
34925
|
);
|
|
34535
34926
|
bridgeUpdateInfo = { from: bridgeVersion, to: serverVersion };
|
|
34536
|
-
|
|
34537
|
-
|
|
34927
|
+
const shutdownSock = getBridgeSocket();
|
|
34928
|
+
if (shutdownSock) {
|
|
34929
|
+
shutdownSock.write(
|
|
34538
34930
|
`${JSON.stringify({ type: "bridge_shutdown_request", timestamp: Date.now() })}
|
|
34539
34931
|
`
|
|
34540
34932
|
);
|
|
@@ -34664,7 +35056,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34664
35056
|
}
|
|
34665
35057
|
if (tool === "browser_health") {
|
|
34666
35058
|
const health = getHealthStatus();
|
|
34667
|
-
if (!health.extensionConnected || !
|
|
35059
|
+
if (!health.extensionConnected || !getBridgeSocket()) {
|
|
34668
35060
|
return {
|
|
34669
35061
|
content: [{ type: "text", text: JSON.stringify(health) }]
|
|
34670
35062
|
};
|
|
@@ -34748,7 +35140,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34748
35140
|
}
|
|
34749
35141
|
}
|
|
34750
35142
|
}
|
|
34751
|
-
if (!
|
|
35143
|
+
if (!bridgeClient) {
|
|
34752
35144
|
return {
|
|
34753
35145
|
content: [
|
|
34754
35146
|
{
|
|
@@ -34756,7 +35148,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34756
35148
|
text: JSON.stringify({
|
|
34757
35149
|
error: {
|
|
34758
35150
|
code: "EXTENSION_NOT_CONNECTED",
|
|
34759
|
-
message: "
|
|
35151
|
+
message: "Bridge client not initialized"
|
|
34760
35152
|
}
|
|
34761
35153
|
})
|
|
34762
35154
|
}
|
|
@@ -34766,31 +35158,10 @@ async function callExtensionTool(tool, input) {
|
|
|
34766
35158
|
const requestId = randomUUID5();
|
|
34767
35159
|
const agentId = getCurrentAgentId(getDefaultAgentId);
|
|
34768
35160
|
touchSession(agentId);
|
|
34769
|
-
const
|
|
35161
|
+
const client = bridgeClient;
|
|
34770
35162
|
const toolTimeout = computeToolTimeout(tool, input);
|
|
34771
35163
|
return new Promise((resolve3) => {
|
|
34772
|
-
const onError = () => {
|
|
34773
|
-
const pending = pendingRequests.get(requestId);
|
|
34774
|
-
if (pending) {
|
|
34775
|
-
clearTimeout(pending.timer);
|
|
34776
|
-
pendingRequests.delete(requestId);
|
|
34777
|
-
resolve3({
|
|
34778
|
-
content: [
|
|
34779
|
-
{
|
|
34780
|
-
type: "text",
|
|
34781
|
-
text: JSON.stringify({
|
|
34782
|
-
error: {
|
|
34783
|
-
code: "EXTENSION_NOT_CONNECTED",
|
|
34784
|
-
message: "Socket write failed"
|
|
34785
|
-
}
|
|
34786
|
-
})
|
|
34787
|
-
}
|
|
34788
|
-
]
|
|
34789
|
-
});
|
|
34790
|
-
}
|
|
34791
|
-
};
|
|
34792
35164
|
const removeErrorListener = () => {
|
|
34793
|
-
sock.removeListener("error", onError);
|
|
34794
35165
|
};
|
|
34795
35166
|
const timer = setTimeout(() => {
|
|
34796
35167
|
pendingRequests.delete(requestId);
|
|
@@ -34936,13 +35307,17 @@ async function callExtensionTool(tool, input) {
|
|
|
34936
35307
|
`[viyv-browser:mcp] tool_call id=${requestId} agent=${agentId} pid=${ctxLog?.claims?.pid ?? chromeProfile ?? "-"} tool=${tool}
|
|
34937
35308
|
`
|
|
34938
35309
|
);
|
|
34939
|
-
|
|
34940
|
-
`)
|
|
34941
|
-
|
|
34942
|
-
|
|
35310
|
+
client.enqueueToolCall(`${JSON.stringify(request)}
|
|
35311
|
+
`).catch((rej) => {
|
|
35312
|
+
const pending = pendingRequests.get(requestId);
|
|
35313
|
+
if (!pending) return;
|
|
35314
|
+
clearTimeout(pending.timer);
|
|
35315
|
+
pendingRequests.delete(requestId);
|
|
35316
|
+
const { code, message: message2 } = toolCallRejectionToErrorCode(rej);
|
|
35317
|
+
resolve3({
|
|
35318
|
+
content: [{ type: "text", text: JSON.stringify({ error: { code, message: message2 } }) }]
|
|
34943
35319
|
});
|
|
34944
|
-
}
|
|
34945
|
-
sock.once("error", onError);
|
|
35320
|
+
});
|
|
34946
35321
|
});
|
|
34947
35322
|
}
|
|
34948
35323
|
async function handleSwitchBrowser() {
|
|
@@ -34957,16 +35332,15 @@ async function handleSwitchBrowser() {
|
|
|
34957
35332
|
}
|
|
34958
35333
|
});
|
|
34959
35334
|
}
|
|
34960
|
-
if (
|
|
35335
|
+
if (bridgeClient) {
|
|
34961
35336
|
process.stderr.write("[viyv-browser:mcp] switch_browser: closing current connection\n");
|
|
34962
|
-
|
|
34963
|
-
extensionSocket = null;
|
|
35337
|
+
bridgeClient.forceReconnect();
|
|
34964
35338
|
setExtensionConnected(false);
|
|
34965
35339
|
}
|
|
34966
35340
|
process.stderr.write("[viyv-browser:mcp] switch_browser: waiting for bridge reconnection...\n");
|
|
34967
35341
|
return new Promise((resolve3) => {
|
|
34968
35342
|
const checkInterval = setInterval(() => {
|
|
34969
|
-
if (
|
|
35343
|
+
if (bridgeClient?.isReady() && isExtensionConnected()) {
|
|
34970
35344
|
clearInterval(checkInterval);
|
|
34971
35345
|
clearTimeout(timer);
|
|
34972
35346
|
resolve3({
|