viyv-browser-mcp 0.10.1 → 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 +549 -176
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
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) {
|
|
@@ -33775,8 +34203,11 @@ function computeToolTimeout(tool, input) {
|
|
|
33775
34203
|
|
|
33776
34204
|
// src/server.ts
|
|
33777
34205
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
33778
|
-
var
|
|
34206
|
+
var bridgeClient = null;
|
|
33779
34207
|
var bridgeUpdateInfo = null;
|
|
34208
|
+
function getBridgeSocket() {
|
|
34209
|
+
return bridgeClient?.getSocket() ?? null;
|
|
34210
|
+
}
|
|
33780
34211
|
function readJwtConfigFromEnv() {
|
|
33781
34212
|
const alg = process.env.VIYV_JWT_ALG ?? "none";
|
|
33782
34213
|
return {
|
|
@@ -33844,8 +34275,9 @@ function bridgeRegisterAgent(agentId, sessionId) {
|
|
|
33844
34275
|
bridgeAgentRefs.set(agentId, set);
|
|
33845
34276
|
}
|
|
33846
34277
|
set.add(sessionId);
|
|
33847
|
-
if (isFirst
|
|
33848
|
-
|
|
34278
|
+
if (isFirst) {
|
|
34279
|
+
const sock = getBridgeSocket();
|
|
34280
|
+
if (sock) sendSessionInit(sock, agentId);
|
|
33849
34281
|
}
|
|
33850
34282
|
}
|
|
33851
34283
|
function bridgeUnregisterAgent(agentId, sessionId) {
|
|
@@ -33854,8 +34286,9 @@ function bridgeUnregisterAgent(agentId, sessionId) {
|
|
|
33854
34286
|
set.delete(sessionId);
|
|
33855
34287
|
if (set.size === 0) {
|
|
33856
34288
|
bridgeAgentRefs.delete(agentId);
|
|
33857
|
-
|
|
33858
|
-
|
|
34289
|
+
const sock = getBridgeSocket();
|
|
34290
|
+
if (sock) {
|
|
34291
|
+
sock.write(
|
|
33859
34292
|
`${JSON.stringify({
|
|
33860
34293
|
id: randomUUID5(),
|
|
33861
34294
|
type: "session_close",
|
|
@@ -34006,7 +34439,8 @@ async function startMcpServer(agentName, options) {
|
|
|
34006
34439
|
setDefaultAgentId(agentName);
|
|
34007
34440
|
}
|
|
34008
34441
|
configuredChromeProfile = options?.chromeProfile;
|
|
34009
|
-
|
|
34442
|
+
bridgeClient = initBridgeClient();
|
|
34443
|
+
bridgeClient.start();
|
|
34010
34444
|
if (options?.transport === "sse") {
|
|
34011
34445
|
const sessions2 = /* @__PURE__ */ new Map();
|
|
34012
34446
|
const httpServer = http.createServer();
|
|
@@ -34044,7 +34478,7 @@ async function startMcpServer(agentName, options) {
|
|
|
34044
34478
|
}
|
|
34045
34479
|
httpServer.close(() => {
|
|
34046
34480
|
});
|
|
34047
|
-
|
|
34481
|
+
bridgeClient?.stop();
|
|
34048
34482
|
process.exit(0);
|
|
34049
34483
|
};
|
|
34050
34484
|
process.on("SIGINT", () => {
|
|
@@ -34119,7 +34553,7 @@ async function startMcpServer(agentName, options) {
|
|
|
34119
34553
|
}
|
|
34120
34554
|
httpServer.close(() => {
|
|
34121
34555
|
});
|
|
34122
|
-
|
|
34556
|
+
bridgeClient?.stop();
|
|
34123
34557
|
process.exit(0);
|
|
34124
34558
|
};
|
|
34125
34559
|
process.on("SIGINT", () => {
|
|
@@ -34138,13 +34572,13 @@ async function startMcpServer(agentName, options) {
|
|
|
34138
34572
|
);
|
|
34139
34573
|
process.stdin.on("end", () => {
|
|
34140
34574
|
process.stderr.write("[viyv-browser:mcp] stdin closed, shutting down\n");
|
|
34141
|
-
|
|
34575
|
+
bridgeClient?.stop();
|
|
34142
34576
|
process.exit(0);
|
|
34143
34577
|
});
|
|
34144
34578
|
process.on("SIGINT", () => process.exit(0));
|
|
34145
34579
|
process.on("SIGTERM", () => process.exit(0));
|
|
34146
34580
|
process.on("exit", () => {
|
|
34147
|
-
|
|
34581
|
+
bridgeClient?.stop();
|
|
34148
34582
|
});
|
|
34149
34583
|
}
|
|
34150
34584
|
}
|
|
@@ -34327,65 +34761,6 @@ function parseJsonBody(req) {
|
|
|
34327
34761
|
});
|
|
34328
34762
|
});
|
|
34329
34763
|
}
|
|
34330
|
-
function connectToBridge() {
|
|
34331
|
-
let retryCount = 0;
|
|
34332
|
-
let bridgeClosingReceived = false;
|
|
34333
|
-
let cleanup = null;
|
|
34334
|
-
function connect() {
|
|
34335
|
-
bridgeClosingReceived = false;
|
|
34336
|
-
const socket = createConnection2({ port: BRIDGE.TCP_PORT, host: BRIDGE.TCP_HOST });
|
|
34337
|
-
socket.on("connect", () => {
|
|
34338
|
-
retryCount = 0;
|
|
34339
|
-
cleanup = setupBridgeConnection(socket, () => {
|
|
34340
|
-
bridgeClosingReceived = true;
|
|
34341
|
-
});
|
|
34342
|
-
});
|
|
34343
|
-
socket.on("error", () => {
|
|
34344
|
-
});
|
|
34345
|
-
socket.on("close", () => {
|
|
34346
|
-
const wasConnected = cleanup !== null;
|
|
34347
|
-
cleanup?.();
|
|
34348
|
-
cleanup = null;
|
|
34349
|
-
if (wasConnected) {
|
|
34350
|
-
process.stderr.write("[viyv-browser:mcp] Bridge disconnected\n");
|
|
34351
|
-
}
|
|
34352
|
-
if (extensionSocket === socket) {
|
|
34353
|
-
extensionSocket = null;
|
|
34354
|
-
setExtensionConnected(false);
|
|
34355
|
-
}
|
|
34356
|
-
for (const [id, pending] of pendingRequests) {
|
|
34357
|
-
clearTimeout(pending.timer);
|
|
34358
|
-
pendingRequests.delete(id);
|
|
34359
|
-
pending.resolve({
|
|
34360
|
-
error: {
|
|
34361
|
-
code: "EXTENSION_NOT_CONNECTED",
|
|
34362
|
-
message: "Bridge disconnected while request was pending"
|
|
34363
|
-
}
|
|
34364
|
-
});
|
|
34365
|
-
}
|
|
34366
|
-
scheduleReconnect();
|
|
34367
|
-
});
|
|
34368
|
-
}
|
|
34369
|
-
function scheduleReconnect() {
|
|
34370
|
-
if (bridgeClosingReceived) {
|
|
34371
|
-
retryCount = 0;
|
|
34372
|
-
setTimeout(connect, RECONNECT.INITIAL_DELAY);
|
|
34373
|
-
return;
|
|
34374
|
-
}
|
|
34375
|
-
retryCount++;
|
|
34376
|
-
const BRIDGE_RECONNECT_CAP = 3e3;
|
|
34377
|
-
const delay = Math.min(
|
|
34378
|
-
RECONNECT.INITIAL_DELAY * RECONNECT.MULTIPLIER ** (retryCount - 1),
|
|
34379
|
-
BRIDGE_RECONNECT_CAP
|
|
34380
|
-
);
|
|
34381
|
-
process.stderr.write(
|
|
34382
|
-
`[viyv-browser:mcp] Reconnecting to bridge in ${delay}ms (attempt ${retryCount})
|
|
34383
|
-
`
|
|
34384
|
-
);
|
|
34385
|
-
setTimeout(connect, delay);
|
|
34386
|
-
}
|
|
34387
|
-
connect();
|
|
34388
|
-
}
|
|
34389
34764
|
function sendSessionInit(socket, agentId) {
|
|
34390
34765
|
const initMsg = {
|
|
34391
34766
|
id: randomUUID5(),
|
|
@@ -34400,65 +34775,79 @@ function sendSessionInit(socket, agentId) {
|
|
|
34400
34775
|
socket.write(`${JSON.stringify(initMsg)}
|
|
34401
34776
|
`);
|
|
34402
34777
|
}
|
|
34403
|
-
function
|
|
34404
|
-
|
|
34405
|
-
|
|
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)}
|
|
34406
34791
|
`
|
|
34407
|
-
|
|
34408
|
-
|
|
34409
|
-
|
|
34410
|
-
setExtensionConnected(true);
|
|
34411
|
-
const agentId = getDefaultAgentId();
|
|
34412
|
-
sendSessionInit(socket, agentId);
|
|
34413
|
-
bridgeResyncAgents(socket);
|
|
34414
|
-
const heartbeatInterval = setInterval(() => {
|
|
34415
|
-
if (socket.destroyed) {
|
|
34416
|
-
clearInterval(heartbeatInterval);
|
|
34417
|
-
return;
|
|
34792
|
+
);
|
|
34793
|
+
return;
|
|
34794
|
+
}
|
|
34418
34795
|
}
|
|
34419
|
-
|
|
34420
|
-
|
|
34421
|
-
|
|
34422
|
-
|
|
34423
|
-
|
|
34424
|
-
|
|
34425
|
-
})}
|
|
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}
|
|
34426
34802
|
`
|
|
34427
|
-
|
|
34428
|
-
|
|
34429
|
-
|
|
34430
|
-
|
|
34431
|
-
|
|
34432
|
-
|
|
34433
|
-
|
|
34434
|
-
|
|
34435
|
-
|
|
34436
|
-
|
|
34437
|
-
|
|
34438
|
-
|
|
34439
|
-
|
|
34440
|
-
|
|
34441
|
-
|
|
34442
|
-
|
|
34443
|
-
|
|
34444
|
-
|
|
34445
|
-
|
|
34446
|
-
|
|
34447
|
-
|
|
34448
|
-
|
|
34449
|
-
|
|
34450
|
-
|
|
34451
|
-
`
|
|
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
|
+
});
|
|
34452
34847
|
}
|
|
34453
34848
|
}
|
|
34454
34849
|
});
|
|
34455
|
-
|
|
34456
|
-
process.stderr.write(`[viyv-browser:mcp] Bridge socket error: ${error2.message}
|
|
34457
|
-
`);
|
|
34458
|
-
});
|
|
34459
|
-
return () => {
|
|
34460
|
-
clearInterval(heartbeatInterval);
|
|
34461
|
-
};
|
|
34850
|
+
return client;
|
|
34462
34851
|
}
|
|
34463
34852
|
function handleExtensionMessage(message2) {
|
|
34464
34853
|
if (!message2 || typeof message2 !== "object") return;
|
|
@@ -34516,9 +34905,10 @@ function handleExtensionMessage(message2) {
|
|
|
34516
34905
|
} else {
|
|
34517
34906
|
setExtensionConnected(true);
|
|
34518
34907
|
recordHeartbeat();
|
|
34519
|
-
|
|
34908
|
+
const sockForReinit = getBridgeSocket();
|
|
34909
|
+
if (sockForReinit && listSessions().length === 0) {
|
|
34520
34910
|
const agentId = getDefaultAgentId();
|
|
34521
|
-
sendSessionInit(
|
|
34911
|
+
sendSessionInit(sockForReinit, agentId);
|
|
34522
34912
|
process.stderr.write(
|
|
34523
34913
|
`[viyv-browser:mcp] Session re-initialized after Chrome reconnect: ${agentId}
|
|
34524
34914
|
`
|
|
@@ -34534,8 +34924,9 @@ function handleExtensionMessage(message2) {
|
|
|
34534
34924
|
`
|
|
34535
34925
|
);
|
|
34536
34926
|
bridgeUpdateInfo = { from: bridgeVersion, to: serverVersion };
|
|
34537
|
-
|
|
34538
|
-
|
|
34927
|
+
const shutdownSock = getBridgeSocket();
|
|
34928
|
+
if (shutdownSock) {
|
|
34929
|
+
shutdownSock.write(
|
|
34539
34930
|
`${JSON.stringify({ type: "bridge_shutdown_request", timestamp: Date.now() })}
|
|
34540
34931
|
`
|
|
34541
34932
|
);
|
|
@@ -34665,7 +35056,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34665
35056
|
}
|
|
34666
35057
|
if (tool === "browser_health") {
|
|
34667
35058
|
const health = getHealthStatus();
|
|
34668
|
-
if (!health.extensionConnected || !
|
|
35059
|
+
if (!health.extensionConnected || !getBridgeSocket()) {
|
|
34669
35060
|
return {
|
|
34670
35061
|
content: [{ type: "text", text: JSON.stringify(health) }]
|
|
34671
35062
|
};
|
|
@@ -34749,7 +35140,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34749
35140
|
}
|
|
34750
35141
|
}
|
|
34751
35142
|
}
|
|
34752
|
-
if (!
|
|
35143
|
+
if (!bridgeClient) {
|
|
34753
35144
|
return {
|
|
34754
35145
|
content: [
|
|
34755
35146
|
{
|
|
@@ -34757,7 +35148,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34757
35148
|
text: JSON.stringify({
|
|
34758
35149
|
error: {
|
|
34759
35150
|
code: "EXTENSION_NOT_CONNECTED",
|
|
34760
|
-
message: "
|
|
35151
|
+
message: "Bridge client not initialized"
|
|
34761
35152
|
}
|
|
34762
35153
|
})
|
|
34763
35154
|
}
|
|
@@ -34767,31 +35158,10 @@ async function callExtensionTool(tool, input) {
|
|
|
34767
35158
|
const requestId = randomUUID5();
|
|
34768
35159
|
const agentId = getCurrentAgentId(getDefaultAgentId);
|
|
34769
35160
|
touchSession(agentId);
|
|
34770
|
-
const
|
|
35161
|
+
const client = bridgeClient;
|
|
34771
35162
|
const toolTimeout = computeToolTimeout(tool, input);
|
|
34772
35163
|
return new Promise((resolve3) => {
|
|
34773
|
-
const onError = () => {
|
|
34774
|
-
const pending = pendingRequests.get(requestId);
|
|
34775
|
-
if (pending) {
|
|
34776
|
-
clearTimeout(pending.timer);
|
|
34777
|
-
pendingRequests.delete(requestId);
|
|
34778
|
-
resolve3({
|
|
34779
|
-
content: [
|
|
34780
|
-
{
|
|
34781
|
-
type: "text",
|
|
34782
|
-
text: JSON.stringify({
|
|
34783
|
-
error: {
|
|
34784
|
-
code: "EXTENSION_NOT_CONNECTED",
|
|
34785
|
-
message: "Socket write failed"
|
|
34786
|
-
}
|
|
34787
|
-
})
|
|
34788
|
-
}
|
|
34789
|
-
]
|
|
34790
|
-
});
|
|
34791
|
-
}
|
|
34792
|
-
};
|
|
34793
35164
|
const removeErrorListener = () => {
|
|
34794
|
-
sock.removeListener("error", onError);
|
|
34795
35165
|
};
|
|
34796
35166
|
const timer = setTimeout(() => {
|
|
34797
35167
|
pendingRequests.delete(requestId);
|
|
@@ -34937,13 +35307,17 @@ async function callExtensionTool(tool, input) {
|
|
|
34937
35307
|
`[viyv-browser:mcp] tool_call id=${requestId} agent=${agentId} pid=${ctxLog?.claims?.pid ?? chromeProfile ?? "-"} tool=${tool}
|
|
34938
35308
|
`
|
|
34939
35309
|
);
|
|
34940
|
-
|
|
34941
|
-
`)
|
|
34942
|
-
|
|
34943
|
-
|
|
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 } }) }]
|
|
34944
35319
|
});
|
|
34945
|
-
}
|
|
34946
|
-
sock.once("error", onError);
|
|
35320
|
+
});
|
|
34947
35321
|
});
|
|
34948
35322
|
}
|
|
34949
35323
|
async function handleSwitchBrowser() {
|
|
@@ -34958,16 +35332,15 @@ async function handleSwitchBrowser() {
|
|
|
34958
35332
|
}
|
|
34959
35333
|
});
|
|
34960
35334
|
}
|
|
34961
|
-
if (
|
|
35335
|
+
if (bridgeClient) {
|
|
34962
35336
|
process.stderr.write("[viyv-browser:mcp] switch_browser: closing current connection\n");
|
|
34963
|
-
|
|
34964
|
-
extensionSocket = null;
|
|
35337
|
+
bridgeClient.forceReconnect();
|
|
34965
35338
|
setExtensionConnected(false);
|
|
34966
35339
|
}
|
|
34967
35340
|
process.stderr.write("[viyv-browser:mcp] switch_browser: waiting for bridge reconnection...\n");
|
|
34968
35341
|
return new Promise((resolve3) => {
|
|
34969
35342
|
const checkInterval = setInterval(() => {
|
|
34970
|
-
if (
|
|
35343
|
+
if (bridgeClient?.isReady() && isExtensionConnected()) {
|
|
34971
35344
|
clearInterval(checkInterval);
|
|
34972
35345
|
clearTimeout(timer);
|
|
34973
35346
|
resolve3({
|