viyv-browser-mcp 0.10.1 → 0.10.3
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 +777 -281
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
package/dist/index.js
CHANGED
|
@@ -2988,7 +2988,7 @@ var require_compile = __commonJS({
|
|
|
2988
2988
|
const schOrFunc = root.refs[ref];
|
|
2989
2989
|
if (schOrFunc)
|
|
2990
2990
|
return schOrFunc;
|
|
2991
|
-
let _sch =
|
|
2991
|
+
let _sch = resolve4.call(this, root, ref);
|
|
2992
2992
|
if (_sch === void 0) {
|
|
2993
2993
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
2994
2994
|
const { schemaId } = this.opts;
|
|
@@ -3015,7 +3015,7 @@ var require_compile = __commonJS({
|
|
|
3015
3015
|
function sameSchemaEnv(s1, s2) {
|
|
3016
3016
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3017
3017
|
}
|
|
3018
|
-
function
|
|
3018
|
+
function resolve4(root, ref) {
|
|
3019
3019
|
let sch;
|
|
3020
3020
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3021
3021
|
ref = sch;
|
|
@@ -3590,7 +3590,7 @@ var require_fast_uri = __commonJS({
|
|
|
3590
3590
|
}
|
|
3591
3591
|
return uri;
|
|
3592
3592
|
}
|
|
3593
|
-
function
|
|
3593
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
3594
3594
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3595
3595
|
const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
3596
3596
|
schemelessOptions.skipEscape = true;
|
|
@@ -3817,7 +3817,7 @@ var require_fast_uri = __commonJS({
|
|
|
3817
3817
|
var fastUri = {
|
|
3818
3818
|
SCHEMES,
|
|
3819
3819
|
normalize: normalize2,
|
|
3820
|
-
resolve:
|
|
3820
|
+
resolve: resolve4,
|
|
3821
3821
|
resolveComponent,
|
|
3822
3822
|
equal,
|
|
3823
3823
|
serialize,
|
|
@@ -11452,10 +11452,10 @@ var require_raw_body = __commonJS({
|
|
|
11452
11452
|
if (done) {
|
|
11453
11453
|
return readStream(stream, encoding, length, limit, wrap(done));
|
|
11454
11454
|
}
|
|
11455
|
-
return new Promise(function executor(
|
|
11455
|
+
return new Promise(function executor(resolve4, reject) {
|
|
11456
11456
|
readStream(stream, encoding, length, limit, function onRead(err, buf) {
|
|
11457
11457
|
if (err) return reject(err);
|
|
11458
|
-
|
|
11458
|
+
resolve4(buf);
|
|
11459
11459
|
});
|
|
11460
11460
|
});
|
|
11461
11461
|
}
|
|
@@ -11695,7 +11695,7 @@ var require_content_type = __commonJS({
|
|
|
11695
11695
|
|
|
11696
11696
|
// src/native-host/bridge.ts
|
|
11697
11697
|
import { randomUUID } from "crypto";
|
|
11698
|
-
import { readFileSync as
|
|
11698
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
11699
11699
|
import { createServer as createServer2 } from "net";
|
|
11700
11700
|
import { join as join2 } from "path";
|
|
11701
11701
|
|
|
@@ -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,59 +11811,185 @@ 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
|
+
|
|
11843
|
+
// ../shared/dist/util/semver.js
|
|
11844
|
+
function compareSemver(a, b) {
|
|
11845
|
+
const pa = a.split(".").map((s) => Number.parseInt(s, 10));
|
|
11846
|
+
const pb = b.split(".").map((s) => Number.parseInt(s, 10));
|
|
11847
|
+
for (let i = 0; i < 3; i++) {
|
|
11848
|
+
const ai = Number.isFinite(pa[i]) ? pa[i] : 0;
|
|
11849
|
+
const bi = Number.isFinite(pb[i]) ? pb[i] : 0;
|
|
11850
|
+
const diff = ai - bi;
|
|
11851
|
+
if (diff !== 0)
|
|
11852
|
+
return diff;
|
|
11853
|
+
}
|
|
11854
|
+
return 0;
|
|
11855
|
+
}
|
|
11856
|
+
|
|
11806
11857
|
// src/native-host-updater.ts
|
|
11807
|
-
import {
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
renameSync,
|
|
11814
|
-
unlinkSync
|
|
11815
|
-
} from "fs";
|
|
11858
|
+
import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
11859
|
+
import { resolve as resolve2 } from "path";
|
|
11860
|
+
import { fileURLToPath } from "url";
|
|
11861
|
+
|
|
11862
|
+
// src/native-host/deployment-state.ts
|
|
11863
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
11816
11864
|
import { homedir } from "os";
|
|
11817
11865
|
import { resolve } from "path";
|
|
11818
|
-
|
|
11866
|
+
function getNativeHostDir() {
|
|
11867
|
+
return resolve(homedir(), ".viyv-browser", "native-host");
|
|
11868
|
+
}
|
|
11869
|
+
function getDeploymentBinaryPath() {
|
|
11870
|
+
return resolve(getNativeHostDir(), "index.js");
|
|
11871
|
+
}
|
|
11872
|
+
function getDeploymentVersionPath() {
|
|
11873
|
+
return resolve(getNativeHostDir(), "version");
|
|
11874
|
+
}
|
|
11875
|
+
var SEMVER_CORE = /^\d+\.\d+\.\d+/;
|
|
11876
|
+
function readDeploymentState() {
|
|
11877
|
+
const versionPath = getDeploymentVersionPath();
|
|
11878
|
+
if (!existsSync(versionPath)) {
|
|
11879
|
+
return { version: null };
|
|
11880
|
+
}
|
|
11881
|
+
try {
|
|
11882
|
+
const raw = readFileSync(versionPath, "utf-8").trim();
|
|
11883
|
+
if (!raw || !SEMVER_CORE.test(raw)) {
|
|
11884
|
+
return { version: null };
|
|
11885
|
+
}
|
|
11886
|
+
return { version: raw };
|
|
11887
|
+
} catch {
|
|
11888
|
+
return { version: null };
|
|
11889
|
+
}
|
|
11890
|
+
}
|
|
11891
|
+
function writeDeploymentVersion(version2) {
|
|
11892
|
+
const dir = getNativeHostDir();
|
|
11893
|
+
mkdirSync(dir, { recursive: true });
|
|
11894
|
+
const target = getDeploymentVersionPath();
|
|
11895
|
+
const tmp = resolve(dir, `version.tmp.${process.pid}`);
|
|
11896
|
+
try {
|
|
11897
|
+
writeFileSync(tmp, `${version2}
|
|
11898
|
+
`, "utf-8");
|
|
11899
|
+
renameSync(tmp, target);
|
|
11900
|
+
} catch (err) {
|
|
11901
|
+
try {
|
|
11902
|
+
unlinkSync(tmp);
|
|
11903
|
+
} catch {
|
|
11904
|
+
}
|
|
11905
|
+
throw err;
|
|
11906
|
+
}
|
|
11907
|
+
}
|
|
11908
|
+
|
|
11909
|
+
// src/native-host/sync-policy.ts
|
|
11910
|
+
function decideSyncAction(state, selfVersion) {
|
|
11911
|
+
if (state.version === null) {
|
|
11912
|
+
return {
|
|
11913
|
+
shouldSync: true,
|
|
11914
|
+
reason: "no-deploy-version",
|
|
11915
|
+
deployedVersion: null,
|
|
11916
|
+
selfVersion
|
|
11917
|
+
};
|
|
11918
|
+
}
|
|
11919
|
+
const cmp = compareSemver(selfVersion, state.version);
|
|
11920
|
+
if (cmp > 0) {
|
|
11921
|
+
return {
|
|
11922
|
+
shouldSync: true,
|
|
11923
|
+
reason: "upgrade",
|
|
11924
|
+
deployedVersion: state.version,
|
|
11925
|
+
selfVersion
|
|
11926
|
+
};
|
|
11927
|
+
}
|
|
11928
|
+
if (cmp === 0) {
|
|
11929
|
+
return {
|
|
11930
|
+
shouldSync: true,
|
|
11931
|
+
reason: "same-version-idempotent",
|
|
11932
|
+
deployedVersion: state.version,
|
|
11933
|
+
selfVersion
|
|
11934
|
+
};
|
|
11935
|
+
}
|
|
11936
|
+
return {
|
|
11937
|
+
shouldSync: false,
|
|
11938
|
+
reason: "avoid-downgrade",
|
|
11939
|
+
deployedVersion: state.version,
|
|
11940
|
+
selfVersion
|
|
11941
|
+
};
|
|
11942
|
+
}
|
|
11943
|
+
|
|
11944
|
+
// src/native-host-updater.ts
|
|
11819
11945
|
var PKG_VERSION = (() => {
|
|
11820
|
-
if (true) return "0.10.
|
|
11946
|
+
if (true) return "0.10.3";
|
|
11821
11947
|
try {
|
|
11822
11948
|
const here = fileURLToPath(import.meta.url);
|
|
11823
|
-
const pkgPath =
|
|
11824
|
-
const pkg = JSON.parse(
|
|
11949
|
+
const pkgPath = resolve2(here, "..", "..", "package.json");
|
|
11950
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
11825
11951
|
return pkg.version ?? "0.0.0-test";
|
|
11826
11952
|
} catch {
|
|
11827
11953
|
return "0.0.0-test";
|
|
11828
11954
|
}
|
|
11829
11955
|
})();
|
|
11830
|
-
var NATIVE_HOST_DIR = resolve(homedir(), ".viyv-browser", "native-host");
|
|
11831
11956
|
function getSourceBinaryPath() {
|
|
11832
11957
|
return fileURLToPath(import.meta.url);
|
|
11833
11958
|
}
|
|
11834
11959
|
function getNativeHostBinaryPath() {
|
|
11835
|
-
return
|
|
11960
|
+
return getDeploymentBinaryPath();
|
|
11836
11961
|
}
|
|
11837
11962
|
function isNativeHostDeployed() {
|
|
11838
|
-
return
|
|
11963
|
+
return existsSync2(getNativeHostBinaryPath());
|
|
11839
11964
|
}
|
|
11840
11965
|
function syncNativeHostBinary() {
|
|
11841
11966
|
const source = getSourceBinaryPath();
|
|
11842
|
-
if (!
|
|
11967
|
+
if (!existsSync2(source)) {
|
|
11843
11968
|
throw new Error(`Source binary not found: ${source}`);
|
|
11844
11969
|
}
|
|
11845
|
-
|
|
11970
|
+
const dir = getNativeHostDir();
|
|
11971
|
+
mkdirSync2(dir, { recursive: true });
|
|
11846
11972
|
const target = getNativeHostBinaryPath();
|
|
11847
|
-
const tmp =
|
|
11973
|
+
const tmp = resolve2(dir, `index.js.tmp.${process.pid}`);
|
|
11848
11974
|
try {
|
|
11849
11975
|
copyFileSync(source, tmp);
|
|
11850
11976
|
chmodSync(tmp, 493);
|
|
11851
|
-
|
|
11977
|
+
renameSync2(tmp, target);
|
|
11852
11978
|
} catch (err) {
|
|
11853
11979
|
try {
|
|
11854
|
-
|
|
11980
|
+
unlinkSync2(tmp);
|
|
11855
11981
|
} catch {
|
|
11856
11982
|
}
|
|
11857
11983
|
throw err;
|
|
11858
11984
|
}
|
|
11985
|
+
writeDeploymentVersion(PKG_VERSION);
|
|
11986
|
+
}
|
|
11987
|
+
function syncNativeHostBinaryIfNeeded() {
|
|
11988
|
+
const decision = decideSyncAction(readDeploymentState(), PKG_VERSION);
|
|
11989
|
+
if (decision.shouldSync) {
|
|
11990
|
+
syncNativeHostBinary();
|
|
11991
|
+
}
|
|
11992
|
+
return decision;
|
|
11859
11993
|
}
|
|
11860
11994
|
function shouldUpdateBridge(bridgeVersion) {
|
|
11861
11995
|
return compareSemver(PKG_VERSION, bridgeVersion) > 0;
|
|
@@ -11863,20 +11997,11 @@ function shouldUpdateBridge(bridgeVersion) {
|
|
|
11863
11997
|
function getPackageVersion() {
|
|
11864
11998
|
return PKG_VERSION;
|
|
11865
11999
|
}
|
|
11866
|
-
function compareSemver(a, b) {
|
|
11867
|
-
const pa = a.split(".").map(Number);
|
|
11868
|
-
const pb = b.split(".").map(Number);
|
|
11869
|
-
for (let i = 0; i < 3; i++) {
|
|
11870
|
-
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
11871
|
-
if (diff !== 0) return diff;
|
|
11872
|
-
}
|
|
11873
|
-
return 0;
|
|
11874
|
-
}
|
|
11875
12000
|
|
|
11876
12001
|
// src/native-host/bridge-control.ts
|
|
11877
12002
|
import { randomBytes } from "crypto";
|
|
11878
12003
|
import { createServer } from "net";
|
|
11879
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
12004
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
11880
12005
|
import { dirname } from "path";
|
|
11881
12006
|
var LOG_PREFIX = "[viyv-browser:native-host:control]";
|
|
11882
12007
|
function startBridgeControl(opts) {
|
|
@@ -11886,9 +12011,9 @@ function startBridgeControl(opts) {
|
|
|
11886
12011
|
const { deps, onError } = opts;
|
|
11887
12012
|
let cookie;
|
|
11888
12013
|
try {
|
|
11889
|
-
|
|
12014
|
+
mkdirSync3(dirname(cookiePath), { recursive: true });
|
|
11890
12015
|
cookie = randomBytes(BRIDGE.CONTROL_COOKIE_BYTES);
|
|
11891
|
-
|
|
12016
|
+
writeFileSync2(cookiePath, cookie.toString("hex"), { mode: 384 });
|
|
11892
12017
|
try {
|
|
11893
12018
|
chmodSync2(cookiePath, 384);
|
|
11894
12019
|
} catch {
|
|
@@ -12026,7 +12151,7 @@ function startBridgeControl(opts) {
|
|
|
12026
12151
|
}
|
|
12027
12152
|
|
|
12028
12153
|
// src/native-host/bridge-relay.ts
|
|
12029
|
-
import { readFileSync as
|
|
12154
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
12030
12155
|
import { createConnection } from "net";
|
|
12031
12156
|
import { join } from "path";
|
|
12032
12157
|
|
|
@@ -12128,7 +12253,7 @@ function readProfileIdFromEnv() {
|
|
|
12128
12253
|
const dir = process.env.VIYV_USER_DATA_DIR;
|
|
12129
12254
|
if (!dir) return null;
|
|
12130
12255
|
try {
|
|
12131
|
-
const raw =
|
|
12256
|
+
const raw = readFileSync3(join(dir, "viyv-profile.id"), "utf8").trim();
|
|
12132
12257
|
return raw || null;
|
|
12133
12258
|
} catch {
|
|
12134
12259
|
return null;
|
|
@@ -12460,7 +12585,7 @@ function startBridge(options) {
|
|
|
12460
12585
|
const DRAIN_TIMEOUT = 5e3;
|
|
12461
12586
|
const drainAndExit = () => {
|
|
12462
12587
|
if (requestOrigin.size === 0) {
|
|
12463
|
-
gracefulExit("
|
|
12588
|
+
gracefulExit("shutdown-request", "version update");
|
|
12464
12589
|
return;
|
|
12465
12590
|
}
|
|
12466
12591
|
process.stderr.write(
|
|
@@ -12471,7 +12596,7 @@ function startBridge(options) {
|
|
|
12471
12596
|
const check2 = setInterval(() => {
|
|
12472
12597
|
if (requestOrigin.size === 0 || Date.now() - start > DRAIN_TIMEOUT) {
|
|
12473
12598
|
clearInterval(check2);
|
|
12474
|
-
gracefulExit("
|
|
12599
|
+
gracefulExit("shutdown-request", "version update");
|
|
12475
12600
|
}
|
|
12476
12601
|
}, 200);
|
|
12477
12602
|
};
|
|
@@ -12536,6 +12661,7 @@ function startBridge(options) {
|
|
|
12536
12661
|
} else if (type === "browser_event") {
|
|
12537
12662
|
const agentId = message2.agentId;
|
|
12538
12663
|
if (agentId) broadcastToAgent(agentId, message2);
|
|
12664
|
+
} else if (type === "sw_keepalive") {
|
|
12539
12665
|
} else {
|
|
12540
12666
|
process.stderr.write(`${LOG_PREFIX3} Unknown message type from Chrome: ${type}, dropping
|
|
12541
12667
|
`);
|
|
@@ -12565,19 +12691,22 @@ function startBridge(options) {
|
|
|
12565
12691
|
let primaryStdinClosed = false;
|
|
12566
12692
|
const ORPHAN_TIMEOUT = 6e4;
|
|
12567
12693
|
let orphanTimer = null;
|
|
12568
|
-
function gracefulExit(reason) {
|
|
12569
|
-
|
|
12694
|
+
function gracefulExit(reason, detail) {
|
|
12695
|
+
const suffix = detail ? ` (${detail})` : "";
|
|
12696
|
+
process.stderr.write(`${LOG_PREFIX3} exiting reason=${reason}${suffix}
|
|
12570
12697
|
`);
|
|
12571
12698
|
if (orphanTimer) {
|
|
12572
12699
|
clearTimeout(orphanTimer);
|
|
12573
12700
|
orphanTimer = null;
|
|
12574
12701
|
}
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12702
|
+
if (shouldBroadcastClosing(reason)) {
|
|
12703
|
+
const closingMsg = JSON.stringify({ type: "bridge_closing", timestamp: Date.now() });
|
|
12704
|
+
for (const [, conn] of mcpConnections) {
|
|
12705
|
+
try {
|
|
12706
|
+
conn.socket.write(`${closingMsg}
|
|
12579
12707
|
`);
|
|
12580
|
-
|
|
12708
|
+
} catch {
|
|
12709
|
+
}
|
|
12581
12710
|
}
|
|
12582
12711
|
}
|
|
12583
12712
|
for (const [, conn] of mcpConnections) {
|
|
@@ -12603,7 +12732,7 @@ function startBridge(options) {
|
|
|
12603
12732
|
return;
|
|
12604
12733
|
}
|
|
12605
12734
|
if (chromeConnections.size === 0 && mcpConnections.size === 0) {
|
|
12606
|
-
gracefulExit("
|
|
12735
|
+
gracefulExit("all-empty", "no connections remaining");
|
|
12607
12736
|
}
|
|
12608
12737
|
if (chromeConnections.size === 0 && mcpConnections.size > 0 && !orphanTimer) {
|
|
12609
12738
|
process.stderr.write(
|
|
@@ -12800,7 +12929,7 @@ function startBridge(options) {
|
|
|
12800
12929
|
const primaryUserDataDir = process.env.VIYV_USER_DATA_DIR;
|
|
12801
12930
|
if (primaryUserDataDir) {
|
|
12802
12931
|
try {
|
|
12803
|
-
const raw =
|
|
12932
|
+
const raw = readFileSync4(join2(primaryUserDataDir, "viyv-profile.id"), "utf8").trim();
|
|
12804
12933
|
if (raw) {
|
|
12805
12934
|
setProfileId(primaryChromeId, raw);
|
|
12806
12935
|
process.stderr.write(
|
|
@@ -12819,8 +12948,8 @@ function startBridge(options) {
|
|
|
12819
12948
|
onError: (e) => onError?.(e)
|
|
12820
12949
|
});
|
|
12821
12950
|
notifyChromeConnected(false);
|
|
12822
|
-
process.on("SIGINT", () => gracefulExit("
|
|
12823
|
-
process.on("SIGTERM", () => gracefulExit("
|
|
12951
|
+
process.on("SIGINT", () => gracefulExit("sigint"));
|
|
12952
|
+
process.on("SIGTERM", () => gracefulExit("chrome-sigterm"));
|
|
12824
12953
|
process.stdin.on("end", () => {
|
|
12825
12954
|
process.stderr.write(`${LOG_PREFIX3} Primary Chrome stdin closed
|
|
12826
12955
|
`);
|
|
@@ -12833,9 +12962,8 @@ function startBridge(options) {
|
|
|
12833
12962
|
|
|
12834
12963
|
// src/server.ts
|
|
12835
12964
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
12836
|
-
import { existsSync as
|
|
12965
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
12837
12966
|
import http from "http";
|
|
12838
|
-
import { createConnection as createConnection2 } from "net";
|
|
12839
12967
|
import { dirname as dirname2 } from "path";
|
|
12840
12968
|
|
|
12841
12969
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
@@ -24912,7 +25040,7 @@ var Protocol = class {
|
|
|
24912
25040
|
return;
|
|
24913
25041
|
}
|
|
24914
25042
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
24915
|
-
await new Promise((
|
|
25043
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
24916
25044
|
options?.signal?.throwIfAborted();
|
|
24917
25045
|
}
|
|
24918
25046
|
} catch (error2) {
|
|
@@ -24929,7 +25057,7 @@ var Protocol = class {
|
|
|
24929
25057
|
*/
|
|
24930
25058
|
request(request, resultSchema, options) {
|
|
24931
25059
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
24932
|
-
return new Promise((
|
|
25060
|
+
return new Promise((resolve4, reject) => {
|
|
24933
25061
|
const earlyReject = (error2) => {
|
|
24934
25062
|
reject(error2);
|
|
24935
25063
|
};
|
|
@@ -25007,7 +25135,7 @@ var Protocol = class {
|
|
|
25007
25135
|
if (!parseResult.success) {
|
|
25008
25136
|
reject(parseResult.error);
|
|
25009
25137
|
} else {
|
|
25010
|
-
|
|
25138
|
+
resolve4(parseResult.data);
|
|
25011
25139
|
}
|
|
25012
25140
|
} catch (error2) {
|
|
25013
25141
|
reject(error2);
|
|
@@ -25268,12 +25396,12 @@ var Protocol = class {
|
|
|
25268
25396
|
}
|
|
25269
25397
|
} catch {
|
|
25270
25398
|
}
|
|
25271
|
-
return new Promise((
|
|
25399
|
+
return new Promise((resolve4, reject) => {
|
|
25272
25400
|
if (signal.aborted) {
|
|
25273
25401
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
25274
25402
|
return;
|
|
25275
25403
|
}
|
|
25276
|
-
const timeoutId = setTimeout(
|
|
25404
|
+
const timeoutId = setTimeout(resolve4, interval);
|
|
25277
25405
|
signal.addEventListener("abort", () => {
|
|
25278
25406
|
clearTimeout(timeoutId);
|
|
25279
25407
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -26232,7 +26360,7 @@ var McpServer = class {
|
|
|
26232
26360
|
let task = createTaskResult.task;
|
|
26233
26361
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
26234
26362
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
26235
|
-
await new Promise((
|
|
26363
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
26236
26364
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
26237
26365
|
if (!updatedTask) {
|
|
26238
26366
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -27020,12 +27148,12 @@ var StdioServerTransport = class {
|
|
|
27020
27148
|
this.onclose?.();
|
|
27021
27149
|
}
|
|
27022
27150
|
send(message2) {
|
|
27023
|
-
return new Promise((
|
|
27151
|
+
return new Promise((resolve4) => {
|
|
27024
27152
|
const json = serializeMessage(message2);
|
|
27025
27153
|
if (this._stdout.write(json)) {
|
|
27026
|
-
|
|
27154
|
+
resolve4();
|
|
27027
27155
|
} else {
|
|
27028
|
-
this._stdout.once("drain",
|
|
27156
|
+
this._stdout.once("drain", resolve4);
|
|
27029
27157
|
}
|
|
27030
27158
|
});
|
|
27031
27159
|
}
|
|
@@ -27439,7 +27567,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
27439
27567
|
});
|
|
27440
27568
|
if (!chunk) {
|
|
27441
27569
|
if (i === 1) {
|
|
27442
|
-
await new Promise((
|
|
27570
|
+
await new Promise((resolve4) => setTimeout(resolve4));
|
|
27443
27571
|
maxReadCount = 3;
|
|
27444
27572
|
continue;
|
|
27445
27573
|
}
|
|
@@ -27921,9 +28049,9 @@ data:
|
|
|
27921
28049
|
const initRequest = messages.find((m) => isInitializeRequest(m));
|
|
27922
28050
|
const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
27923
28051
|
if (this._enableJsonResponse) {
|
|
27924
|
-
return new Promise((
|
|
28052
|
+
return new Promise((resolve4) => {
|
|
27925
28053
|
this._streamMapping.set(streamId, {
|
|
27926
|
-
resolveJson:
|
|
28054
|
+
resolveJson: resolve4,
|
|
27927
28055
|
cleanup: () => {
|
|
27928
28056
|
this._streamMapping.delete(streamId);
|
|
27929
28057
|
}
|
|
@@ -28249,6 +28377,408 @@ var StreamableHTTPServerTransport = class {
|
|
|
28249
28377
|
}
|
|
28250
28378
|
};
|
|
28251
28379
|
|
|
28380
|
+
// src/native-host/bridge-client/index.ts
|
|
28381
|
+
import { createConnection as createConnection2 } from "net";
|
|
28382
|
+
|
|
28383
|
+
// src/native-host/bridge-client/connection-state.ts
|
|
28384
|
+
function transition(state, event) {
|
|
28385
|
+
if (event.kind === "stop") return "disconnected";
|
|
28386
|
+
switch (state) {
|
|
28387
|
+
case "disconnected":
|
|
28388
|
+
if (event.kind === "start") return "connecting";
|
|
28389
|
+
return state;
|
|
28390
|
+
case "connecting":
|
|
28391
|
+
if (event.kind === "socket-connected") return "handshaking";
|
|
28392
|
+
if (event.kind === "socket-closed") return "disconnected";
|
|
28393
|
+
return state;
|
|
28394
|
+
case "handshaking":
|
|
28395
|
+
if (event.kind === "bridge-status-true") return "ready";
|
|
28396
|
+
if (event.kind === "bridge-status-false") return "handshaking";
|
|
28397
|
+
if (event.kind === "bridge-closing") return "draining";
|
|
28398
|
+
if (event.kind === "socket-closed") return "disconnected";
|
|
28399
|
+
return state;
|
|
28400
|
+
case "ready":
|
|
28401
|
+
if (event.kind === "bridge-status-false") return "handshaking";
|
|
28402
|
+
if (event.kind === "bridge-closing") return "draining";
|
|
28403
|
+
if (event.kind === "socket-closed") return "disconnected";
|
|
28404
|
+
return state;
|
|
28405
|
+
case "draining":
|
|
28406
|
+
if (event.kind === "socket-closed") return "disconnected";
|
|
28407
|
+
return state;
|
|
28408
|
+
default: {
|
|
28409
|
+
const _exhaustive = state;
|
|
28410
|
+
return _exhaustive;
|
|
28411
|
+
}
|
|
28412
|
+
}
|
|
28413
|
+
}
|
|
28414
|
+
function isReady(state) {
|
|
28415
|
+
return state === "ready";
|
|
28416
|
+
}
|
|
28417
|
+
|
|
28418
|
+
// src/native-host/bridge-client/pending-queue.ts
|
|
28419
|
+
var PendingQueue = class {
|
|
28420
|
+
constructor(max, ttlMs, timers) {
|
|
28421
|
+
this.max = max;
|
|
28422
|
+
this.ttlMs = ttlMs;
|
|
28423
|
+
this.timers = timers;
|
|
28424
|
+
if (max < 1) throw new Error("PendingQueue max must be >= 1");
|
|
28425
|
+
if (ttlMs < 0) throw new Error("PendingQueue ttlMs must be >= 0");
|
|
28426
|
+
}
|
|
28427
|
+
entries = [];
|
|
28428
|
+
/** Try to enqueue. Returns `true` on success, `false` when rejected (onReject already fired). */
|
|
28429
|
+
enqueue(entry) {
|
|
28430
|
+
if (this.entries.length >= this.max) {
|
|
28431
|
+
entry.onReject("queue-full");
|
|
28432
|
+
return false;
|
|
28433
|
+
}
|
|
28434
|
+
const stored = { ...entry, timer: null };
|
|
28435
|
+
stored.timer = this.timers.setTimer(() => this.expire(stored), this.ttlMs);
|
|
28436
|
+
this.entries.push(stored);
|
|
28437
|
+
return true;
|
|
28438
|
+
}
|
|
28439
|
+
/** Flush every queued entry in FIFO order. */
|
|
28440
|
+
flushAll() {
|
|
28441
|
+
const snapshot = this.entries.splice(0, this.entries.length);
|
|
28442
|
+
for (const entry of snapshot) {
|
|
28443
|
+
this.timers.clearTimer(entry.timer);
|
|
28444
|
+
entry.onFlush(entry.payload);
|
|
28445
|
+
}
|
|
28446
|
+
}
|
|
28447
|
+
/** Reject every queued entry with the given reason. Used on `stop()`. */
|
|
28448
|
+
rejectAll(reason) {
|
|
28449
|
+
const snapshot = this.entries.splice(0, this.entries.length);
|
|
28450
|
+
for (const entry of snapshot) {
|
|
28451
|
+
this.timers.clearTimer(entry.timer);
|
|
28452
|
+
entry.onReject(reason);
|
|
28453
|
+
}
|
|
28454
|
+
}
|
|
28455
|
+
size() {
|
|
28456
|
+
return this.entries.length;
|
|
28457
|
+
}
|
|
28458
|
+
expire(entry) {
|
|
28459
|
+
const idx = this.entries.indexOf(entry);
|
|
28460
|
+
if (idx === -1) return;
|
|
28461
|
+
this.entries.splice(idx, 1);
|
|
28462
|
+
entry.onReject("timeout");
|
|
28463
|
+
}
|
|
28464
|
+
};
|
|
28465
|
+
|
|
28466
|
+
// src/native-host/bridge-client/index.ts
|
|
28467
|
+
var LOG_PREFIX4 = "[viyv-browser:mcp:bridge]";
|
|
28468
|
+
var METRIC_PREFIX = "[viyv-browser:mcp:metric]";
|
|
28469
|
+
var LINE_TERMINATOR = "\n";
|
|
28470
|
+
var BRIDGE_RECONNECT_CAP_MS = 3e3;
|
|
28471
|
+
var defaultTimers = {
|
|
28472
|
+
setTimer: (fn, ms) => setTimeout(fn, ms),
|
|
28473
|
+
clearTimer: (h) => clearTimeout(h)
|
|
28474
|
+
};
|
|
28475
|
+
var defaultSchedule = (fn, ms) => {
|
|
28476
|
+
const h = setTimeout(fn, ms);
|
|
28477
|
+
return () => clearTimeout(h);
|
|
28478
|
+
};
|
|
28479
|
+
var BridgeClient = class {
|
|
28480
|
+
constructor(options) {
|
|
28481
|
+
this.options = options;
|
|
28482
|
+
this.log = options.log ?? ((line) => process.stderr.write(`${line}
|
|
28483
|
+
`));
|
|
28484
|
+
this.metric = options.metric ?? ((line) => process.stderr.write(`${line}
|
|
28485
|
+
`));
|
|
28486
|
+
this.now = options.now ?? (() => Date.now());
|
|
28487
|
+
this.schedule = options.schedule ?? defaultSchedule;
|
|
28488
|
+
this.random = options.random ?? Math.random;
|
|
28489
|
+
this.queue = new PendingQueue(
|
|
28490
|
+
BRIDGE_READINESS.PENDING_QUEUE_MAX,
|
|
28491
|
+
BRIDGE_READINESS.TIMEOUT_MS,
|
|
28492
|
+
options.timers ?? defaultTimers
|
|
28493
|
+
);
|
|
28494
|
+
}
|
|
28495
|
+
state = "disconnected";
|
|
28496
|
+
socket = null;
|
|
28497
|
+
reconnectAttempt = 0;
|
|
28498
|
+
cancelReconnect = null;
|
|
28499
|
+
messageHandlers = /* @__PURE__ */ new Set();
|
|
28500
|
+
stateHandlers = /* @__PURE__ */ new Set();
|
|
28501
|
+
queue;
|
|
28502
|
+
/** ms since epoch when the current handshake started; `null` when not measuring. */
|
|
28503
|
+
handshakeStartMs = null;
|
|
28504
|
+
started = false;
|
|
28505
|
+
log;
|
|
28506
|
+
metric;
|
|
28507
|
+
now;
|
|
28508
|
+
schedule;
|
|
28509
|
+
random;
|
|
28510
|
+
start() {
|
|
28511
|
+
if (this.started) return;
|
|
28512
|
+
this.started = true;
|
|
28513
|
+
this.apply({ kind: "start" });
|
|
28514
|
+
this.connect();
|
|
28515
|
+
}
|
|
28516
|
+
stop() {
|
|
28517
|
+
this.started = false;
|
|
28518
|
+
this.cancelReconnect?.();
|
|
28519
|
+
this.cancelReconnect = null;
|
|
28520
|
+
this.queue.rejectAll("stopped");
|
|
28521
|
+
if (this.socket && !this.socket.destroyed) {
|
|
28522
|
+
this.socket.destroy();
|
|
28523
|
+
}
|
|
28524
|
+
this.socket = null;
|
|
28525
|
+
this.apply({ kind: "stop" });
|
|
28526
|
+
}
|
|
28527
|
+
/**
|
|
28528
|
+
* Drop the current socket and reconnect immediately. Used by switch_browser
|
|
28529
|
+
* to pivot to a fresh Chrome. Pending tool_calls are drained with the
|
|
28530
|
+
* SWITCH_IN_PROGRESS error by the caller before this is invoked.
|
|
28531
|
+
*/
|
|
28532
|
+
forceReconnect() {
|
|
28533
|
+
if (this.socket && !this.socket.destroyed) {
|
|
28534
|
+
this.socket.destroy();
|
|
28535
|
+
}
|
|
28536
|
+
this.socket = null;
|
|
28537
|
+
this.apply({ kind: "socket-closed" });
|
|
28538
|
+
if (this.started) {
|
|
28539
|
+
this.reconnectAttempt = 0;
|
|
28540
|
+
this.scheduleReconnect();
|
|
28541
|
+
}
|
|
28542
|
+
}
|
|
28543
|
+
getState() {
|
|
28544
|
+
return this.state;
|
|
28545
|
+
}
|
|
28546
|
+
isReady() {
|
|
28547
|
+
return isReady(this.state);
|
|
28548
|
+
}
|
|
28549
|
+
/**
|
|
28550
|
+
* Return the socket when it is TCP-live (handshaking, ready, or draining).
|
|
28551
|
+
* Ancillary writes like session_init / session_close tolerate writes during
|
|
28552
|
+
* handshake — the bridge buffers them until Chrome is reachable.
|
|
28553
|
+
* Returns null when the socket is not safe to write to.
|
|
28554
|
+
*/
|
|
28555
|
+
getSocket() {
|
|
28556
|
+
if (!this.socket || this.socket.destroyed) return null;
|
|
28557
|
+
if (this.state === "handshaking" || this.state === "ready" || this.state === "draining") {
|
|
28558
|
+
return this.socket;
|
|
28559
|
+
}
|
|
28560
|
+
return null;
|
|
28561
|
+
}
|
|
28562
|
+
onMessage(fn) {
|
|
28563
|
+
this.messageHandlers.add(fn);
|
|
28564
|
+
return () => this.messageHandlers.delete(fn);
|
|
28565
|
+
}
|
|
28566
|
+
onStateChange(fn) {
|
|
28567
|
+
this.stateHandlers.add(fn);
|
|
28568
|
+
return () => this.stateHandlers.delete(fn);
|
|
28569
|
+
}
|
|
28570
|
+
/**
|
|
28571
|
+
* Send a tool_call line. If the bridge is `ready`, writes immediately.
|
|
28572
|
+
* Otherwise enqueues until readiness, failing with `queue-full` if capacity
|
|
28573
|
+
* is exhausted or `timeout` if the handshake takes too long.
|
|
28574
|
+
*/
|
|
28575
|
+
enqueueToolCall(line) {
|
|
28576
|
+
return new Promise((resolve4, reject) => {
|
|
28577
|
+
if (this.state === "ready" && this.socket && !this.socket.destroyed) {
|
|
28578
|
+
try {
|
|
28579
|
+
this.socket.write(line.endsWith(LINE_TERMINATOR) ? line : line + LINE_TERMINATOR);
|
|
28580
|
+
resolve4();
|
|
28581
|
+
} catch (err) {
|
|
28582
|
+
reject({ reason: "write-failed", detail: err instanceof Error ? err.message : String(err) });
|
|
28583
|
+
}
|
|
28584
|
+
return;
|
|
28585
|
+
}
|
|
28586
|
+
const enqueued = this.queue.enqueue({
|
|
28587
|
+
payload: {
|
|
28588
|
+
line: line.endsWith(LINE_TERMINATOR) ? line : line + LINE_TERMINATOR,
|
|
28589
|
+
resolve: resolve4,
|
|
28590
|
+
reject
|
|
28591
|
+
},
|
|
28592
|
+
onFlush: (entry) => {
|
|
28593
|
+
if (this.socket && !this.socket.destroyed) {
|
|
28594
|
+
try {
|
|
28595
|
+
this.socket.write(entry.line);
|
|
28596
|
+
entry.resolve();
|
|
28597
|
+
} catch (err) {
|
|
28598
|
+
entry.reject({
|
|
28599
|
+
reason: "write-failed",
|
|
28600
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
28601
|
+
});
|
|
28602
|
+
}
|
|
28603
|
+
} else {
|
|
28604
|
+
entry.reject({ reason: "write-failed", detail: "socket unavailable at flush" });
|
|
28605
|
+
}
|
|
28606
|
+
},
|
|
28607
|
+
onReject: (reason) => {
|
|
28608
|
+
reject({ reason });
|
|
28609
|
+
}
|
|
28610
|
+
});
|
|
28611
|
+
if (enqueued) {
|
|
28612
|
+
this.emitMetric("queue.enqueue", { depth: this.queue.size() });
|
|
28613
|
+
}
|
|
28614
|
+
});
|
|
28615
|
+
}
|
|
28616
|
+
connect() {
|
|
28617
|
+
if (!this.started) return;
|
|
28618
|
+
this.cancelReconnect = null;
|
|
28619
|
+
this.apply({ kind: "start" });
|
|
28620
|
+
if (this.state !== "connecting") {
|
|
28621
|
+
this.forceState("connecting");
|
|
28622
|
+
}
|
|
28623
|
+
const sock = createConnection2({ host: this.options.host, port: this.options.port });
|
|
28624
|
+
this.socket = sock;
|
|
28625
|
+
sock.on("connect", () => {
|
|
28626
|
+
this.handshakeStartMs = this.now();
|
|
28627
|
+
sock.setKeepAlive(true, 3e4);
|
|
28628
|
+
this.reconnectAttempt = 0;
|
|
28629
|
+
this.apply({ kind: "socket-connected" });
|
|
28630
|
+
});
|
|
28631
|
+
sock.on("error", (err) => {
|
|
28632
|
+
this.log(`${LOG_PREFIX4} socket error: ${err.message}`);
|
|
28633
|
+
});
|
|
28634
|
+
sock.on("close", () => {
|
|
28635
|
+
const wasReady = this.state === "ready";
|
|
28636
|
+
if (this.socket === sock) this.socket = null;
|
|
28637
|
+
this.apply({ kind: "socket-closed" });
|
|
28638
|
+
if (wasReady) this.log(`${LOG_PREFIX4} bridge disconnected`);
|
|
28639
|
+
if (this.started) this.scheduleReconnect();
|
|
28640
|
+
});
|
|
28641
|
+
let lineBuffer = "";
|
|
28642
|
+
sock.on("data", (data) => {
|
|
28643
|
+
lineBuffer += data.toString("utf-8");
|
|
28644
|
+
const lines = lineBuffer.split(LINE_TERMINATOR);
|
|
28645
|
+
lineBuffer = lines.pop() ?? "";
|
|
28646
|
+
for (const line of lines) {
|
|
28647
|
+
if (!line) continue;
|
|
28648
|
+
try {
|
|
28649
|
+
const parsed = JSON.parse(line);
|
|
28650
|
+
this.handleIncoming(parsed);
|
|
28651
|
+
} catch (err) {
|
|
28652
|
+
this.log(
|
|
28653
|
+
`${LOG_PREFIX4} parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
28654
|
+
);
|
|
28655
|
+
}
|
|
28656
|
+
}
|
|
28657
|
+
});
|
|
28658
|
+
}
|
|
28659
|
+
handleIncoming(msg) {
|
|
28660
|
+
if (msg && typeof msg === "object") {
|
|
28661
|
+
const type = msg.type;
|
|
28662
|
+
if (type === "bridge_closing") {
|
|
28663
|
+
this.log(`${LOG_PREFIX4} received bridge_closing, will reconnect`);
|
|
28664
|
+
this.apply({ kind: "bridge-closing" });
|
|
28665
|
+
if (this.socket && !this.socket.destroyed) this.socket.destroy();
|
|
28666
|
+
return;
|
|
28667
|
+
}
|
|
28668
|
+
if (type === "bridge_status") {
|
|
28669
|
+
const connected = msg.connected === true;
|
|
28670
|
+
this.apply({ kind: connected ? "bridge-status-true" : "bridge-status-false" });
|
|
28671
|
+
}
|
|
28672
|
+
}
|
|
28673
|
+
for (const h of this.messageHandlers) {
|
|
28674
|
+
try {
|
|
28675
|
+
h(msg);
|
|
28676
|
+
} catch (err) {
|
|
28677
|
+
this.log(
|
|
28678
|
+
`${LOG_PREFIX4} handler threw: ${err instanceof Error ? err.message : String(err)}`
|
|
28679
|
+
);
|
|
28680
|
+
}
|
|
28681
|
+
}
|
|
28682
|
+
}
|
|
28683
|
+
scheduleReconnect() {
|
|
28684
|
+
if (!this.started) return;
|
|
28685
|
+
if (this.cancelReconnect) return;
|
|
28686
|
+
this.reconnectAttempt++;
|
|
28687
|
+
const base = Math.min(
|
|
28688
|
+
RECONNECT.INITIAL_DELAY * RECONNECT.MULTIPLIER ** (this.reconnectAttempt - 1),
|
|
28689
|
+
BRIDGE_RECONNECT_CAP_MS
|
|
28690
|
+
);
|
|
28691
|
+
const delay = applyJitter(base, RECONNECT.JITTER_RATIO, this.random);
|
|
28692
|
+
this.emitMetric("reconnect.scheduled", { attempt: this.reconnectAttempt, delayMs: delay });
|
|
28693
|
+
this.log(`${LOG_PREFIX4} reconnect in ${delay}ms (attempt ${this.reconnectAttempt})`);
|
|
28694
|
+
this.cancelReconnect = this.schedule(() => {
|
|
28695
|
+
this.cancelReconnect = null;
|
|
28696
|
+
this.connect();
|
|
28697
|
+
}, delay);
|
|
28698
|
+
}
|
|
28699
|
+
apply(event) {
|
|
28700
|
+
const next = transition(this.state, event);
|
|
28701
|
+
if (next === this.state) return;
|
|
28702
|
+
const prev = this.state;
|
|
28703
|
+
this.state = next;
|
|
28704
|
+
this.emitMetric("state.transition", { from: prev, to: next, event: event.kind });
|
|
28705
|
+
if (next === "ready") {
|
|
28706
|
+
if (this.handshakeStartMs !== null) {
|
|
28707
|
+
this.emitMetric("handshake.complete", {
|
|
28708
|
+
elapsedMs: this.now() - this.handshakeStartMs
|
|
28709
|
+
});
|
|
28710
|
+
this.handshakeStartMs = null;
|
|
28711
|
+
}
|
|
28712
|
+
this.queue.flushAll();
|
|
28713
|
+
}
|
|
28714
|
+
for (const h of this.stateHandlers) {
|
|
28715
|
+
try {
|
|
28716
|
+
h(next, prev);
|
|
28717
|
+
} catch (err) {
|
|
28718
|
+
this.log(
|
|
28719
|
+
`${LOG_PREFIX4} state handler threw: ${err instanceof Error ? err.message : String(err)}`
|
|
28720
|
+
);
|
|
28721
|
+
}
|
|
28722
|
+
}
|
|
28723
|
+
}
|
|
28724
|
+
/** Force a specific state (used only for recovering from impossible transitions). */
|
|
28725
|
+
forceState(to) {
|
|
28726
|
+
if (this.state === to) return;
|
|
28727
|
+
const prev = this.state;
|
|
28728
|
+
this.state = to;
|
|
28729
|
+
this.emitMetric("state.force", { from: prev, to });
|
|
28730
|
+
for (const h of this.stateHandlers) {
|
|
28731
|
+
try {
|
|
28732
|
+
h(to, prev);
|
|
28733
|
+
} catch {
|
|
28734
|
+
}
|
|
28735
|
+
}
|
|
28736
|
+
}
|
|
28737
|
+
emitMetric(name, fields) {
|
|
28738
|
+
this.metric(
|
|
28739
|
+
`${METRIC_PREFIX} ${name} ${Object.entries(fields).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(" ")}`
|
|
28740
|
+
);
|
|
28741
|
+
}
|
|
28742
|
+
};
|
|
28743
|
+
function toolCallRejectionToErrorCode(rej) {
|
|
28744
|
+
switch (rej.reason) {
|
|
28745
|
+
case "queue-full":
|
|
28746
|
+
return {
|
|
28747
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
28748
|
+
message: "Bridge readiness queue is full; try again shortly"
|
|
28749
|
+
};
|
|
28750
|
+
case "timeout":
|
|
28751
|
+
return {
|
|
28752
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
28753
|
+
message: "Timed out waiting for bridge readiness"
|
|
28754
|
+
};
|
|
28755
|
+
case "stopped":
|
|
28756
|
+
return {
|
|
28757
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
28758
|
+
message: "Bridge client was stopped"
|
|
28759
|
+
};
|
|
28760
|
+
case "write-failed":
|
|
28761
|
+
return {
|
|
28762
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
28763
|
+
message: `Socket write failed: ${rej.detail}`
|
|
28764
|
+
};
|
|
28765
|
+
}
|
|
28766
|
+
}
|
|
28767
|
+
|
|
28768
|
+
// src/bridge-protocol/shutdown-request-gate.ts
|
|
28769
|
+
var SHUTDOWN_REQUEST_COOLDOWN_MS = 3e4;
|
|
28770
|
+
function createShutdownGateState() {
|
|
28771
|
+
return { lastSentAt: null, lastVersion: null };
|
|
28772
|
+
}
|
|
28773
|
+
function shouldSendShutdownRequest(state, observedVersion, now, cooldownMs = SHUTDOWN_REQUEST_COOLDOWN_MS) {
|
|
28774
|
+
if (state.lastSentAt === null) return true;
|
|
28775
|
+
if (state.lastVersion !== observedVersion) return true;
|
|
28776
|
+
return now - state.lastSentAt >= cooldownMs;
|
|
28777
|
+
}
|
|
28778
|
+
function markSent(state, version2, now) {
|
|
28779
|
+
return { lastSentAt: now, lastVersion: version2 };
|
|
28780
|
+
}
|
|
28781
|
+
|
|
28252
28782
|
// ../shared/dist/bearer-equals.js
|
|
28253
28783
|
import { timingSafeEqual } from "crypto";
|
|
28254
28784
|
function bearerEquals(header, expectedToken) {
|
|
@@ -33775,8 +34305,12 @@ function computeToolTimeout(tool, input) {
|
|
|
33775
34305
|
|
|
33776
34306
|
// src/server.ts
|
|
33777
34307
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
33778
|
-
var
|
|
34308
|
+
var bridgeClient = null;
|
|
33779
34309
|
var bridgeUpdateInfo = null;
|
|
34310
|
+
var shutdownGate = createShutdownGateState();
|
|
34311
|
+
function getBridgeSocket() {
|
|
34312
|
+
return bridgeClient?.getSocket() ?? null;
|
|
34313
|
+
}
|
|
33780
34314
|
function readJwtConfigFromEnv() {
|
|
33781
34315
|
const alg = process.env.VIYV_JWT_ALG ?? "none";
|
|
33782
34316
|
return {
|
|
@@ -33844,8 +34378,9 @@ function bridgeRegisterAgent(agentId, sessionId) {
|
|
|
33844
34378
|
bridgeAgentRefs.set(agentId, set);
|
|
33845
34379
|
}
|
|
33846
34380
|
set.add(sessionId);
|
|
33847
|
-
if (isFirst
|
|
33848
|
-
|
|
34381
|
+
if (isFirst) {
|
|
34382
|
+
const sock = getBridgeSocket();
|
|
34383
|
+
if (sock) sendSessionInit(sock, agentId);
|
|
33849
34384
|
}
|
|
33850
34385
|
}
|
|
33851
34386
|
function bridgeUnregisterAgent(agentId, sessionId) {
|
|
@@ -33854,8 +34389,9 @@ function bridgeUnregisterAgent(agentId, sessionId) {
|
|
|
33854
34389
|
set.delete(sessionId);
|
|
33855
34390
|
if (set.size === 0) {
|
|
33856
34391
|
bridgeAgentRefs.delete(agentId);
|
|
33857
|
-
|
|
33858
|
-
|
|
34392
|
+
const sock = getBridgeSocket();
|
|
34393
|
+
if (sock) {
|
|
34394
|
+
sock.write(
|
|
33859
34395
|
`${JSON.stringify({
|
|
33860
34396
|
id: randomUUID5(),
|
|
33861
34397
|
type: "session_close",
|
|
@@ -33892,7 +34428,7 @@ function coerceShape(shape) {
|
|
|
33892
34428
|
return result;
|
|
33893
34429
|
}
|
|
33894
34430
|
function handleFileExport(filePath, result) {
|
|
33895
|
-
|
|
34431
|
+
mkdirSync4(dirname2(filePath), { recursive: true });
|
|
33896
34432
|
let content;
|
|
33897
34433
|
let metadata;
|
|
33898
34434
|
if (typeof result.data === "string") {
|
|
@@ -33912,7 +34448,7 @@ function handleFileExport(filePath, result) {
|
|
|
33912
34448
|
const pages = Array.isArray(result.pages) ? result.pages : [];
|
|
33913
34449
|
metadata = { ok: true, file_path: filePath, page_count: pages.length };
|
|
33914
34450
|
}
|
|
33915
|
-
|
|
34451
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
33916
34452
|
metadata.file_size = Buffer.byteLength(content, "utf-8");
|
|
33917
34453
|
return metadata;
|
|
33918
34454
|
}
|
|
@@ -34006,7 +34542,8 @@ async function startMcpServer(agentName, options) {
|
|
|
34006
34542
|
setDefaultAgentId(agentName);
|
|
34007
34543
|
}
|
|
34008
34544
|
configuredChromeProfile = options?.chromeProfile;
|
|
34009
|
-
|
|
34545
|
+
bridgeClient = initBridgeClient();
|
|
34546
|
+
bridgeClient.start();
|
|
34010
34547
|
if (options?.transport === "sse") {
|
|
34011
34548
|
const sessions2 = /* @__PURE__ */ new Map();
|
|
34012
34549
|
const httpServer = http.createServer();
|
|
@@ -34044,7 +34581,7 @@ async function startMcpServer(agentName, options) {
|
|
|
34044
34581
|
}
|
|
34045
34582
|
httpServer.close(() => {
|
|
34046
34583
|
});
|
|
34047
|
-
|
|
34584
|
+
bridgeClient?.stop();
|
|
34048
34585
|
process.exit(0);
|
|
34049
34586
|
};
|
|
34050
34587
|
process.on("SIGINT", () => {
|
|
@@ -34119,7 +34656,7 @@ async function startMcpServer(agentName, options) {
|
|
|
34119
34656
|
}
|
|
34120
34657
|
httpServer.close(() => {
|
|
34121
34658
|
});
|
|
34122
|
-
|
|
34659
|
+
bridgeClient?.stop();
|
|
34123
34660
|
process.exit(0);
|
|
34124
34661
|
};
|
|
34125
34662
|
process.on("SIGINT", () => {
|
|
@@ -34138,13 +34675,13 @@ async function startMcpServer(agentName, options) {
|
|
|
34138
34675
|
);
|
|
34139
34676
|
process.stdin.on("end", () => {
|
|
34140
34677
|
process.stderr.write("[viyv-browser:mcp] stdin closed, shutting down\n");
|
|
34141
|
-
|
|
34678
|
+
bridgeClient?.stop();
|
|
34142
34679
|
process.exit(0);
|
|
34143
34680
|
});
|
|
34144
34681
|
process.on("SIGINT", () => process.exit(0));
|
|
34145
34682
|
process.on("SIGTERM", () => process.exit(0));
|
|
34146
34683
|
process.on("exit", () => {
|
|
34147
|
-
|
|
34684
|
+
bridgeClient?.stop();
|
|
34148
34685
|
});
|
|
34149
34686
|
}
|
|
34150
34687
|
}
|
|
@@ -34299,7 +34836,7 @@ async function handleStreamableHttpRequest(req, res, sessions2) {
|
|
|
34299
34836
|
}
|
|
34300
34837
|
var MAX_BODY_SIZE = 4 * 1024 * 1024;
|
|
34301
34838
|
function parseJsonBody(req) {
|
|
34302
|
-
return new Promise((
|
|
34839
|
+
return new Promise((resolve4, reject) => {
|
|
34303
34840
|
const chunks = [];
|
|
34304
34841
|
let size = 0;
|
|
34305
34842
|
let destroyed = false;
|
|
@@ -34317,7 +34854,7 @@ function parseJsonBody(req) {
|
|
|
34317
34854
|
req.on("end", () => {
|
|
34318
34855
|
if (destroyed) return;
|
|
34319
34856
|
try {
|
|
34320
|
-
|
|
34857
|
+
resolve4(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
|
|
34321
34858
|
} catch (error2) {
|
|
34322
34859
|
reject(new Error(`Invalid JSON: ${error2.message}`));
|
|
34323
34860
|
}
|
|
@@ -34327,65 +34864,6 @@ function parseJsonBody(req) {
|
|
|
34327
34864
|
});
|
|
34328
34865
|
});
|
|
34329
34866
|
}
|
|
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
34867
|
function sendSessionInit(socket, agentId) {
|
|
34390
34868
|
const initMsg = {
|
|
34391
34869
|
id: randomUUID5(),
|
|
@@ -34400,65 +34878,79 @@ function sendSessionInit(socket, agentId) {
|
|
|
34400
34878
|
socket.write(`${JSON.stringify(initMsg)}
|
|
34401
34879
|
`);
|
|
34402
34880
|
}
|
|
34403
|
-
function
|
|
34404
|
-
|
|
34405
|
-
|
|
34881
|
+
function initBridgeClient() {
|
|
34882
|
+
const client = new BridgeClient({ host: BRIDGE.TCP_HOST, port: BRIDGE.TCP_PORT });
|
|
34883
|
+
let heartbeatInterval = null;
|
|
34884
|
+
client.onMessage((rawMsg) => {
|
|
34885
|
+
if (!rawMsg || typeof rawMsg !== "object") return;
|
|
34886
|
+
let parsed = rawMsg;
|
|
34887
|
+
if (parsed.type === "compressed" && typeof parsed.data === "string") {
|
|
34888
|
+
try {
|
|
34889
|
+
const decompressed = decompressPayload(parsed.data, true);
|
|
34890
|
+
parsed = JSON.parse(decompressed);
|
|
34891
|
+
} catch (err) {
|
|
34892
|
+
process.stderr.write(
|
|
34893
|
+
`[viyv-browser:mcp] Decompress error: ${err instanceof Error ? err.message : String(err)}
|
|
34406
34894
|
`
|
|
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;
|
|
34895
|
+
);
|
|
34896
|
+
return;
|
|
34897
|
+
}
|
|
34418
34898
|
}
|
|
34419
|
-
|
|
34420
|
-
|
|
34421
|
-
|
|
34422
|
-
|
|
34423
|
-
|
|
34424
|
-
|
|
34425
|
-
})}
|
|
34899
|
+
handleExtensionMessage(parsed);
|
|
34900
|
+
});
|
|
34901
|
+
client.onStateChange((next, prev) => {
|
|
34902
|
+
if (next === "ready" && prev !== "ready") {
|
|
34903
|
+
process.stderr.write(
|
|
34904
|
+
`[viyv-browser:mcp] Connected to bridge at ${BRIDGE.TCP_HOST}:${BRIDGE.TCP_PORT}
|
|
34426
34905
|
`
|
|
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
|
-
`
|
|
34906
|
+
);
|
|
34907
|
+
setExtensionConnected(true);
|
|
34908
|
+
const sock = client.getSocket();
|
|
34909
|
+
if (sock) {
|
|
34910
|
+
const agentId = getDefaultAgentId();
|
|
34911
|
+
sendSessionInit(sock, agentId);
|
|
34912
|
+
bridgeResyncAgents(sock);
|
|
34913
|
+
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
34914
|
+
heartbeatInterval = setInterval(() => {
|
|
34915
|
+
const s = client.getSocket();
|
|
34916
|
+
if (!s || s.destroyed) {
|
|
34917
|
+
if (heartbeatInterval) {
|
|
34918
|
+
clearInterval(heartbeatInterval);
|
|
34919
|
+
heartbeatInterval = null;
|
|
34920
|
+
}
|
|
34921
|
+
return;
|
|
34922
|
+
}
|
|
34923
|
+
s.write(
|
|
34924
|
+
`${JSON.stringify({
|
|
34925
|
+
id: randomUUID5(),
|
|
34926
|
+
type: "session_heartbeat",
|
|
34927
|
+
agentId,
|
|
34928
|
+
timestamp: Date.now()
|
|
34929
|
+
})}
|
|
34930
|
+
`
|
|
34931
|
+
);
|
|
34932
|
+
}, TIMEOUTS.HEARTBEAT);
|
|
34933
|
+
}
|
|
34934
|
+
}
|
|
34935
|
+
if (prev === "ready" && next !== "ready") {
|
|
34936
|
+
setExtensionConnected(false);
|
|
34937
|
+
if (heartbeatInterval) {
|
|
34938
|
+
clearInterval(heartbeatInterval);
|
|
34939
|
+
heartbeatInterval = null;
|
|
34940
|
+
}
|
|
34941
|
+
for (const [id, pending] of pendingRequests) {
|
|
34942
|
+
clearTimeout(pending.timer);
|
|
34943
|
+
pendingRequests.delete(id);
|
|
34944
|
+
pending.resolve({
|
|
34945
|
+
error: {
|
|
34946
|
+
code: "EXTENSION_NOT_CONNECTED",
|
|
34947
|
+
message: "Bridge disconnected while request was pending"
|
|
34948
|
+
}
|
|
34949
|
+
});
|
|
34452
34950
|
}
|
|
34453
34951
|
}
|
|
34454
34952
|
});
|
|
34455
|
-
|
|
34456
|
-
process.stderr.write(`[viyv-browser:mcp] Bridge socket error: ${error2.message}
|
|
34457
|
-
`);
|
|
34458
|
-
});
|
|
34459
|
-
return () => {
|
|
34460
|
-
clearInterval(heartbeatInterval);
|
|
34461
|
-
};
|
|
34953
|
+
return client;
|
|
34462
34954
|
}
|
|
34463
34955
|
function handleExtensionMessage(message2) {
|
|
34464
34956
|
if (!message2 || typeof message2 !== "object") return;
|
|
@@ -34516,9 +35008,10 @@ function handleExtensionMessage(message2) {
|
|
|
34516
35008
|
} else {
|
|
34517
35009
|
setExtensionConnected(true);
|
|
34518
35010
|
recordHeartbeat();
|
|
34519
|
-
|
|
35011
|
+
const sockForReinit = getBridgeSocket();
|
|
35012
|
+
if (sockForReinit && listSessions().length === 0) {
|
|
34520
35013
|
const agentId = getDefaultAgentId();
|
|
34521
|
-
sendSessionInit(
|
|
35014
|
+
sendSessionInit(sockForReinit, agentId);
|
|
34522
35015
|
process.stderr.write(
|
|
34523
35016
|
`[viyv-browser:mcp] Session re-initialized after Chrome reconnect: ${agentId}
|
|
34524
35017
|
`
|
|
@@ -34529,14 +35022,24 @@ function handleExtensionMessage(message2) {
|
|
|
34529
35022
|
const bridgeVersion = typeof msg.bridgeVersion === "string" ? msg.bridgeVersion : null;
|
|
34530
35023
|
if (bridgeVersion && shouldUpdateBridge(bridgeVersion)) {
|
|
34531
35024
|
const serverVersion = getPackageVersion();
|
|
34532
|
-
|
|
34533
|
-
|
|
35025
|
+
const nowMs = Date.now();
|
|
35026
|
+
if (shouldSendShutdownRequest(shutdownGate, bridgeVersion, nowMs)) {
|
|
35027
|
+
process.stderr.write(
|
|
35028
|
+
`[viyv-browser:mcp] Bridge version mismatch (bridge=${bridgeVersion}, server=${serverVersion}), requesting restart
|
|
34534
35029
|
`
|
|
34535
|
-
|
|
34536
|
-
|
|
34537
|
-
|
|
34538
|
-
|
|
34539
|
-
|
|
35030
|
+
);
|
|
35031
|
+
bridgeUpdateInfo = { from: bridgeVersion, to: serverVersion };
|
|
35032
|
+
const shutdownSock = getBridgeSocket();
|
|
35033
|
+
if (shutdownSock) {
|
|
35034
|
+
shutdownSock.write(
|
|
35035
|
+
`${JSON.stringify({ type: "bridge_shutdown_request", timestamp: nowMs })}
|
|
35036
|
+
`
|
|
35037
|
+
);
|
|
35038
|
+
shutdownGate = markSent(shutdownGate, bridgeVersion, nowMs);
|
|
35039
|
+
}
|
|
35040
|
+
} else {
|
|
35041
|
+
process.stderr.write(
|
|
35042
|
+
`[viyv-browser:mcp] Skipping bridge_shutdown_request \u2014 same version ${bridgeVersion} within cooldown (orphan MCP or external tampering is re-seeding the deploy binary; check for stale viyv-browser-mcp processes)
|
|
34540
35043
|
`
|
|
34541
35044
|
);
|
|
34542
35045
|
}
|
|
@@ -34665,7 +35168,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34665
35168
|
}
|
|
34666
35169
|
if (tool === "browser_health") {
|
|
34667
35170
|
const health = getHealthStatus();
|
|
34668
|
-
if (!health.extensionConnected || !
|
|
35171
|
+
if (!health.extensionConnected || !getBridgeSocket()) {
|
|
34669
35172
|
return {
|
|
34670
35173
|
content: [{ type: "text", text: JSON.stringify(health) }]
|
|
34671
35174
|
};
|
|
@@ -34679,7 +35182,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34679
35182
|
if (paths) {
|
|
34680
35183
|
for (const p of paths) {
|
|
34681
35184
|
try {
|
|
34682
|
-
if (!
|
|
35185
|
+
if (!existsSync3(p) || !statSync(p).isFile()) {
|
|
34683
35186
|
return {
|
|
34684
35187
|
content: [
|
|
34685
35188
|
{
|
|
@@ -34711,7 +35214,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34711
35214
|
let csvData;
|
|
34712
35215
|
if (typeof input.file_path === "string") {
|
|
34713
35216
|
const fp = input.file_path;
|
|
34714
|
-
if (!
|
|
35217
|
+
if (!existsSync3(fp) || !statSync(fp).isFile()) {
|
|
34715
35218
|
return {
|
|
34716
35219
|
content: [
|
|
34717
35220
|
{
|
|
@@ -34723,7 +35226,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34723
35226
|
]
|
|
34724
35227
|
};
|
|
34725
35228
|
}
|
|
34726
|
-
csvData =
|
|
35229
|
+
csvData = readFileSync5(fp, "utf-8");
|
|
34727
35230
|
} else if (typeof input.data === "string") {
|
|
34728
35231
|
csvData = input.data;
|
|
34729
35232
|
}
|
|
@@ -34749,7 +35252,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34749
35252
|
}
|
|
34750
35253
|
}
|
|
34751
35254
|
}
|
|
34752
|
-
if (!
|
|
35255
|
+
if (!bridgeClient) {
|
|
34753
35256
|
return {
|
|
34754
35257
|
content: [
|
|
34755
35258
|
{
|
|
@@ -34757,7 +35260,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34757
35260
|
text: JSON.stringify({
|
|
34758
35261
|
error: {
|
|
34759
35262
|
code: "EXTENSION_NOT_CONNECTED",
|
|
34760
|
-
message: "
|
|
35263
|
+
message: "Bridge client not initialized"
|
|
34761
35264
|
}
|
|
34762
35265
|
})
|
|
34763
35266
|
}
|
|
@@ -34767,36 +35270,15 @@ async function callExtensionTool(tool, input) {
|
|
|
34767
35270
|
const requestId = randomUUID5();
|
|
34768
35271
|
const agentId = getCurrentAgentId(getDefaultAgentId);
|
|
34769
35272
|
touchSession(agentId);
|
|
34770
|
-
const
|
|
35273
|
+
const client = bridgeClient;
|
|
34771
35274
|
const toolTimeout = computeToolTimeout(tool, input);
|
|
34772
|
-
return new Promise((
|
|
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
|
-
};
|
|
35275
|
+
return new Promise((resolve4) => {
|
|
34793
35276
|
const removeErrorListener = () => {
|
|
34794
|
-
sock.removeListener("error", onError);
|
|
34795
35277
|
};
|
|
34796
35278
|
const timer = setTimeout(() => {
|
|
34797
35279
|
pendingRequests.delete(requestId);
|
|
34798
35280
|
removeErrorListener();
|
|
34799
|
-
|
|
35281
|
+
resolve4({
|
|
34800
35282
|
content: [
|
|
34801
35283
|
{
|
|
34802
35284
|
type: "text",
|
|
@@ -34823,12 +35305,12 @@ async function callExtensionTool(tool, input) {
|
|
|
34823
35305
|
if (FILE_EXPORT_TOOLS.has(tool) && typeof filePath === "string" && ("data" in result || "pages" in result)) {
|
|
34824
35306
|
try {
|
|
34825
35307
|
const metadata = handleFileExport(filePath, result);
|
|
34826
|
-
|
|
35308
|
+
resolve4({
|
|
34827
35309
|
content: [{ type: "text", text: JSON.stringify(metadata) }]
|
|
34828
35310
|
});
|
|
34829
35311
|
} catch (e) {
|
|
34830
35312
|
const msg = e instanceof Error ? e.message : String(e);
|
|
34831
|
-
|
|
35313
|
+
resolve4({
|
|
34832
35314
|
content: [
|
|
34833
35315
|
{
|
|
34834
35316
|
type: "text",
|
|
@@ -34847,9 +35329,9 @@ async function callExtensionTool(tool, input) {
|
|
|
34847
35329
|
if (RESULT_EXPORT_TOOLS.has(tool) && typeof filePath === "string") {
|
|
34848
35330
|
try {
|
|
34849
35331
|
const content = JSON.stringify(result, null, 2);
|
|
34850
|
-
|
|
34851
|
-
|
|
34852
|
-
|
|
35332
|
+
mkdirSync4(dirname2(filePath), { recursive: true });
|
|
35333
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
35334
|
+
resolve4({
|
|
34853
35335
|
content: [
|
|
34854
35336
|
{
|
|
34855
35337
|
type: "text",
|
|
@@ -34862,7 +35344,7 @@ async function callExtensionTool(tool, input) {
|
|
|
34862
35344
|
});
|
|
34863
35345
|
} catch (e) {
|
|
34864
35346
|
const msg = e instanceof Error ? e.message : String(e);
|
|
34865
|
-
|
|
35347
|
+
resolve4({
|
|
34866
35348
|
content: [
|
|
34867
35349
|
{
|
|
34868
35350
|
type: "text",
|
|
@@ -34889,21 +35371,21 @@ async function callExtensionTool(tool, input) {
|
|
|
34889
35371
|
}
|
|
34890
35372
|
if (tempImportFile) {
|
|
34891
35373
|
try {
|
|
34892
|
-
|
|
35374
|
+
unlinkSync3(tempImportFile);
|
|
34893
35375
|
} catch {
|
|
34894
35376
|
}
|
|
34895
35377
|
}
|
|
34896
|
-
|
|
35378
|
+
resolve4({ content: buildResponseContent(tool, finalResult) });
|
|
34897
35379
|
},
|
|
34898
35380
|
reject: (error2) => {
|
|
34899
35381
|
removeErrorListener();
|
|
34900
35382
|
if (tempImportFile) {
|
|
34901
35383
|
try {
|
|
34902
|
-
|
|
35384
|
+
unlinkSync3(tempImportFile);
|
|
34903
35385
|
} catch {
|
|
34904
35386
|
}
|
|
34905
35387
|
}
|
|
34906
|
-
|
|
35388
|
+
resolve4({
|
|
34907
35389
|
content: [
|
|
34908
35390
|
{
|
|
34909
35391
|
type: "text",
|
|
@@ -34937,13 +35419,17 @@ async function callExtensionTool(tool, input) {
|
|
|
34937
35419
|
`[viyv-browser:mcp] tool_call id=${requestId} agent=${agentId} pid=${ctxLog?.claims?.pid ?? chromeProfile ?? "-"} tool=${tool}
|
|
34938
35420
|
`
|
|
34939
35421
|
);
|
|
34940
|
-
|
|
34941
|
-
`)
|
|
34942
|
-
|
|
34943
|
-
|
|
35422
|
+
client.enqueueToolCall(`${JSON.stringify(request)}
|
|
35423
|
+
`).catch((rej) => {
|
|
35424
|
+
const pending = pendingRequests.get(requestId);
|
|
35425
|
+
if (!pending) return;
|
|
35426
|
+
clearTimeout(pending.timer);
|
|
35427
|
+
pendingRequests.delete(requestId);
|
|
35428
|
+
const { code, message: message2 } = toolCallRejectionToErrorCode(rej);
|
|
35429
|
+
resolve4({
|
|
35430
|
+
content: [{ type: "text", text: JSON.stringify({ error: { code, message: message2 } }) }]
|
|
34944
35431
|
});
|
|
34945
|
-
}
|
|
34946
|
-
sock.once("error", onError);
|
|
35432
|
+
});
|
|
34947
35433
|
});
|
|
34948
35434
|
}
|
|
34949
35435
|
async function handleSwitchBrowser() {
|
|
@@ -34958,19 +35444,18 @@ async function handleSwitchBrowser() {
|
|
|
34958
35444
|
}
|
|
34959
35445
|
});
|
|
34960
35446
|
}
|
|
34961
|
-
if (
|
|
35447
|
+
if (bridgeClient) {
|
|
34962
35448
|
process.stderr.write("[viyv-browser:mcp] switch_browser: closing current connection\n");
|
|
34963
|
-
|
|
34964
|
-
extensionSocket = null;
|
|
35449
|
+
bridgeClient.forceReconnect();
|
|
34965
35450
|
setExtensionConnected(false);
|
|
34966
35451
|
}
|
|
34967
35452
|
process.stderr.write("[viyv-browser:mcp] switch_browser: waiting for bridge reconnection...\n");
|
|
34968
|
-
return new Promise((
|
|
35453
|
+
return new Promise((resolve4) => {
|
|
34969
35454
|
const checkInterval = setInterval(() => {
|
|
34970
|
-
if (
|
|
35455
|
+
if (bridgeClient?.isReady() && isExtensionConnected()) {
|
|
34971
35456
|
clearInterval(checkInterval);
|
|
34972
35457
|
clearTimeout(timer);
|
|
34973
|
-
|
|
35458
|
+
resolve4({
|
|
34974
35459
|
content: [
|
|
34975
35460
|
{
|
|
34976
35461
|
type: "text",
|
|
@@ -34985,7 +35470,7 @@ async function handleSwitchBrowser() {
|
|
|
34985
35470
|
}, 500);
|
|
34986
35471
|
const timer = setTimeout(() => {
|
|
34987
35472
|
clearInterval(checkInterval);
|
|
34988
|
-
|
|
35473
|
+
resolve4({
|
|
34989
35474
|
content: [
|
|
34990
35475
|
{
|
|
34991
35476
|
type: "text",
|
|
@@ -35004,9 +35489,9 @@ async function handleSwitchBrowser() {
|
|
|
35004
35489
|
|
|
35005
35490
|
// src/setup.ts
|
|
35006
35491
|
import { execSync } from "child_process";
|
|
35007
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
35492
|
+
import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
35008
35493
|
import { homedir as homedir2, platform } from "os";
|
|
35009
|
-
import { dirname as dirname3, resolve as
|
|
35494
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
35010
35495
|
function runSetup(options = {}) {
|
|
35011
35496
|
const os = platform();
|
|
35012
35497
|
console.log("Viyv Browser MCP - Native Messaging Host Setup");
|
|
@@ -35038,8 +35523,8 @@ function runSetup(options = {}) {
|
|
|
35038
35523
|
console.log(`Wrapper: ${wrapperPath}`);
|
|
35039
35524
|
for (const manifestPath of manifestPaths) {
|
|
35040
35525
|
console.log(`Manifest path: ${manifestPath}`);
|
|
35041
|
-
|
|
35042
|
-
|
|
35526
|
+
mkdirSync5(dirname3(manifestPath), { recursive: true });
|
|
35527
|
+
writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2));
|
|
35043
35528
|
chmodSync3(manifestPath, 420);
|
|
35044
35529
|
}
|
|
35045
35530
|
console.log("\nNative Messaging Host registered successfully!");
|
|
@@ -35053,17 +35538,17 @@ function runSetup(options = {}) {
|
|
|
35053
35538
|
}
|
|
35054
35539
|
function createNativeHostWrapper(os, binaryPath, dockerMode) {
|
|
35055
35540
|
const manifestDir = dockerMode ? "/etc/chromium/native-messaging-hosts" : dirname3(getManifestPath(os));
|
|
35056
|
-
|
|
35541
|
+
mkdirSync5(manifestDir, { recursive: true });
|
|
35057
35542
|
const nodePath = getNodePath();
|
|
35058
35543
|
if (os === "win32") {
|
|
35059
|
-
const wrapperPath2 =
|
|
35060
|
-
|
|
35544
|
+
const wrapperPath2 = resolve3(manifestDir, `${NATIVE_HOST_NAME}.bat`);
|
|
35545
|
+
writeFileSync4(wrapperPath2, `@echo off\r
|
|
35061
35546
|
"${nodePath}" "${binaryPath}" --native-host\r
|
|
35062
35547
|
`);
|
|
35063
35548
|
return wrapperPath2;
|
|
35064
35549
|
}
|
|
35065
|
-
const wrapperPath =
|
|
35066
|
-
|
|
35550
|
+
const wrapperPath = resolve3(manifestDir, `${NATIVE_HOST_NAME}.sh`);
|
|
35551
|
+
writeFileSync4(wrapperPath, `#!/bin/bash
|
|
35067
35552
|
exec "${nodePath}" "${binaryPath}" --native-host
|
|
35068
35553
|
`);
|
|
35069
35554
|
chmodSync3(wrapperPath, 493);
|
|
@@ -35078,23 +35563,23 @@ function getNodePath() {
|
|
|
35078
35563
|
}
|
|
35079
35564
|
function getDockerManifestPaths() {
|
|
35080
35565
|
return [
|
|
35081
|
-
|
|
35082
|
-
|
|
35566
|
+
resolve3("/etc/chromium/native-messaging-hosts", `${NATIVE_HOST_NAME}.json`),
|
|
35567
|
+
resolve3(homedir2(), ".config/chromium/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`)
|
|
35083
35568
|
];
|
|
35084
35569
|
}
|
|
35085
35570
|
function getManifestPath(os) {
|
|
35086
35571
|
const home = homedir2();
|
|
35087
35572
|
switch (os) {
|
|
35088
35573
|
case "darwin":
|
|
35089
|
-
return
|
|
35574
|
+
return resolve3(
|
|
35090
35575
|
home,
|
|
35091
35576
|
"Library/Application Support/Google/Chrome/NativeMessagingHosts",
|
|
35092
35577
|
`${NATIVE_HOST_NAME}.json`
|
|
35093
35578
|
);
|
|
35094
35579
|
case "linux":
|
|
35095
|
-
return
|
|
35580
|
+
return resolve3(home, ".config/google-chrome/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`);
|
|
35096
35581
|
case "win32":
|
|
35097
|
-
return
|
|
35582
|
+
return resolve3(
|
|
35098
35583
|
home,
|
|
35099
35584
|
"AppData/Local/Google/Chrome/User Data/NativeMessagingHosts",
|
|
35100
35585
|
`${NATIVE_HOST_NAME}.json`
|
|
@@ -35114,9 +35599,9 @@ function setupClaudeDesktopConfig() {
|
|
|
35114
35599
|
const configPath = getClaudeDesktopConfigPath();
|
|
35115
35600
|
console.log(`Config path: ${configPath}`);
|
|
35116
35601
|
let config2 = {};
|
|
35117
|
-
if (
|
|
35602
|
+
if (existsSync4(configPath)) {
|
|
35118
35603
|
try {
|
|
35119
|
-
config2 = JSON.parse(
|
|
35604
|
+
config2 = JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
35120
35605
|
} catch {
|
|
35121
35606
|
console.error(
|
|
35122
35607
|
`ERROR: Failed to parse ${configPath}. Fix the JSON manually or delete the file.`
|
|
@@ -35136,8 +35621,8 @@ function setupClaudeDesktopConfig() {
|
|
|
35136
35621
|
env: { PATH: envPath }
|
|
35137
35622
|
};
|
|
35138
35623
|
const configDir = dirname3(configPath);
|
|
35139
|
-
|
|
35140
|
-
|
|
35624
|
+
mkdirSync5(configDir, { recursive: true });
|
|
35625
|
+
writeFileSync4(configPath, JSON.stringify(config2, null, 2));
|
|
35141
35626
|
console.log(`npx path: ${npxPath}`);
|
|
35142
35627
|
console.log("\nClaude Desktop config updated successfully!");
|
|
35143
35628
|
console.log("Please restart Claude Desktop for changes to take effect.");
|
|
@@ -35154,13 +35639,13 @@ function getClaudeDesktopConfigPath() {
|
|
|
35154
35639
|
const home = homedir2();
|
|
35155
35640
|
switch (process.platform) {
|
|
35156
35641
|
case "darwin":
|
|
35157
|
-
return
|
|
35642
|
+
return resolve3(home, "Library/Application Support/Claude/claude_desktop_config.json");
|
|
35158
35643
|
case "linux":
|
|
35159
|
-
return
|
|
35644
|
+
return resolve3(home, ".config/Claude/claude_desktop_config.json");
|
|
35160
35645
|
case "win32":
|
|
35161
|
-
return
|
|
35646
|
+
return resolve3(home, "AppData/Roaming/Claude/claude_desktop_config.json");
|
|
35162
35647
|
default:
|
|
35163
|
-
return
|
|
35648
|
+
return resolve3(home, ".config/Claude/claude_desktop_config.json");
|
|
35164
35649
|
}
|
|
35165
35650
|
}
|
|
35166
35651
|
|
|
@@ -35202,7 +35687,18 @@ if (args.includes("setup")) {
|
|
|
35202
35687
|
const authBearer = authIdx >= 0 ? args[authIdx + 1] : process.env.VIYV_MCP_AUTH_BEARER;
|
|
35203
35688
|
if (isNativeHostDeployed()) {
|
|
35204
35689
|
try {
|
|
35205
|
-
|
|
35690
|
+
const decision = syncNativeHostBinaryIfNeeded();
|
|
35691
|
+
if (decision.shouldSync) {
|
|
35692
|
+
process.stderr.write(
|
|
35693
|
+
`[viyv-browser:mcp] Native host binary synced (self=${decision.selfVersion}, was=${decision.deployedVersion ?? "unknown"}, reason=${decision.reason})
|
|
35694
|
+
`
|
|
35695
|
+
);
|
|
35696
|
+
} else {
|
|
35697
|
+
process.stderr.write(
|
|
35698
|
+
`[viyv-browser:mcp] Native host sync skipped \u2014 deployed v${decision.deployedVersion} is newer than self v${decision.selfVersion} (preventing downgrade by orphan MCP)
|
|
35699
|
+
`
|
|
35700
|
+
);
|
|
35701
|
+
}
|
|
35206
35702
|
} catch (err) {
|
|
35207
35703
|
process.stderr.write(
|
|
35208
35704
|
`[viyv-browser:mcp] WARNING: Failed to update native host binary: ${err.message}
|